diff --git a/apps/extension/src/shared/extension-agent-session-manager.ts b/apps/extension/src/shared/extension-agent-session-manager.ts index 216769c29e0..864142b1602 100644 --- a/apps/extension/src/shared/extension-agent-session-manager.ts +++ b/apps/extension/src/shared/extension-agent-session-manager.ts @@ -615,6 +615,7 @@ export function createExtensionAgentSessionManager({ !rs ), organizationId: sessionResult.organization_id, + profileId: sessionResult.profile_id, prompt: rs?.prompt ?? null, repository: rs?.githubRepo ?? null, ...(rs?.runtimeAgents === undefined ? {} : { runtimeAgents: rs.runtimeAgents }), diff --git a/apps/mobile/src/app/(app)/(tabs)/(3_profile)/_layout.tsx b/apps/mobile/src/app/(app)/(tabs)/(3_profile)/_layout.tsx index e77c1584b5f..a5bd90cf2d7 100644 --- a/apps/mobile/src/app/(app)/(tabs)/(3_profile)/_layout.tsx +++ b/apps/mobile/src/app/(app)/(tabs)/(3_profile)/_layout.tsx @@ -13,6 +13,7 @@ export default function ProfileLayout() { [['personalReviewAgent']], [['securityAgent']], [['kiloPass']], + [['agentProfiles']], ]); return ; } diff --git a/apps/mobile/src/app/(app)/(tabs)/(3_profile)/profiles/[profileId]/agents.tsx b/apps/mobile/src/app/(app)/(tabs)/(3_profile)/profiles/[profileId]/agents.tsx new file mode 100644 index 00000000000..1b53c10de17 --- /dev/null +++ b/apps/mobile/src/app/(app)/(tabs)/(3_profile)/profiles/[profileId]/agents.tsx @@ -0,0 +1,23 @@ +import { useLocalSearchParams } from 'expo-router'; + +import { InvalidRouteState } from '@/components/invalid-route-state'; +import { ProfileAgentsScreen } from '@/components/profiles/profile-agents-screen'; +import { getProfilesPath } from '@/lib/profile-agent-navigation'; +import { parseParam } from '@/lib/route-params'; + +export default function ProfileAgentsRoute() { + const { profileId: rawProfileId, organizationId: rawOrganizationId } = useLocalSearchParams<{ + profileId: string; + organizationId?: string; + }>(); + const profileId = parseParam(rawProfileId); + if (!profileId) { + return ; + } + return ( + + ); +} diff --git a/apps/mobile/src/app/(app)/(tabs)/(3_profile)/profiles/[profileId]/commands.tsx b/apps/mobile/src/app/(app)/(tabs)/(3_profile)/profiles/[profileId]/commands.tsx new file mode 100644 index 00000000000..e4aa89b7c1f --- /dev/null +++ b/apps/mobile/src/app/(app)/(tabs)/(3_profile)/profiles/[profileId]/commands.tsx @@ -0,0 +1,23 @@ +import { useLocalSearchParams } from 'expo-router'; + +import { InvalidRouteState } from '@/components/invalid-route-state'; +import { ProfileCommandsScreen } from '@/components/profiles/profile-commands-screen'; +import { getProfilesPath } from '@/lib/profile-agent-navigation'; +import { parseParam } from '@/lib/route-params'; + +export default function ProfileCommandsRoute() { + const { profileId: rawProfileId, organizationId: rawOrganizationId } = useLocalSearchParams<{ + profileId: string; + organizationId?: string; + }>(); + const profileId = parseParam(rawProfileId); + if (!profileId) { + return ; + } + return ( + + ); +} diff --git a/apps/mobile/src/app/(app)/(tabs)/(3_profile)/profiles/[profileId]/index.tsx b/apps/mobile/src/app/(app)/(tabs)/(3_profile)/profiles/[profileId]/index.tsx new file mode 100644 index 00000000000..e6581d3064d --- /dev/null +++ b/apps/mobile/src/app/(app)/(tabs)/(3_profile)/profiles/[profileId]/index.tsx @@ -0,0 +1,23 @@ +import { useLocalSearchParams } from 'expo-router'; + +import { InvalidRouteState } from '@/components/invalid-route-state'; +import { ProfileOverviewScreen } from '@/components/profiles/profile-overview-screen'; +import { getProfilesPath } from '@/lib/profile-agent-navigation'; +import { parseParam } from '@/lib/route-params'; + +export default function ProfileOverviewRoute() { + const { profileId: rawProfileId, organizationId: rawOrganizationId } = useLocalSearchParams<{ + profileId: string; + organizationId?: string; + }>(); + const profileId = parseParam(rawProfileId); + if (!profileId) { + return ; + } + return ( + + ); +} diff --git a/apps/mobile/src/app/(app)/(tabs)/(3_profile)/profiles/[profileId]/mcp.tsx b/apps/mobile/src/app/(app)/(tabs)/(3_profile)/profiles/[profileId]/mcp.tsx new file mode 100644 index 00000000000..2888b773ee0 --- /dev/null +++ b/apps/mobile/src/app/(app)/(tabs)/(3_profile)/profiles/[profileId]/mcp.tsx @@ -0,0 +1,23 @@ +import { useLocalSearchParams } from 'expo-router'; + +import { InvalidRouteState } from '@/components/invalid-route-state'; +import { ProfileMcpScreen } from '@/components/profiles/profile-mcp-screen'; +import { getProfilesPath } from '@/lib/profile-agent-navigation'; +import { parseParam } from '@/lib/route-params'; + +export default function ProfileMcpRoute() { + const { profileId: rawProfileId, organizationId: rawOrganizationId } = useLocalSearchParams<{ + profileId: string; + organizationId?: string; + }>(); + const profileId = parseParam(rawProfileId); + if (!profileId) { + return ; + } + return ( + + ); +} diff --git a/apps/mobile/src/app/(app)/(tabs)/(3_profile)/profiles/[profileId]/skills.tsx b/apps/mobile/src/app/(app)/(tabs)/(3_profile)/profiles/[profileId]/skills.tsx new file mode 100644 index 00000000000..6f1add1b7fb --- /dev/null +++ b/apps/mobile/src/app/(app)/(tabs)/(3_profile)/profiles/[profileId]/skills.tsx @@ -0,0 +1,23 @@ +import { useLocalSearchParams } from 'expo-router'; + +import { InvalidRouteState } from '@/components/invalid-route-state'; +import { ProfileSkillsScreen } from '@/components/profiles/profile-skills-screen'; +import { getProfilesPath } from '@/lib/profile-agent-navigation'; +import { parseParam } from '@/lib/route-params'; + +export default function ProfileSkillsRoute() { + const { profileId: rawProfileId, organizationId: rawOrganizationId } = useLocalSearchParams<{ + profileId: string; + organizationId?: string; + }>(); + const profileId = parseParam(rawProfileId); + if (!profileId) { + return ; + } + return ( + + ); +} diff --git a/apps/mobile/src/app/(app)/(tabs)/(3_profile)/profiles/[profileId]/slash-commands.tsx b/apps/mobile/src/app/(app)/(tabs)/(3_profile)/profiles/[profileId]/slash-commands.tsx new file mode 100644 index 00000000000..b22b81a0dd0 --- /dev/null +++ b/apps/mobile/src/app/(app)/(tabs)/(3_profile)/profiles/[profileId]/slash-commands.tsx @@ -0,0 +1,23 @@ +import { useLocalSearchParams } from 'expo-router'; + +import { InvalidRouteState } from '@/components/invalid-route-state'; +import { ProfileKiloCommandsScreen } from '@/components/profiles/profile-kilo-commands-screen'; +import { getProfilesPath } from '@/lib/profile-agent-navigation'; +import { parseParam } from '@/lib/route-params'; + +export default function ProfileSlashCommandsRoute() { + const { profileId: rawProfileId, organizationId: rawOrganizationId } = useLocalSearchParams<{ + profileId: string; + organizationId?: string; + }>(); + const profileId = parseParam(rawProfileId); + if (!profileId) { + return ; + } + return ( + + ); +} diff --git a/apps/mobile/src/app/(app)/(tabs)/(3_profile)/profiles/[profileId]/variables.tsx b/apps/mobile/src/app/(app)/(tabs)/(3_profile)/profiles/[profileId]/variables.tsx new file mode 100644 index 00000000000..8d08674f5a2 --- /dev/null +++ b/apps/mobile/src/app/(app)/(tabs)/(3_profile)/profiles/[profileId]/variables.tsx @@ -0,0 +1,23 @@ +import { useLocalSearchParams } from 'expo-router'; + +import { InvalidRouteState } from '@/components/invalid-route-state'; +import { ProfileVariablesScreen } from '@/components/profiles/profile-variables-screen'; +import { getProfilesPath } from '@/lib/profile-agent-navigation'; +import { parseParam } from '@/lib/route-params'; + +export default function ProfileVariablesRoute() { + const { profileId: rawProfileId, organizationId: rawOrganizationId } = useLocalSearchParams<{ + profileId: string; + organizationId?: string; + }>(); + const profileId = parseParam(rawProfileId); + if (!profileId) { + return ; + } + return ( + + ); +} diff --git a/apps/mobile/src/app/(app)/(tabs)/(3_profile)/profiles/index.tsx b/apps/mobile/src/app/(app)/(tabs)/(3_profile)/profiles/index.tsx new file mode 100644 index 00000000000..20a552bf95d --- /dev/null +++ b/apps/mobile/src/app/(app)/(tabs)/(3_profile)/profiles/index.tsx @@ -0,0 +1,5 @@ +import { ProfilesListScreen } from '@/components/profiles/profiles-list-screen'; + +export default function ProfilesListRoute() { + return ; +} diff --git a/apps/mobile/src/app/(app)/(tabs)/(3_profile)/profiles/new.tsx b/apps/mobile/src/app/(app)/(tabs)/(3_profile)/profiles/new.tsx new file mode 100644 index 00000000000..07b13b7df4f --- /dev/null +++ b/apps/mobile/src/app/(app)/(tabs)/(3_profile)/profiles/new.tsx @@ -0,0 +1,5 @@ +import { NewProfileScreen } from '@/components/profiles/new-profile-screen'; + +export default function NewProfileRoute() { + return ; +} diff --git a/apps/mobile/src/app/(app)/(tabs)/(3_profile)/profiles/repo-bindings.tsx b/apps/mobile/src/app/(app)/(tabs)/(3_profile)/profiles/repo-bindings.tsx new file mode 100644 index 00000000000..73e5a4b66b2 --- /dev/null +++ b/apps/mobile/src/app/(app)/(tabs)/(3_profile)/profiles/repo-bindings.tsx @@ -0,0 +1,11 @@ +import { useLocalSearchParams } from 'expo-router'; + +import { RepoBindingsScreen } from '@/components/profiles/repo-bindings-screen'; +import { parseParam } from '@/lib/route-params'; + +export default function RepoBindingsRoute() { + const { organizationId: rawOrganizationId } = useLocalSearchParams<{ + organizationId?: string; + }>(); + return ; +} diff --git a/apps/mobile/src/app/(app)/_layout.tsx b/apps/mobile/src/app/(app)/_layout.tsx index b5c397935fd..d0c907a5a25 100644 --- a/apps/mobile/src/app/(app)/_layout.tsx +++ b/apps/mobile/src/app/(app)/_layout.tsx @@ -159,6 +159,7 @@ export default function AppLayout() { /> + diff --git a/apps/mobile/src/app/(app)/agent-chat/profile-picker.tsx b/apps/mobile/src/app/(app)/agent-chat/profile-picker.tsx new file mode 100644 index 00000000000..78090080b69 --- /dev/null +++ b/apps/mobile/src/app/(app)/agent-chat/profile-picker.tsx @@ -0,0 +1,81 @@ +import { type Href, useRouter } from 'expo-router'; +import { useState } from 'react'; +import { useTranslation } from 'react-i18next'; + +import { ProfilePickerSheet } from '@/components/agents/profile-picker-sheet'; +import { resolveSessionProfilePicker } from '@/components/agents/session-profile-picker-model'; +import { useEffectiveAgentProfile } from '@/components/agents/use-effective-agent-profile'; +import { PickerSheet } from '@/components/picker-sheet'; +import { profilePickerSlot, UNFENCED_ROUTE_KEY, useRouteRegistry } from '@/lib/route-registry'; + +const PROFILES_HREF = '/(app)/(tabs)/(3_profile)/profiles' as Href; + +/** + * The new-session profile picker, presented as the standard native formSheet. + * The bridge carries the current pick and the context; the route re-queries the + * same profiles scope so an open sheet is fresh and a failure has a Retry. + */ +export default function ProfilePickerScreen() { + const router = useRouter(); + const { t } = useTranslation(); + useRouteRegistry(UNFENCED_ROUTE_KEY); + // Lazy init reads the slot synchronously on first render — no effect, no + // "Options expired" flash before a later effect populates state. + const [bridge] = useState(() => profilePickerSlot.get(UNFENCED_ROUTE_KEY)); + + const { allProfiles, effectiveDefaultId, isLoading, isError, refetch } = useEffectiveAgentProfile( + bridge?.organizationId, + bridge?.selectedOverrideProfileId ?? null + ); + + if (!bridge) { + return ( + { + router.back(); + }} + scrollable={false} + expired + /> + ); + } + + const picker = resolveSessionProfilePicker({ + profiles: allProfiles, + repoBindingProfileId: bridge.repoBindingProfileId, + effectiveDefaultProfileId: effectiveDefaultId, + selectedOverrideProfileId: bridge.selectedOverrideProfileId, + }); + + function close() { + profilePickerSlot.clear(UNFENCED_ROUTE_KEY); + router.back(); + } + + return ( + 0} + selectedOverrideProfileId={bridge.selectedOverrideProfileId} + // A default exists, so clearing the override is not "no profile": the + // server resolves that default on top of any repository binding. + defaultProfileApplies={effectiveDefaultId !== null} + isLoading={isLoading} + isError={isError} + needsAttention={picker.overrideNeedsAttention} + onSelect={id => { + bridge.onSelect(id); + close(); + }} + onManageProfiles={() => { + close(); + router.push(PROFILES_HREF); + }} + onRetry={() => { + void refetch(); + }} + onClose={close} + /> + ); +} diff --git a/apps/mobile/src/components/agents/active-profile-indicator-model.test.ts b/apps/mobile/src/components/agents/active-profile-indicator-model.test.ts new file mode 100644 index 00000000000..0124effafb6 --- /dev/null +++ b/apps/mobile/src/components/agents/active-profile-indicator-model.test.ts @@ -0,0 +1,159 @@ +import { describe, expect, it } from 'vitest'; + +import { + buildActiveProfileIndicatorState, + ACTIVE_PROFILE_INDICATOR_KEYS as K, +} from './active-profile-indicator-model'; + +const noManual = { hasManualEnvVars: false, hasManualSetupCommands: false }; + +describe('buildActiveProfileIndicatorState', () => { + it('shows a single selected profile as Profile active', () => { + const state = buildActiveProfileIndicatorState({ + selectedProfileName: 'Backend', + repoBoundProfileName: null, + hasSelectedProfileId: true, + ...noManual, + }); + + expect(state).toEqual({ + kind: 'profile-active', + labelKey: K.profileActive, + layers: [{ labelKey: K.selectedProfile, detail: 'Backend' }], + needsAttention: false, + }); + }); + + it('shows a repo profile and a selected profile as Profiles active', () => { + const state = buildActiveProfileIndicatorState({ + selectedProfileName: 'Backend', + repoBoundProfileName: 'Repo profile', + hasSelectedProfileId: true, + ...noManual, + }); + + expect(state?.kind).toBe('profiles-active'); + expect(state?.labelKey).toBe(K.profilesActive); + expect(state?.layers.map(layer => layer.labelKey)).toEqual([K.repoProfile, K.selectedProfile]); + expect(state?.needsAttention).toBe(false); + }); + + it('shows manual config on top of a profile as Custom config active', () => { + const state = buildActiveProfileIndicatorState({ + selectedProfileName: 'Backend', + repoBoundProfileName: null, + hasSelectedProfileId: true, + hasManualEnvVars: true, + hasManualSetupCommands: true, + }); + + expect(state?.kind).toBe('custom-config-active'); + expect(state?.labelKey).toBe(K.customConfigActive); + expect(state?.layers).toEqual([ + { labelKey: K.selectedProfile, detail: 'Backend' }, + { labelKey: K.manualOverrides, detailKeys: [K.manualEnvVars, K.manualSetupCommands] }, + ]); + }); + + it('shows custom config with only manual overrides, no profile', () => { + const state = buildActiveProfileIndicatorState({ + selectedProfileName: null, + repoBoundProfileName: null, + hasSelectedProfileId: false, + hasManualEnvVars: true, + hasManualSetupCommands: false, + }); + + expect(state?.kind).toBe('custom-config-active'); + expect(state?.labelKey).toBe(K.customConfigActive); + expect(state?.layers).toEqual([{ labelKey: K.manualOverrides, detailKeys: [K.manualEnvVars] }]); + }); + + it('needs attention when the selected id resolves to no profile', () => { + const state = buildActiveProfileIndicatorState({ + selectedProfileName: null, + repoBoundProfileName: null, + hasSelectedProfileId: true, + ...noManual, + }); + + expect(state?.kind).toBe('config-needs-attention'); + expect(state?.labelKey).toBe(K.configNeedsAttention); + expect(state?.needsAttention).toBe(true); + expect(state?.layers).toEqual([ + { labelKey: K.profileSelection, detailKeys: [K.openSettingsToReview] }, + ]); + }); + + it('does not flag attention while the profiles query is still loading', () => { + const state = buildActiveProfileIndicatorState({ + selectedProfileName: null, + repoBoundProfileName: null, + hasSelectedProfileId: true, + ...noManual, + isProfilesLoading: true, + }); + + expect(state).toBeNull(); + }); + + it('needs attention on a profile error only when a profile layer is known', () => { + const withLayer = buildActiveProfileIndicatorState({ + selectedProfileName: 'Backend', + repoBoundProfileName: null, + hasSelectedProfileId: true, + ...noManual, + hasProfileError: true, + }); + expect(withLayer?.needsAttention).toBe(true); + expect(withLayer?.labelKey).toBe(K.configNeedsAttention); + expect(withLayer?.layers).toEqual([{ labelKey: K.selectedProfile, detail: 'Backend' }]); + + const withoutLayer = buildActiveProfileIndicatorState({ + selectedProfileName: null, + repoBoundProfileName: null, + hasSelectedProfileId: false, + ...noManual, + hasProfileError: true, + }); + expect(withoutLayer).toBeNull(); + }); + + it('needs attention on a repo-binding error that leaves no repo profile', () => { + const state = buildActiveProfileIndicatorState({ + selectedProfileName: null, + repoBoundProfileName: null, + hasSelectedProfileId: false, + ...noManual, + hasRepoBindingError: true, + }); + + expect(state?.needsAttention).toBe(true); + expect(state?.layers).toEqual([ + { labelKey: K.repoProfile, detailKeys: [K.openSettingsToReview] }, + ]); + }); + + it('renders nothing when there is no profile and no manual config', () => { + expect( + buildActiveProfileIndicatorState({ + selectedProfileName: null, + repoBoundProfileName: null, + hasSelectedProfileId: false, + ...noManual, + }) + ).toBeNull(); + }); + + it('renders nothing while profiles load and only a profile layer would show', () => { + expect( + buildActiveProfileIndicatorState({ + selectedProfileName: null, + repoBoundProfileName: null, + hasSelectedProfileId: false, + ...noManual, + isProfilesLoading: true, + }) + ).toBeNull(); + }); +}); diff --git a/apps/mobile/src/components/agents/active-profile-indicator-model.ts b/apps/mobile/src/components/agents/active-profile-indicator-model.ts new file mode 100644 index 00000000000..89a58e20cb7 --- /dev/null +++ b/apps/mobile/src/components/agents/active-profile-indicator-model.ts @@ -0,0 +1,133 @@ +/** + * Pure state for the active-profile indicator. Ports the web + * `buildProfileConfigIndicatorState` (`apps/web/src/components/cloud-agent/ + * ProfileConfigIndicator.tsx`) to the mobile copy keys, so the two surfaces + * agree on when a profile is active, when manual config is layered on top, and + * when the configuration needs attention. + */ + +export const ACTIVE_PROFILE_INDICATOR_KEYS = { + profileActive: 'agentChat.newSession.profileActive', + profilesActive: 'agentChat.newSession.profilesActive', + customConfigActive: 'agentChat.newSession.customConfigActive', + configNeedsAttention: 'agentChat.newSession.configNeedsAttention', + repoProfile: 'agentChat.newSession.repoProfile', + selectedProfile: 'agentChat.newSession.selectedProfile', + manualOverrides: 'agentChat.newSession.manualOverrides', + manualEnvVars: 'profiles.variablesTitle', + manualSetupCommands: 'profiles.commandsTitle', + openSettingsToReview: 'agentChat.newSession.openSettingsToReview', + profileSelection: 'agentChat.newSession.profileSelection', +} as const; + +type ActiveProfileIndicatorKind = + | 'profile-active' + | 'profiles-active' + | 'custom-config-active' + | 'config-needs-attention'; + +export type ActiveProfileIndicatorLayer = { + /** i18n key for the layer's label. */ + labelKey: string; + /** A raw detail value (a profile name), when the layer has one. */ + detail?: string; + /** i18n keys whose translated labels form the detail (manual overrides). */ + detailKeys?: string[]; +}; + +export type ActiveProfileIndicatorState = { + kind: ActiveProfileIndicatorKind; + labelKey: string; + layers: ActiveProfileIndicatorLayer[]; + needsAttention: boolean; +}; + +export type BuildActiveProfileIndicatorStateInput = { + selectedProfileName?: string | null; + repoBoundProfileName?: string | null; + hasManualEnvVars: boolean; + hasManualSetupCommands: boolean; + hasSelectedProfileId: boolean; + isProfilesLoading?: boolean; + hasProfileError?: boolean; + hasRepoBindingError?: boolean; +}; + +const K = ACTIVE_PROFILE_INDICATOR_KEYS; + +export function buildActiveProfileIndicatorState({ + selectedProfileName, + repoBoundProfileName, + hasManualEnvVars, + hasManualSetupCommands, + hasSelectedProfileId, + isProfilesLoading = false, + hasProfileError = false, + hasRepoBindingError = false, +}: BuildActiveProfileIndicatorStateInput): ActiveProfileIndicatorState | null { + const hasManualConfig = hasManualEnvVars || hasManualSetupCommands; + const layers: ActiveProfileIndicatorLayer[] = []; + + if (repoBoundProfileName) { + layers.push({ labelKey: K.repoProfile, detail: repoBoundProfileName }); + } + if (selectedProfileName) { + layers.push({ labelKey: K.selectedProfile, detail: selectedProfileName }); + } + if (hasManualConfig) { + const detailKeys: string[] = []; + if (hasManualEnvVars) { + detailKeys.push(K.manualEnvVars); + } + if (hasManualSetupCommands) { + detailKeys.push(K.manualSetupCommands); + } + layers.push({ labelKey: K.manualOverrides, detailKeys }); + } + + const selectedProfileNeedsAttention = + hasSelectedProfileId && !selectedProfileName && !isProfilesLoading; + const profileNames = new Set(); + if (repoBoundProfileName) { + profileNames.add(repoBoundProfileName); + } + if (selectedProfileName) { + profileNames.add(selectedProfileName); + } + const profileLayerCount = profileNames.size; + const hasKnownProfileOrSelection = profileLayerCount > 0 || hasSelectedProfileId; + const profileErrorNeedsAttention = hasProfileError && hasKnownProfileOrSelection; + const repoBindingNeedsAttention = hasRepoBindingError && !repoBoundProfileName; + const fallbackAttentionLayer: ActiveProfileIndicatorLayer = repoBindingNeedsAttention + ? { labelKey: K.repoProfile, detailKeys: [K.openSettingsToReview] } + : { labelKey: K.profileSelection, detailKeys: [K.openSettingsToReview] }; + + if (profileErrorNeedsAttention || repoBindingNeedsAttention || selectedProfileNeedsAttention) { + return { + kind: 'config-needs-attention', + labelKey: K.configNeedsAttention, + layers: layers.length > 0 ? layers : [fallbackAttentionLayer], + needsAttention: true, + }; + } + + if (layers.length === 0) { + return null; + } + if (isProfilesLoading && !hasManualConfig) { + return null; + } + + if (profileLayerCount > 1) { + return { kind: 'profiles-active', labelKey: K.profilesActive, layers, needsAttention: false }; + } + if (profileLayerCount === 1 && !hasManualConfig) { + return { kind: 'profile-active', labelKey: K.profileActive, layers, needsAttention: false }; + } + return { + kind: 'custom-config-active', + labelKey: K.customConfigActive, + layers, + needsAttention: false, + }; +} diff --git a/apps/mobile/src/components/agents/active-profile-indicator.mounted.test.tsx b/apps/mobile/src/components/agents/active-profile-indicator.mounted.test.tsx new file mode 100644 index 00000000000..b98596457d9 --- /dev/null +++ b/apps/mobile/src/components/agents/active-profile-indicator.mounted.test.tsx @@ -0,0 +1,107 @@ +import { createElement } from 'react'; +import { act, TestRenderer } from '@/test/renderer'; +import { beforeEach, describe, expect, it, vi } from 'vitest'; + +import '@/i18n'; +import { buildActiveProfileIndicatorState } from './active-profile-indicator-model'; +import { ActiveProfileIndicator } from './active-profile-indicator'; + +vi.mock('react-native', () => ({ + Pressable: 'Pressable', + View: 'View', +})); +vi.mock('@/components/ui/icons', () => ({ + SlidersHorizontal: 'SlidersHorizontal', + Check: 'Check', + Settings2: 'Settings2', +})); +vi.mock('@/components/ui/text', async () => { + const React = await import('react'); + return { Text: 'Text', TextClassContext: React.createContext(undefined) }; +}); +vi.mock('@/lib/hooks/use-theme-colors', () => ({ + useThemeColors: () => ({ warn: '#956011', mutedForeground: '#6f6a61', foreground: '#14130f' }), +})); + +function mount( + state: Parameters[0]['state'], + onPress = vi.fn<() => void>() +) { + const ref: { current: TestRenderer.ReactTestRenderer | null } = { current: null }; + act(() => { + ref.current = TestRenderer.create(createElement(ActiveProfileIndicator, { state, onPress })); + }); + const renderer = ref.current; + if (renderer === null) { + throw new Error('the indicator did not render'); + } + return { renderer, onPress }; +} + +function pill(renderer: TestRenderer.ReactTestRenderer) { + return renderer.root.findByType('Pressable' as never); +} + +beforeEach(() => { + (globalThis as { IS_REACT_ACT_ENVIRONMENT?: boolean }).IS_REACT_ACT_ENVIRONMENT = true; +}); + +describe('ActiveProfileIndicator', () => { + it('renders nothing when there is no state', () => { + const { renderer } = mount(null); + expect(renderer.toJSON()).toBeNull(); + }); + + it('renders the label and names every layer in the accessibility label', () => { + const state = buildActiveProfileIndicatorState({ + selectedProfileName: 'Backend', + repoBoundProfileName: 'Repo profile', + hasSelectedProfileId: true, + hasManualEnvVars: false, + hasManualSetupCommands: false, + }); + const { renderer } = mount(state); + + const node = pill(renderer); + expect(node.props.accessibilityRole).toBe('button'); + const label = node.props.accessibilityLabel as string; + expect(label).toContain('Profiles active'); + expect(label).toContain('Repo profile: Repo profile'); + expect(label).toContain('Selected profile: Backend'); + expect(label).toContain('Open Settings to review'); + expect(renderer.root.findByType('SlidersHorizontal' as never)).toBeTruthy(); + }); + + it('renders the amber attention treatment for the needs-attention state', () => { + const state = buildActiveProfileIndicatorState({ + selectedProfileName: null, + repoBoundProfileName: null, + hasSelectedProfileId: true, + hasManualEnvVars: false, + hasManualSetupCommands: false, + }); + const { renderer } = mount(state); + + const node = pill(renderer); + expect(node.props.accessibilityLabel).toContain('Config needs attention'); + expect(node.props.className).toContain('border-warn-tile-border'); + expect(node.props.className).toContain('bg-warn-tile-bg'); + }); + + it('calls onPress when tapped', () => { + const { renderer, onPress } = mount( + buildActiveProfileIndicatorState({ + selectedProfileName: 'Backend', + repoBoundProfileName: null, + hasSelectedProfileId: true, + hasManualEnvVars: false, + hasManualSetupCommands: false, + }) + ); + + act(() => { + (pill(renderer).props.onPress as () => void)(); + }); + expect(onPress).toHaveBeenCalledTimes(1); + }); +}); diff --git a/apps/mobile/src/components/agents/active-profile-indicator.tsx b/apps/mobile/src/components/agents/active-profile-indicator.tsx new file mode 100644 index 00000000000..334ab21e27b --- /dev/null +++ b/apps/mobile/src/components/agents/active-profile-indicator.tsx @@ -0,0 +1,77 @@ +import { Pressable } from 'react-native'; +import { useTranslation } from 'react-i18next'; + +import { + type ActiveProfileIndicatorLayer, + type ActiveProfileIndicatorState, +} from '@/components/agents/active-profile-indicator-model'; +import { SlidersHorizontal } from '@/components/ui/icons'; +import { Text } from '@/components/ui/text'; +import { useThemeColors } from '@/lib/hooks/use-theme-colors'; +import { cn } from '@/lib/utils'; + +type ActiveProfileIndicatorProps = { + state: ActiveProfileIndicatorState | null; + /** Opens the profile picker (new session) or the profile editor (session). */ + onPress: () => void; + className?: string; +}; + +/** + * The active-profile chip: a translated label plus its layers in the + * accessibility label. Renders nothing when no profile or manual config + * applies, so the caller never reserves space for an absent state. + */ +export function ActiveProfileIndicator({ + state, + onPress, + className, +}: Readonly) { + const { t } = useTranslation(); + const colors = useThemeColors(); + + if (!state) { + return null; + } + + const layerText = (layer: ActiveProfileIndicatorLayer): string => { + const label = t(layer.labelKey); + const detail = layer.detail ?? layer.detailKeys?.map(key => t(key)).join(', '); + return detail ? `${label}: ${detail}` : label; + }; + const accessibleLabel = [ + t(state.labelKey), + ...state.layers.map(layerText), + t('agentChat.newSession.openSettingsToReview'), + ].join('. '); + + return ( + + + + {t(state.labelKey)} + + + ); +} diff --git a/apps/mobile/src/components/agents/advanced-config-editors.tsx b/apps/mobile/src/components/agents/advanced-config-editors.tsx new file mode 100644 index 00000000000..726e3067c54 --- /dev/null +++ b/apps/mobile/src/components/agents/advanced-config-editors.tsx @@ -0,0 +1,226 @@ +import { useState } from 'react'; +import { useTranslation } from 'react-i18next'; +import { Pressable, View } from 'react-native'; + +import { + addCommand, + commandRowA11yLabel, + MAX_SETUP_COMMAND_LENGTH, + MAX_SETUP_COMMANDS, + moveCommand, + removeCommand, + replaceCommand, +} from '@/components/profiles/profile-commands-model'; +import { + applyVariableEdit, + type VariableEdit, + variableRows, +} from '@/components/profiles/profile-variables-model'; +import { VariableEditForm, VariableRowView } from '@/components/profiles/profile-variables-rows'; +import { Button } from '@/components/ui/button'; +import { FormField } from '@/components/ui/form-field'; +import { ChevronDown, ChevronUp, Plus, Trash2 } from '@/components/ui/icons'; +import { Text } from '@/components/ui/text'; +import { MAX_SESSION_ENV_VAR_VALUE_LENGTH } from '@/lib/agent-profile-forms'; +import { useThemeColors } from '@/lib/hooks/use-theme-colors'; +import { cn } from '@/lib/utils'; + +type ManualEnvVarsEditorProps = Readonly<{ + vars: readonly VariableEdit[]; + disabled?: boolean; + onChange: (next: VariableEdit[]) => void; +}>; + +/** + * The manual environment-variables editor: plain key/value rows, added and + * edited inline. No JSON blob and no drag — a row is tapped to edit, a secret + * can be revealed, and delete removes it from the draft. + */ +export function ManualEnvVarsEditor({ + vars, + disabled = false, + onChange, +}: Readonly) { + const { t } = useTranslation(); + const colors = useThemeColors(); + const [editingKey, setEditingKey] = useState(null); + const [revealed, setRevealed] = useState>({}); + const rows = variableRows(vars); + + const saveEdit = async (edit: VariableEdit): Promise => { + onChange(applyVariableEdit(vars, edit)); + setEditingKey(null); + await Promise.resolve(); + return true; + }; + + return ( + + + {t('profiles.variablesTitle')} + + {rows.map(row => + row.key === editingKey ? ( + variable.key)} + maxValueLength={MAX_SESSION_ENV_VAR_VALUE_LENGTH} + isSaving={false} + onCancel={() => { + setEditingKey(null); + }} + onSave={saveEdit} + /> + ) : ( + { + setRevealed(current => ({ ...current, [row.key]: current[row.key] !== true })); + }} + onEdit={() => { + setEditingKey(row.key); + }} + onDelete={() => { + onChange(vars.filter(variable => variable.key !== row.key)); + }} + /> + ) + )} + {editingKey === '' ? ( + variable.key)} + maxValueLength={MAX_SESSION_ENV_VAR_VALUE_LENGTH} + isSaving={false} + onCancel={() => { + setEditingKey(null); + }} + onSave={saveEdit} + /> + ) : ( + + )} + + ); +} + +type ManualSetupCommandsEditorProps = Readonly<{ + commands: readonly string[]; + disabled?: boolean; + onChange: (next: string[]) => void; +}>; + +/** + * The manual setup-commands editor: one plain row per command, reorderable + * with up/down buttons (never drag) and removable. The list order is the order + * the commands run in. + */ +export function ManualSetupCommandsEditor({ + commands, + disabled = false, + onChange, +}: Readonly) { + const { t } = useTranslation(); + const colors = useThemeColors(); + // Bumped on every reorder so the uncontrolled fields remount seeded in the + // new order (their text belongs to the position, not the input instance). + const [generation, setGeneration] = useState(0); + + const controlClass = (controlDisabled: boolean) => + cn('h-11 w-11 items-center justify-center active:opacity-70', controlDisabled && 'opacity-40'); + + return ( + + + {t('profiles.commandsTitle')} + + {commands.map((command, index) => { + const isFirst = index === 0; + const isLast = index === commands.length - 1; + return ( + + { + onChange(replaceCommand(commands, index, value)); + }} + /> + + { + onChange(moveCommand(commands, index, -1)); + setGeneration(current => current + 1); + }} + > + + + { + onChange(moveCommand(commands, index, 1)); + setGeneration(current => current + 1); + }} + > + + + { + onChange(removeCommand(commands, index)); + setGeneration(current => current + 1); + }} + > + + + + + ); + })} + + + ); +} diff --git a/apps/mobile/src/components/agents/advanced-config-panel.mounted.test.tsx b/apps/mobile/src/components/agents/advanced-config-panel.mounted.test.tsx new file mode 100644 index 00000000000..a6da96c0cc5 --- /dev/null +++ b/apps/mobile/src/components/agents/advanced-config-panel.mounted.test.tsx @@ -0,0 +1,474 @@ +/* eslint-disable max-lines -- one mounted case per panel state: the controlled selector, both manual editors, the save flow, and the retryable failure */ +import { createElement, type ReactNode, useState } from 'react'; +import { act, TestRenderer } from '@/test/renderer'; +import { beforeEach, describe, expect, it, vi } from 'vitest'; + +import '@/i18n'; +import { AdvancedConfigPanel } from './advanced-config-panel'; +import { type ProfileSelectorProfile } from './profile-selector-model'; +import { MAX_SETUP_COMMANDS } from '@/components/profiles/profile-commands-model'; +import { type VariableEdit } from '@/components/profiles/profile-variables-model'; + +const push = vi.fn<(href: string) => void>(); +vi.mock('expo-router', () => ({ useRouter: () => ({ push }) })); +const toast = vi.hoisted(() => ({ success: vi.fn(), error: vi.fn() })); +vi.mock('sonner-native', () => ({ toast })); + +const listMock = vi.hoisted(() => ({ + useAgentProfileList: vi.fn(), + useAgentProfileMutations: vi.fn(), +})); +vi.mock('@/lib/hooks/use-agent-profiles', () => listMock); + +vi.mock('react-native', () => ({ + Pressable: 'Pressable', + ScrollView: 'ScrollView', + Switch: 'Switch', + View: 'View', +})); +vi.mock('react-native-safe-area-context', () => ({ + useSafeAreaInsets: () => ({ top: 0, bottom: 0, left: 0, right: 0 }), +})); +vi.mock('@/components/agents/session-page-sheet', () => ({ + SessionPageSheet: (props: { visible?: boolean; children?: ReactNode }) => + props.visible === false ? null : createElement('SessionPageSheet', null, props.children), +})); +vi.mock('@/components/sheet-header', () => ({ + SheetHeader: (props: Record) => createElement('SheetHeader', props), +})); +vi.mock('@/components/ui/button', () => ({ + Button: (props: Record & { children?: ReactNode }) => + createElement('Button', props, props.children), +})); +vi.mock('@/components/ui/form-field', () => ({ + FormField: (props: Record) => createElement('FormField', props), +})); +vi.mock('@/components/ui/skeleton', () => ({ Skeleton: 'Skeleton' })); +vi.mock('@/components/ui/text', async () => { + const React = await import('react'); + return { Text: 'Text', TextClassContext: React.createContext(undefined) }; +}); +vi.mock('@/components/ui/icons', () => ({ + AlertCircle: 'AlertCircle', + Building2: 'Building2', + ChevronDown: 'ChevronDown', + ChevronUp: 'ChevronUp', + GitBranch: 'GitBranch', + Lock: 'Lock', + Plus: 'Plus', + Settings: 'Settings', + Settings2: 'Settings2', + Star: 'Star', + Trash2: 'Trash2', + User: 'User', +})); +vi.mock('@/lib/hooks/use-theme-colors', () => ({ + useThemeColors: () => ({ + foreground: '#000', + mutedForeground: '#666', + destructive: '#f00', + primary: '#0a84ff', + }), +})); + +const BACKEND: ProfileSelectorProfile = { + id: 'backend', + name: 'Backend', + varCount: 3, + commandCount: 1, + isDefault: false, + ownerType: 'user', +}; + +type Mutations = { + create: { mutateAsync: ReturnType }; + setVar: { mutateAsync: ReturnType }; + setCommands: { mutateAsync: ReturnType }; + setAsDefault: { mutateAsync: ReturnType }; +}; + +function mutations(): Mutations { + return { + create: { mutateAsync: vi.fn().mockResolvedValue({ id: 'new-1' }) }, + setVar: { mutateAsync: vi.fn().mockResolvedValue({}) }, + setCommands: { mutateAsync: vi.fn().mockResolvedValue({}) }, + setAsDefault: { mutateAsync: vi.fn().mockResolvedValue({}) }, + }; +} + +type ListState = { + orgProfiles: ProfileSelectorProfile[]; + personalProfiles: ProfileSelectorProfile[]; + effectiveDefaultId: string | null; + isLoading: boolean; + isError: boolean; + isRefetching: boolean; + refetch: ReturnType; +}; + +function listState(overrides: Partial = {}): ListState { + return { + orgProfiles: [], + personalProfiles: [BACKEND], + effectiveDefaultId: null, + isLoading: false, + isError: false, + isRefetching: false, + refetch: vi.fn(), + ...overrides, + }; +} + +/** + * The panel's profile pick is controlled by the session, so the harness mirrors + * the real parent: it owns `selectedProfileId` and reports each pick both to + * the spy and to its own state, the way the new-session body does. + */ +function ControlledPanel({ + organizationId, + initialSelectedProfileId, + onSelectProfile, +}: { + organizationId?: string; + initialSelectedProfileId?: string | null; + onSelectProfile?: (id: string | null) => void; +}) { + const [selectedProfileId, setSelectedProfileId] = useState( + initialSelectedProfileId ?? null + ); + // The panel's manual env vars/commands are the session's state now, so the + // harness owns them the way the new-session body does. + const [manualVars, setManualVars] = useState([]); + const [manualCommands, setManualCommands] = useState([]); + return createElement(AdvancedConfigPanel, { + organizationId, + selectedProfileId, + onSelectProfile: id => { + onSelectProfile?.(id); + setSelectedProfileId(id); + }, + manualVars, + manualCommands, + onManualVarsChange: setManualVars, + onManualCommandsChange: setManualCommands, + }); +} + +function mount( + options: { + list?: Partial; + mocks?: Mutations; + organizationId?: string; + selectedProfileId?: string | null; + onSelectProfile?: (id: string | null) => void; + } = {} +) { + listMock.useAgentProfileList.mockReturnValue(listState(options.list)); + listMock.useAgentProfileMutations.mockReturnValue(options.mocks ?? mutations()); + const ref: { current: TestRenderer.ReactTestRenderer | null } = { current: null }; + act(() => { + ref.current = TestRenderer.create( + createElement(ControlledPanel, { + organizationId: options.organizationId, + initialSelectedProfileId: options.selectedProfileId, + onSelectProfile: options.onSelectProfile, + }) + ); + }); + const renderer = ref.current; + if (renderer === null) { + throw new Error('the panel did not render'); + } + return renderer; +} + +function press(node: TestRenderer.ReactTestInstance | undefined) { + act(() => { + (node?.props.onPress as (() => void) | undefined)?.(); + }); +} + +function byLabel(renderer: TestRenderer.ReactTestRenderer, label: string) { + return renderer.root + .findAll(node => node.props.accessibilityLabel === label) + .find(node => typeof node.props.onPress === 'function'); +} + +function radios(renderer: TestRenderer.ReactTestRenderer) { + return renderer.root.findAll(node => node.props.accessibilityRole === 'radio'); +} + +function radio(renderer: TestRenderer.ReactTestRenderer, label: string) { + return radios(renderer).find(node => node.props.accessibilityLabel === label); +} + +function containsText(node: TestRenderer.ReactTestInstance, text: string): boolean { + if (node.children.some(child => typeof child === 'string' && child === text)) { + return true; + } + return node.children.some(child => typeof child !== 'string' && containsText(child, text)); +} + +function buttonByText(renderer: TestRenderer.ReactTestRenderer, text: string) { + return renderer.root.findAllByType('Button' as never).find(node => containsText(node, text)); +} + +function texts(renderer: TestRenderer.ReactTestRenderer): string[] { + return renderer.root + .findAllByType('Text' as never) + .flatMap(node => node.children) + .filter((child): child is string => typeof child === 'string'); +} + +function field(renderer: TestRenderer.ReactTestRenderer, label: string) { + const node = renderer.root + .findAllByType('FormField' as never) + .find(instance => instance.props.label === label); + if (!node) { + throw new Error(`no FormField labelled ${label}`); + } + return node; +} + +function typeInto(renderer: TestRenderer.ReactTestRenderer, label: string, value: string) { + act(() => { + (field(renderer, label).props.onChangeText as (value: string) => void)(value); + }); +} + +/** First call position of a mock, for ordering assertions. */ +function callOrder(mock: ReturnType): number { + return mock.mock.invocationCallOrder[0] ?? 0; +} + +function addVariable(renderer: TestRenderer.ReactTestRenderer, key: string, value: string) { + press(buttonByText(renderer, 'Add variable')); + typeInto(renderer, 'Key', key); + typeInto(renderer, 'Value', value); + press(buttonByText(renderer, 'Save')); +} + +beforeEach(() => { + push.mockReset(); + toast.success.mockReset(); + toast.error.mockReset(); + (globalThis as { IS_REACT_ACT_ENVIRONMENT?: boolean }).IS_REACT_ACT_ENVIRONMENT = true; +}); + +describe('AdvancedConfigPanel', () => { + it('is collapsed by default and reserves only the disclosure row', () => { + const renderer = mount(); + + const disclosure = byLabel(renderer, 'Advanced Configuration'); + expect(disclosure?.props.accessibilityState).toMatchObject({ expanded: false }); + expect(byLabel(renderer, 'Pick a profile')).toBeUndefined(); + expect(texts(renderer)).not.toContain('Environment variables'); + expect(buttonByText(renderer, 'Save as Profile')).toBeUndefined(); + }); + + it('expands to the selector, both manual editors and the summary', () => { + const renderer = mount(); + press(byLabel(renderer, 'Advanced Configuration')); + + expect(byLabel(renderer, 'Pick a profile')).toBeDefined(); + expect(texts(renderer)).toContain('Environment variables'); + expect(texts(renderer)).toContain('Setup commands'); + // No manual config yet, so no Save as Profile. + expect(buttonByText(renderer, 'Save as Profile')).toBeUndefined(); + }); + + it('only offers Save as Profile for commands that will be persisted', () => { + const renderer = mount(); + press(byLabel(renderer, 'Advanced Configuration')); + press(buttonByText(renderer, 'Add command')); + expect(buttonByText(renderer, 'Save as Profile')).toBeUndefined(); + typeInto(renderer, 'Command', ' '); + expect(buttonByText(renderer, 'Save as Profile')).toBeUndefined(); + typeInto(renderer, 'Command', 'pnpm install'); + expect(buttonByText(renderer, 'Save as Profile')).toBeDefined(); + typeInto(renderer, 'Command', ''); + expect(buttonByText(renderer, 'Save as Profile')).toBeUndefined(); + }); + + it('offers Save as Profile once a manual variable exists', () => { + const renderer = mount(); + press(byLabel(renderer, 'Advanced Configuration')); + addVariable(renderer, 'API_KEY', 'abc'); + + expect(buttonByText(renderer, 'Save as Profile')).toBeDefined(); + expect(texts(renderer)).toContain('1 environment variables · 0 setup commands'); + }); + + it('caps manual setup commands at the server list and row bounds', () => { + const renderer = mount(); + press(byLabel(renderer, 'Advanced Configuration')); + + press(buttonByText(renderer, 'Add command')); + expect(field(renderer, 'Command').props.maxLength).toBe(500); + + for (let index = 1; index < MAX_SETUP_COMMANDS; index += 1) { + press(buttonByText(renderer, 'Add command')); + } + const commandFields = () => + renderer.root + .findAllByType('FormField' as never) + .filter(node => node.props.label === 'Command'); + expect(commandFields()).toHaveLength(MAX_SETUP_COMMANDS); + expect(buttonByText(renderer, 'Add command')?.props.disabled).toBe(true); + + // A 21st row is refused even if the disabled control is pressed. + press(buttonByText(renderer, 'Add command')); + expect(commandFields()).toHaveLength(MAX_SETUP_COMMANDS); + }); + + it('caps a manual env-var value at the 256-character session bound', () => { + const renderer = mount(); + press(byLabel(renderer, 'Advanced Configuration')); + + press(buttonByText(renderer, 'Add variable')); + // The prepare-session input caps every inline env-var value at 256 + // characters, so the field cannot hold a value Start would be refused for. + expect(field(renderer, 'Value').props.maxLength).toBe(256); + }); + + it('keeps No profile and manual config working with no profiles at all', () => { + const renderer = mount({ list: { personalProfiles: [] } }); + press(byLabel(renderer, 'Advanced Configuration')); + + expect(texts(renderer)).toContain('No profile'); + addVariable(renderer, 'API_KEY', 'abc'); + expect(buttonByText(renderer, 'Save as Profile')).toBeDefined(); + }); + + it('shows the retryable failure and refetches', () => { + const refetch = vi.fn(); + const renderer = mount({ list: { isError: true, refetch } }); + press(byLabel(renderer, 'Advanced Configuration')); + + expect(texts(renderer)).toContain('Failed to load profiles'); + press(byLabel(renderer, 'Retry')); + expect(refetch).toHaveBeenCalledTimes(1); + }); + + it('renders the session-owned selectedProfileId in the closed row', () => { + const renderer = mount({ selectedProfileId: 'backend' }); + press(byLabel(renderer, 'Advanced Configuration')); + + expect(texts(renderer)).toContain('Backend'); + expect(texts(renderer)).toContain('3 environment variables · 1 setup commands'); + }); + + it('names the effective default in the no-override row when one applies', () => { + const renderer = mount({ list: { personalProfiles: [{ ...BACKEND, isDefault: true }] } }); + press(byLabel(renderer, 'Advanced Configuration')); + + // Clearing the pick keeps the default, so neither the closed row nor the + // sheet's no-override row may claim there is no profile. + expect(texts(renderer)).toContain('Default profile'); + press(byLabel(renderer, 'Pick a profile')); + expect(radio(renderer, 'Default profile')?.props.accessibilityState).toMatchObject({ + checked: true, + }); + expect(radio(renderer, 'No profile')).toBeUndefined(); + }); + + it('reports a pick from the sheet through onSelectProfile and shows it', () => { + const onSelectProfile = vi.fn<(id: string | null) => void>(); + const renderer = mount({ onSelectProfile }); + press(byLabel(renderer, 'Advanced Configuration')); + press(byLabel(renderer, 'Pick a profile')); + + expect(radio(renderer, 'No profile')?.props.accessibilityState).toMatchObject({ + checked: true, + }); + press(radio(renderer, 'Backend')); + + expect(onSelectProfile).toHaveBeenCalledWith('backend'); + expect(texts(renderer)).toContain('Backend'); + expect(texts(renderer)).toContain('3 environment variables · 1 setup commands'); + }); + + it('reports No profile through onSelectProfile(null) and clears the row', () => { + const onSelectProfile = vi.fn<(id: string | null) => void>(); + const renderer = mount({ selectedProfileId: 'backend', onSelectProfile }); + press(byLabel(renderer, 'Advanced Configuration')); + press(byLabel(renderer, 'Pick a profile')); + + press(radio(renderer, 'No profile')); + + expect(onSelectProfile).toHaveBeenCalledWith(null); + expect(texts(renderer)).toContain('No profile'); + }); + + it('saves the manual config in the web order and shows the new profile', async () => { + const mocks = mutations(); + const renderer = mount({ mocks }); + press(byLabel(renderer, 'Advanced Configuration')); + addVariable(renderer, 'API_KEY', 'abc'); + // A setup command. + press(buttonByText(renderer, 'Add command')); + typeInto(renderer, 'Command', 'pnpm install'); + press(buttonByText(renderer, 'Save as Profile')); + + typeInto(renderer, 'Profile name', 'My Setup'); + act(() => { + (renderer.root.findByType('Switch' as never).props.onValueChange as (value: boolean) => void)( + true + ); + }); + await act(async () => { + (renderer.root.findByType('SheetHeader' as never).props.onDone as () => void)(); + await Promise.resolve(); + }); + + expect(callOrder(mocks.create.mutateAsync)).toBeLessThan(callOrder(mocks.setVar.mutateAsync)); + expect(callOrder(mocks.setVar.mutateAsync)).toBeLessThan( + callOrder(mocks.setCommands.mutateAsync) + ); + expect(callOrder(mocks.setCommands.mutateAsync)).toBeLessThan( + callOrder(mocks.setAsDefault.mutateAsync) + ); + expect(mocks.create.mutateAsync).toHaveBeenCalledWith({ + name: 'My Setup', + description: undefined, + }); + expect(mocks.setVar.mutateAsync).toHaveBeenCalledWith({ + profileId: 'new-1', + key: 'API_KEY', + value: 'abc', + isSecret: false, + }); + expect(mocks.setCommands.mutateAsync).toHaveBeenCalledWith({ + profileId: 'new-1', + commands: ['pnpm install'], + }); + expect(mocks.setAsDefault.mutateAsync).toHaveBeenCalledWith({ profileId: 'new-1' }); + expect(toast.success).toHaveBeenCalledTimes(1); + // The saved profile appears in the selector immediately. + expect(texts(renderer)).toContain('My Setup'); + // The profile carries the draft now, and the server appends a profile's + // setup commands to the inline ones, so a kept draft would run them twice. + expect(texts(renderer)).not.toContain('API_KEY'); + expect(buttonByText(renderer, 'Save as Profile')).toBeUndefined(); + expect(texts(renderer)).toContain('1 environment variables · 1 setup commands'); + }); + + it('keeps the save sheet open and the typed name when the save fails', async () => { + const mocks = mutations(); + mocks.create.mutateAsync.mockRejectedValue(new Error('nope')); + + const renderer = mount({ mocks }); + press(byLabel(renderer, 'Advanced Configuration')); + addVariable(renderer, 'API_KEY', 'abc'); + press(buttonByText(renderer, 'Save as Profile')); + typeInto(renderer, 'Profile name', 'Broken'); + + await act(async () => { + (renderer.root.findByType('SheetHeader' as never).props.onDone as () => void)(); + await Promise.resolve(); + }); + + expect(field(renderer, 'Profile name')).toBeTruthy(); + expect(toast.success).not.toHaveBeenCalled(); + }); +}); diff --git a/apps/mobile/src/components/agents/advanced-config-panel.tsx b/apps/mobile/src/components/agents/advanced-config-panel.tsx new file mode 100644 index 00000000000..b922537e61c --- /dev/null +++ b/apps/mobile/src/components/agents/advanced-config-panel.tsx @@ -0,0 +1,313 @@ +import { useMemo, useState } from 'react'; +import { useTranslation } from 'react-i18next'; +import { Pressable, View } from 'react-native'; +import { type Href, useRouter } from 'expo-router'; +import { toast } from 'sonner-native'; + +import { + ManualEnvVarsEditor, + ManualSetupCommandsEditor, +} from '@/components/agents/advanced-config-editors'; +import { + buildProfileSelectorState, + type ProfileSelectorOwnerType, + type ProfileSelectorProfile, +} from '@/components/agents/profile-selector-model'; +import { ProfileSelectorRow } from '@/components/agents/profile-selector-row'; +import { + SaveProfileSheet, + type SaveProfileSubmission, +} from '@/components/agents/save-profile-sheet'; +import { type VariableEdit } from '@/components/profiles/profile-variables-model'; +import { Button } from '@/components/ui/button'; +import { ChevronDown, ChevronUp } from '@/components/ui/icons'; +import { Text } from '@/components/ui/text'; +import { useAgentProfileList, useAgentProfileMutations } from '@/lib/hooks/use-agent-profiles'; +import { useThemeColors } from '@/lib/hooks/use-theme-colors'; + +const PROFILES_HREF = '/(app)/(tabs)/(3_profile)/profiles' as Href; + +/** Structural view of a list row, so both the personal and combined shapes fit. */ +type ProfileListRow = Readonly<{ + id: string; + name: string; + varCount: number; + commandCount: number; + isDefault: boolean; + ownerType?: ProfileSelectorOwnerType; +}>; + +function toSelectorProfile( + profile: ProfileListRow, + fallbackOwnerType: ProfileSelectorOwnerType +): ProfileSelectorProfile { + return { + id: profile.id, + name: profile.name, + varCount: profile.varCount, + commandCount: profile.commandCount, + isDefault: profile.isDefault, + ownerType: profile.ownerType ?? fallbackOwnerType, + }; +} + +type AdvancedConfigPanelProps = Readonly<{ + /** The route's organization scope; `undefined` is a personal session. */ + organizationId?: string; + /** + * The profile the session holds as an override, or null when none is picked + * (the selector row then names the effective default, or `No profile` when + * the context has none). Owned by the session body so the Environment row + * and this selector are one control driving the submitted `profileId`. + * Required: a caller cannot render an inert selector. + */ + selectedProfileId: string | null; + /** Reports a pick, or clearing it, to the session that owns the override. */ + onSelectProfile: (id: string | null) => void; + /** + * The session's manual environment variables and setup commands, owned by the + * new-session route so the create can carry them. The panel edits them through + * the callbacks and keeps no draft of its own, so a value entered here is + * never dropped when the session starts without saving a profile first. + * Saving them as a profile clears both, because the profile now carries them. + */ + manualVars: readonly VariableEdit[]; + manualCommands: readonly string[]; + onManualVarsChange: (next: VariableEdit[]) => void; + onManualCommandsChange: (next: string[]) => void; + disabled?: boolean; + /** + * Opens the repo default-profile bindings. Omitted until that surface + * exists, so the selector shows no dead `Default profiles for repos...` + * entry. + */ + onRepoDefaults?: () => void; +}>; + +/** + * The Advanced Configuration panel: a collapsed disclosure that expands to the + * profile selector, the effective var/command summary, a manual environment + * variables editor, a manual setup commands editor, and `Save as Profile` when + * manual configuration exists. Mobile-first — plain tap rows and native + * sheets, no JSON blob and no drag anywhere. + */ +export function AdvancedConfigPanel({ + organizationId, + selectedProfileId, + onSelectProfile, + manualVars, + manualCommands, + onManualVarsChange, + onManualCommandsChange, + disabled = false, + onRepoDefaults, +}: Readonly) { + const { t } = useTranslation(); + const colors = useThemeColors(); + const router = useRouter(); + const list = useAgentProfileList(organizationId); + const { + create, + setVar, + setCommands: saveCommands, + setAsDefault, + } = useAgentProfileMutations(organizationId); + + const [isExpanded, setIsExpanded] = useState(false); + const [createdProfile, setCreatedProfile] = useState(null); + const [isSaveOpen, setIsSaveOpen] = useState(false); + + const orgProfiles = useMemo(() => { + const base = (list.orgProfiles as readonly ProfileListRow[]).map(profile => + toSelectorProfile(profile, 'organization') + ); + if ( + createdProfile?.ownerType === 'organization' && + !base.some(profile => profile.id === createdProfile.id) + ) { + return [...base, createdProfile]; + } + return base; + }, [list.orgProfiles, createdProfile]); + + const personalProfiles = useMemo(() => { + const base = (list.personalProfiles as readonly ProfileListRow[]).map(profile => + toSelectorProfile(profile, 'user') + ); + if ( + createdProfile?.ownerType === 'user' && + !base.some(profile => profile.id === createdProfile.id) + ) { + return [...base, createdProfile]; + } + return base; + }, [list.personalProfiles, createdProfile]); + + const selectorState = useMemo( + () => + buildProfileSelectorState({ + organizationId, + orgProfiles, + personalProfiles, + effectiveDefaultId: list.effectiveDefaultId, + selectedProfileId, + includeRepoDefaults: onRepoDefaults !== undefined, + }), + [ + organizationId, + orgProfiles, + personalProfiles, + list.effectiveDefaultId, + selectedProfileId, + onRepoDefaults, + ] + ); + + const selectedProfile = selectorState.selectedProfile; + const setupCommands = manualCommands.filter(command => command.trim().length > 0); + const hasManualConfig = manualVars.length > 0 || setupCommands.length > 0; + const effectiveVars = (selectedProfile?.varCount ?? 0) + manualVars.length; + const effectiveCommands = (selectedProfile?.commandCount ?? 0) + setupCommands.length; + + const handleSaveProfile = async (submission: SaveProfileSubmission): Promise => { + try { + // Web's order: create the profile, then its vars (in parallel), then the + // commands, then the default flag. + const { id: profileId } = await create.mutateAsync({ + name: submission.name, + description: submission.description === '' ? undefined : submission.description, + }); + await Promise.all( + manualVars.map(async variable => { + await setVar.mutateAsync({ + profileId, + key: variable.key, + value: variable.value, + isSecret: variable.isSecret, + }); + }) + ); + if (setupCommands.length > 0) { + await saveCommands.mutateAsync({ profileId, commands: setupCommands }); + } + if (submission.setAsDefault) { + await setAsDefault.mutateAsync({ profileId }); + } + // Show the new profile immediately, before the invalidated list refetch + // lands, so the selector never falls back to its no-override label. + setCreatedProfile({ + id: profileId, + name: submission.name, + varCount: manualVars.length, + commandCount: setupCommands.length, + isDefault: submission.setAsDefault, + ownerType: organizationId === undefined ? 'user' : 'organization', + }); + onSelectProfile(profileId); + // The saved profile now carries these values, so the draft must not stay + // behind: the profile's setup commands are appended to the inline ones, + // and leaving the draft populated would run every saved command twice. + onManualVarsChange([]); + onManualCommandsChange([]); + toast.success(t('agentChat.newSession.profileSaved', { name: submission.name })); + return true; + } catch (error) { + // The mutation hook already toasts a readable server message; only fill + // in the fallback when the server sent nothing. + if (!(error instanceof Error) || error.message.trim().length === 0) { + toast.error(t('profiles.saveFailed')); + } + return false; + } + }; + + return ( + + { + setIsExpanded(current => !current); + }} + disabled={disabled} + accessibilityRole="button" + accessibilityLabel={t('agentChat.newSession.advancedConfig')} + accessibilityState={{ expanded: isExpanded, disabled }} + > + + {t('agentChat.newSession.advancedConfig')} + + {isExpanded ? ( + + ) : ( + + )} + + + {isExpanded ? ( + + + {t('agentChat.newSession.advancedConfigDescription')} + + + { + void list.refetch(); + }} + onSelect={onSelectProfile} + onManageProfiles={() => { + router.push(PROFILES_HREF); + }} + onRepoDefaults={onRepoDefaults} + /> + + {effectiveVars > 0 || effectiveCommands > 0 ? ( + + {t('agentChat.newSession.profileSummary', { + vars: effectiveVars, + commands: effectiveCommands, + })} + + ) : null} + + + + + {hasManualConfig ? ( + + ) : null} + + ) : null} + + {isSaveOpen ? ( + { + setIsSaveOpen(false); + }} + onSave={handleSaveProfile} + /> + ) : null} + + ); +} diff --git a/apps/mobile/src/components/agents/mobile-session-manager.ts b/apps/mobile/src/components/agents/mobile-session-manager.ts index ad5639e3574..e6c9eb9caf1 100644 --- a/apps/mobile/src/components/agents/mobile-session-manager.ts +++ b/apps/mobile/src/components/agents/mobile-session-manager.ts @@ -465,6 +465,7 @@ export function createMobileAgentSessionManager({ cloudAgentSessionId, title: sessionResult.title, organizationId: sessionResult.organization_id, + profileId: sessionResult.profile_id, gitUrl: sessionResult.git_url, gitBranch: rs?.upstreamBranch ?? sessionResult.git_branch, mode: rs?.mode ?? null, diff --git a/apps/mobile/src/components/agents/new-session-configure-form.mounted.test.tsx b/apps/mobile/src/components/agents/new-session-configure-form.mounted.test.tsx index 0a401c85b3f..b936495a233 100644 --- a/apps/mobile/src/components/agents/new-session-configure-form.mounted.test.tsx +++ b/apps/mobile/src/components/agents/new-session-configure-form.mounted.test.tsx @@ -11,6 +11,7 @@ vi.mock('react-native', () => ({ I18nManager: { isRTL: false }, Keyboard: { addListener: vi.fn(() => ({ remove: vi.fn() })) }, Platform: { OS: 'ios' }, + Pressable: 'Pressable', ScrollView: 'ScrollView', View: 'View', })); @@ -42,8 +43,26 @@ vi.mock('@/components/agents/new-session-run-target', () => ({ vi.mock('@/components/agents/new-session-start-button', () => ({ NewSessionStartButton: 'NewSessionStartButton', })); +// The environment row and the advanced-config disclosure reach the profile +// surfaces; stub them so this geometry suite mounts no picker, no router and no +// profile query, and the theme hook (which reads `expo-router` themes) stays out +// of the module graph. +vi.mock('@/components/agents/advanced-config-panel', () => ({ + AdvancedConfigPanel: 'AdvancedConfigPanel', +})); +vi.mock('@/components/agents/active-profile-indicator', () => ({ + ActiveProfileIndicator: 'ActiveProfileIndicator', +})); +vi.mock('@/lib/hooks/use-theme-colors', () => ({ + useThemeColors: () => ({ foreground: '#000', mutedForeground: '#666' }), +})); + vi.mock('@/components/ui/button', () => ({ Button: 'Button' })); -vi.mock('@/components/ui/icons', () => ({ RefreshCw: 'RefreshCw' })); +vi.mock('@/components/ui/icons', () => ({ + ChevronDown: 'ChevronDown', + RefreshCw: 'RefreshCw', + SlidersHorizontal: 'SlidersHorizontal', +})); vi.mock('@/components/ui/segmented-control', () => ({ SegmentedControl: 'SegmentedControl' })); vi.mock('@/components/ui/skeleton', () => ({ Skeleton: 'Skeleton' })); vi.mock('@/components/ui/text', () => ({ Text: 'Text' })); @@ -102,7 +121,15 @@ function defaultProps() { profile: null, isProfileLoading: false, isProfileError: false, + profileOverrideNeedsAttention: false, onRetryProfile: vi.fn(), + onOpenProfilePicker: vi.fn(), + selectedProfileId: null as string | null, + onSelectProfile: vi.fn(), + manualVars: [], + manualCommands: [], + onManualVarsChange: vi.fn(), + onManualCommandsChange: vi.fn(), autoCommit: false, onAutoCommitChange: vi.fn(), isSpawningRemote: false, diff --git a/apps/mobile/src/components/agents/new-session-configure-form.test.ts b/apps/mobile/src/components/agents/new-session-configure-form.test.ts index d00ce85f0bd..1093fa9d203 100644 --- a/apps/mobile/src/components/agents/new-session-configure-form.test.ts +++ b/apps/mobile/src/components/agents/new-session-configure-form.test.ts @@ -3,6 +3,8 @@ import * as React from 'react'; import { describe, expect, it, vi } from 'vitest'; import { type AgentMode } from '@/components/agents/mode-selector'; +import { ActiveProfileIndicator } from '@/components/agents/active-profile-indicator'; +import { type EffectiveAgentProfile } from '@/components/agents/use-effective-agent-profile'; import { type NewSessionRepository, type RepositoryGroup, @@ -10,6 +12,8 @@ import { import { type InstancePickerInstance } from '@/lib/picker-bridge'; import { remoteSpawnInstanceDisconnectedNote } from '@/lib/remote-submit-outcome'; +import { NewSessionProfileRow } from './new-session-configure-form'; + import '@/i18n'; import type * as ReactI18next from 'react-i18next'; @@ -78,6 +82,7 @@ vi.mock('react-native', () => ({ }), }, Platform: platformState, + Pressable: 'Pressable', ScrollView: 'ScrollView', View: 'View', })); @@ -100,6 +105,8 @@ vi.mock('@/components/agents/instance-selector', () => ({ InstanceSelector: 'InstanceSelector', })); +vi.mock('@/components/ui/skeleton', () => ({ Skeleton: 'Skeleton' })); + vi.mock('@/components/agents/new-session-repository-section', () => ({ NewSessionRepositorySection: 'NewSessionRepositorySection', })); @@ -120,10 +127,18 @@ vi.mock('@/components/agents/new-session-cloud-create-error', () => ({ NewSessionCloudCreateError: 'NewSessionCloudCreateError', })); +vi.mock('@/components/agents/advanced-config-panel', () => ({ + AdvancedConfigPanel: 'AdvancedConfigPanel', +})); + vi.mock('@/components/ui/button', () => ({ Button: 'Button', })); -vi.mock('@/components/ui/icons', () => ({ RefreshCw: 'RefreshCw' })); +vi.mock('@/components/ui/icons', () => ({ + RefreshCw: 'RefreshCw', + ChevronDown: 'ChevronDown', + SlidersHorizontal: 'SlidersHorizontal', +})); vi.mock('@/components/ui/segmented-control', () => ({ SegmentedControl: 'SegmentedControl', @@ -188,6 +203,57 @@ function findElementByType(node: Node, typeName: string): Record[] { + if (node === null || typeof node !== 'object') { + return []; + } + const props = node.props ?? {}; + const children = props.children; + const type = (node as { type?: unknown }).type; + const found: Record[] = type === typeName ? [props] : []; + for (const child of Array.isArray(children) ? children : [children]) { + found.push(...collectElementsByType(child as Node, typeName)); + } + return found; +} + +/** True when the tree contains an element of this component type (by reference). */ +function containsComponent(node: Node, component: unknown): boolean { + if (node === null || typeof node !== 'object') { + return false; + } + if ((node as { type?: unknown }).type === component) { + return true; + } + const props = node.props ?? {}; + const children = props.children; + for (const child of Array.isArray(children) ? children : [children]) { + if (containsComponent(child as Node, component)) { + return true; + } + } + return false; +} + +/** First element of this component type (by reference) in the tree, or null. */ +function findElementByComponent(node: Node, component: unknown): Record | null { + if (node === null || typeof node !== 'object') { + return null; + } + if ((node as { type?: unknown }).type === component) { + return node.props ?? {}; + } + const props = node.props ?? {}; + const children = props.children; + for (const child of Array.isArray(children) ? children : [children]) { + const found = findElementByComponent(child as Node, component); + if (found) { + return found; + } + } + return null; +} + /** Height of the first node carrying an explicit `style.height` (an in-scroll spacer). */ function findElementHeight(node: Node): number | null { if (node === null || typeof node !== 'object') { @@ -315,17 +381,18 @@ function defaultProps() { recents: [] as NewSessionRepository[], selectedRepo: '', organizationId: undefined as string | undefined, - profile: null as { - id: string; - name: string; - commandCount: number; - mcpServerCount: number; - skillCount: number; - agentCount: number; - } | null, + profile: null as EffectiveAgentProfile | null, isProfileLoading: false, isProfileError: false, + profileOverrideNeedsAttention: false, onRetryProfile: vi.fn(), + onOpenProfilePicker: vi.fn(), + selectedProfileId: null as string | null, + onSelectProfile: vi.fn(), + manualVars: [], + manualCommands: [], + onManualVarsChange: vi.fn(), + onManualCommandsChange: vi.fn(), autoCommit: false, onAutoCommitChange: vi.fn(), isSpawningRemote: false, @@ -724,95 +791,186 @@ describe('NewSessionConfigureForm', () => { }); // ── Case 10: effective profile row ── - it('renders the profile name and capability counts when a profile resolves', async () => { - const { NewSessionConfigureForm } = await import('./new-session-configure-form'); + // The row is a component, so its content tests call it directly (the plain + // function call convention of this file); the form only gates its presence. + const PROFILE: EffectiveAgentProfile = { + id: 'profile-1', + name: 'Production', + varCount: 2, + mcpServerCount: 1, + skillCount: 2, + kiloCommandCount: 4, + commandCount: 3, + agentCount: 4, + }; + function renderRow(overrides: Partial[0]> = {}): Node { // eslint-disable-next-line new-cap -- plain function call, matching repo test convention - const element = NewSessionConfigureForm({ - ...defaultProps(), - runOnInstance: null, - profile: { - id: 'profile-1', - name: 'Production', - commandCount: 3, - mcpServerCount: 1, - skillCount: 2, - agentCount: 4, - }, + return NewSessionProfileRow({ + profile: null, + isProfileLoading: false, + isProfileError: false, + overrideNeedsAttention: false, + onRetryProfile: vi.fn<() => void>(), + onOpenProfilePicker: vi.fn<() => void>(), + ...overrides, }) as Node; + } + + it('renders the profile name and capability counts when a profile resolves', () => { + const element = renderRow({ profile: PROFILE }); expect(findTextContent(element, t => t === 'Production')).toBe(true); expect(findTextContent(element, t => t === '3 commands · 1 MCP · 2 skills · 4 agents')).toBe( true ); + // The chip's own copy is covered by its mounted test; here the row must + // hand it a resolved (non-null) indicator state. + expect(findElementByComponent(element, ActiveProfileIndicator)?.state).not.toBeNull(); + }); + + it('renders "Default environment" when no profile resolves', () => { + const element = renderRow(); + + expect(findTextContent(element, t => t === 'Default environment')).toBe(true); + expect(findTextContent(element, t => t === 'Production')).toBe(false); + // The chip is mounted but holds no state, so it renders nothing. + expect(findElementByComponent(element, ActiveProfileIndicator)?.state).toBeNull(); }); - it('renders "Default environment" when no profile resolves', async () => { + it('renders an inline error with Retry when the profile query fails', () => { + const element = renderRow({ isProfileError: true }); + + expect(findTextContent(element, t => t.includes("Couldn't load"))).toBe(true); + expect(findTextContent(element, t => t === 'Retry')).toBe(true); + }); + + it('shows the attention copy when the selected override no longer resolves', () => { + // The hook never yields a resolved profile together with a stale override: + // an unresolvable pick leaves `profile` null and sets the attention flag. + const element = renderRow({ profile: null, overrideNeedsAttention: true }); + + expect(findTextContent(element, t => t === 'Config needs attention')).toBe(true); + expect(findElementByComponent(element, ActiveProfileIndicator)?.state).toMatchObject({ + kind: 'config-needs-attention', + needsAttention: true, + }); + }); + + it('keeps the environment row at reserved height with skeletons while loading', () => { + const element = renderRow({ isProfileLoading: true }); + + // The label stays mounted and skeletons hold the row's height, so the + // rows below never move when the query settles. + expect(findTextContent(element, t => t === 'Environment')).toBe(true); + expect(findElementByType(element, 'Skeleton')).not.toBeNull(); + expect(findTextContent(element, t => t === 'Default environment')).toBe(false); + expect(findTextContent(element, t => t === 'Production')).toBe(false); + }); + + it('opens the profile picker when the row is pressed', () => { + const onOpenProfilePicker = vi.fn<() => void>(); + const element = renderRow({ profile: PROFILE, onOpenProfilePicker }); + + // The row body is the pressable that opens the picker. + const pressables = collectElementsByType(element, 'Pressable'); + expect(pressables.some(p => p.onPress === onOpenProfilePicker)).toBe(true); + }); + + it('explains the pending environment request without hiding the form or showing a default', async () => { const { NewSessionConfigureForm } = await import('./new-session-configure-form'); // eslint-disable-next-line new-cap -- plain function call, matching repo test convention const element = NewSessionConfigureForm({ ...defaultProps(), runOnInstance: null, - profile: null, + isProfileLoading: true, + isStartDisabled: true, }) as Node; - expect(findTextContent(element, t => t === 'Default environment')).toBe(true); + // The Environment row stays mounted while the request is pending, so the + // form is never hidden and no default flashes before the query settles. + // The row is a component here, so its content is asserted through the + // direct render the same way the row's own cases do. + const row = findElementByComponent(element, NewSessionProfileRow); + expect(row?.isProfileLoading).toBe(true); + const loadingRow = renderRow({ isProfileLoading: true }); + expect(findTextContent(loadingRow, t => t === 'Environment')).toBe(true); + expect(findTextContent(loadingRow, t => t === 'Loading…')).toBe(true); + expect(findTextContent(loadingRow, t => t === 'Default environment')).toBe(false); + expect(findElementByType(loadingRow, 'Skeleton')).not.toBeNull(); + expect(findElementByType(element, 'NewSessionPrompt')?.isCreating).toBe(false); + expect(findElementByType(element, 'NewSessionStartButton')?.isStartDisabled).toBe(true); + }); + + it('does not render the environment row for a remote target', async () => { + const { NewSessionConfigureForm } = await import('./new-session-configure-form'); + + // eslint-disable-next-line new-cap -- plain function call, matching repo test convention + const element = NewSessionConfigureForm({ + ...defaultProps(), + runOnInstance: INSTANCE, + profile: PROFILE, + }) as Node; + + expect(containsComponent(element, NewSessionProfileRow)).toBe(false); expect(findTextContent(element, t => t === 'Production')).toBe(false); }); - it('renders an inline error with Retry when the profile query fails', async () => { + it('renders the environment row for a cloud target', async () => { const { NewSessionConfigureForm } = await import('./new-session-configure-form'); // eslint-disable-next-line new-cap -- plain function call, matching repo test convention const element = NewSessionConfigureForm({ ...defaultProps(), runOnInstance: null, - isProfileError: true, + profile: PROFILE, }) as Node; - expect(findTextContent(element, t => t.includes("Couldn't load"))).toBe(true); - expect(findTextContent(element, t => t === 'Retry')).toBe(true); + expect(containsComponent(element, NewSessionProfileRow)).toBe(true); }); - it('explains the pending environment request without hiding the form or showing a default', async () => { + // ── Case 10b: the advanced-config disclosure ── + it('mounts the collapsed advanced-config panel under Environment for a cloud target', async () => { const { NewSessionConfigureForm } = await import('./new-session-configure-form'); + const onSelectProfile = vi.fn<(id: string | null) => void>(); // eslint-disable-next-line new-cap -- plain function call, matching repo test convention const element = NewSessionConfigureForm({ ...defaultProps(), runOnInstance: null, - isProfileLoading: true, - isStartDisabled: true, + organizationId: 'org-1', + selectedProfileId: 'profile-1', + onSelectProfile, }) as Node; - expect(findTextContent(element, t => t === 'Environment')).toBe(true); - expect(findTextContent(element, t => t === 'Loading…')).toBe(true); - expect(findTextContent(element, t => t === 'Default environment')).toBe(false); - expect(findElementByType(element, 'Skeleton')).not.toBeNull(); - expect(findElementByType(element, 'NewSessionPrompt')?.isCreating).toBe(false); - expect(findElementByType(element, 'NewSessionStartButton')?.isStartDisabled).toBe(true); + // The panel is controlled by the session, so it gets the same override the + // Environment row shows and reports picks back through the same callback. + expect(findElementByType(element, 'AdvancedConfigPanel')).toMatchObject({ + organizationId: 'org-1', + selectedProfileId: 'profile-1', + onSelectProfile, + disabled: false, + }); }); - it('does not render the environment row for a remote target', async () => { + it('hides the advanced-config panel for a remote target and for the clone entry', async () => { const { NewSessionConfigureForm } = await import('./new-session-configure-form'); // eslint-disable-next-line new-cap -- plain function call, matching repo test convention - const element = NewSessionConfigureForm({ + const remote = NewSessionConfigureForm({ ...defaultProps(), runOnInstance: INSTANCE, - profile: { - id: 'profile-1', - name: 'Production', - commandCount: 3, - mcpServerCount: 1, - skillCount: 2, - agentCount: 4, - }, }) as Node; + expect(findElementByType(remote, 'AdvancedConfigPanel')).toBeNull(); - expect(findTextContent(element, t => t === 'Environment')).toBe(false); - expect(findTextContent(element, t => t === 'Production')).toBe(false); + // eslint-disable-next-line new-cap -- plain function call, matching repo test convention + const clone = NewSessionConfigureForm({ + ...defaultProps(), + runOnInstance: null, + isCloneEntry: true, + }) as Node; + expect(findElementByType(clone, 'AdvancedConfigPanel')).toBeNull(); }); // ── Case 11: commit choice (cloud-only, default Leave) ── diff --git a/apps/mobile/src/components/agents/new-session-configure-form.tsx b/apps/mobile/src/components/agents/new-session-configure-form.tsx index 5cae70c6c00..0b87290bb31 100644 --- a/apps/mobile/src/components/agents/new-session-configure-form.tsx +++ b/apps/mobile/src/components/agents/new-session-configure-form.tsx @@ -1,23 +1,62 @@ +/* eslint-disable max-lines -- THE new-session body: one screen for every entry point, with a mutually-exclusive branch per target/state. */ import { useState } from 'react'; -import { type LayoutChangeEvent, ScrollView, View } from 'react-native'; +import { type LayoutChangeEvent, Pressable, ScrollView, View } from 'react-native'; import { useTranslation } from 'react-i18next'; import { LaunchFolderField } from '@/components/agents/folder-selector'; +import { ActiveProfileIndicator } from '@/components/agents/active-profile-indicator'; +import { buildActiveProfileIndicatorState } from '@/components/agents/active-profile-indicator-model'; +import { AdvancedConfigPanel } from '@/components/agents/advanced-config-panel'; import { NewSessionCloudCreateError } from '@/components/agents/new-session-cloud-create-error'; import { type NewSessionConfigureFormProps } from '@/components/agents/new-session-configure-form-props'; -import { renderProfileRow } from '@/components/agents/new-session-profile-row'; +import { renderProfileRowBody } from '@/components/agents/new-session-profile-row'; +import { type EffectiveAgentProfile } from '@/components/agents/use-effective-agent-profile'; import { NewSessionPrompt } from '@/components/agents/new-session-prompt'; import { NewSessionRepositorySection } from '@/components/agents/new-session-repository-section'; import { NewSessionRunTarget } from '@/components/agents/new-session-run-target'; import { NewSessionStartButton } from '@/components/agents/new-session-start-button'; import { useComposerRevealScroll } from '@/components/agents/use-composer-reveal-scroll'; import { AppAwareKeyboardPaddingView } from '@/components/kilo-chat/app-aware-keyboard-padding'; +import { type VariableEdit } from '@/components/profiles/profile-variables-model'; +import { ChevronDown } from '@/components/ui/icons'; import { SegmentedControl } from '@/components/ui/segmented-control'; import { Text } from '@/components/ui/text'; import { stripInlineCodeMarkers } from '@/i18n/plain-copy'; +import { useThemeColors } from '@/lib/hooks/use-theme-colors'; import { remoteSpawnInstanceDisconnectedNote } from '@/lib/remote-submit-outcome'; import { useDetailScreenBottomPadding } from '@/lib/screen-insets'; +/** + * The profile override the new-session screen adds to the shared contract: the + * Environment row and the advanced-config selector drive one session-level pick. + * The base fields live in `new-session-configure-form-props`, extracted so this + * file stays within the repo's line cap. + */ +type NewSessionProfileOverrideProps = { + /** The picked override no longer resolves to a profile. */ + profileOverrideNeedsAttention: boolean; + /** Opens the profile picker sheet. */ + onOpenProfilePicker: () => void; + /** + * The session's profile override, shared by the Environment row and the + * advanced-config selector; null keeps the effective default. + */ + selectedProfileId: string | null; + /** Reports a pick (or `No profile`) from the advanced-config selector. */ + onSelectProfile: (id: string | null) => void; + /** + * The session's manual environment variables and setup commands. Owned by the + * new-session body (not the panel) so the create carries them; the advanced + * config editors only report changes. + */ + manualVars: readonly VariableEdit[]; + manualCommands: readonly string[]; + onManualVarsChange: (next: VariableEdit[]) => void; + onManualCommandsChange: (next: string[]) => void; + /** Opens the repo default-profile bindings screen from the advanced config. */ + onOpenRepoDefaults?: () => void; +}; + /** * THE new-session screen body — one screen for every entry point (cloud, * remote CLI, share-staged). The composer, the mode and the model controls @@ -74,7 +113,16 @@ export function NewSessionConfigureForm({ profile, isProfileLoading, isProfileError, + profileOverrideNeedsAttention, onRetryProfile, + onOpenProfilePicker, + selectedProfileId, + onSelectProfile, + manualVars, + manualCommands, + onManualVarsChange, + onManualCommandsChange, + onOpenRepoDefaults, autoCommit, onAutoCommitChange, isSpawningRemote, @@ -82,7 +130,7 @@ export function NewSessionConfigureForm({ onStartSession, cloudCreateError = null, onRetryCloudCreate, -}: Readonly) { +}: Readonly) { const { t } = useTranslation(); // The form is edge-to-edge and the window never resizes for the IME on // either platform, so the screen needs two floors. The first is the @@ -278,9 +326,36 @@ export function NewSessionConfigureForm({ ) : null} - {!isRemote && !isCloneEntry - ? renderProfileRow({ t, profile, isProfileLoading, isProfileError, onRetryProfile }) - : null} + {!isRemote && !isCloneEntry ? ( + + ) : null} + + { + // The advanced configuration disclosure sits under Environment. It is + // collapsed by default, so the screen is unchanged until it is tapped; + // both the profile pick and the manual env/command draft are the + // session's own state, so one submitted create carries them all. + } + {!isRemote && !isCloneEntry ? ( + + ) : null} ); @@ -327,3 +402,90 @@ export function NewSessionConfigureForm({ ); } + +type NewSessionProfileRowProps = { + profile: EffectiveAgentProfile | null; + isProfileLoading: boolean; + isProfileError: boolean; + /** The picked override no longer resolves to a profile. */ + overrideNeedsAttention: boolean; + onRetryProfile: () => void; + /** Opens the profile picker sheet. */ + onOpenProfilePicker: () => void; +}; + +/** + * The new-session Environment row: a tappable summary of the effective profile + * with the active-profile indicator beside its label. Loading and failure keep + * the shared body's reserved lines, so the rows below never move when the query + * settles and no default flashes before it does; the settled row is the entry + * point that opens the profile picker. + * + * It lives with this screen rather than in `new-session-profile-row` because + * the indicator and the chevron reach the app's icon barrel, which the mounted + * suite that renders the read-only row cannot load. + */ +export function NewSessionProfileRow({ + profile, + isProfileLoading, + isProfileError, + overrideNeedsAttention, + onRetryProfile, + onOpenProfilePicker, +}: Readonly) { + const { t } = useTranslation(); + const colors = useThemeColors(); + + const indicatorState = buildActiveProfileIndicatorState({ + selectedProfileName: profile?.name ?? null, + repoBoundProfileName: null, + hasManualEnvVars: false, + hasManualSetupCommands: false, + hasSelectedProfileId: profile !== null || overrideNeedsAttention, + isProfilesLoading: isProfileLoading, + hasProfileError: isProfileError, + }); + + return ( + + + + {t('agentChat.newSession.environment')} + + + + {isProfileLoading || isProfileError ? ( + renderProfileRowBody({ t, profile, isProfileLoading, isProfileError, onRetryProfile }) + ) : ( + + + {overrideNeedsAttention ? ( + + {t('agentChat.newSession.configNeedsAttention')} + + ) : null} + + {profile?.name ?? t('agentChat.newSession.defaultEnvironment')} + + {profile ? ( + + {t('agentChat.newSession.environmentSummary', { + commands: profile.commandCount, + mcp: profile.mcpServerCount, + skills: profile.skillCount, + agents: profile.agentCount, + })} + + ) : null} + + + + )} + + ); +} diff --git a/apps/mobile/src/components/agents/new-session-profile-row.tsx b/apps/mobile/src/components/agents/new-session-profile-row.tsx index fc9e3c7dd24..7f006ad37b7 100644 --- a/apps/mobile/src/components/agents/new-session-profile-row.tsx +++ b/apps/mobile/src/components/agents/new-session-profile-row.tsx @@ -11,14 +11,24 @@ import { cn } from '@/lib/utils'; type RenderProfileRowArgs = { isProfileLoading: boolean; t: TFunction; - profile: EffectiveAgentProfile | null; + profile: ProfileRowProfile | null; isProfileError: boolean; onRetryProfile: () => void; }; +/** The capability summary the row renders; a full profile is assignable. */ +type ProfileRowProfile = Pick< + EffectiveAgentProfile, + 'name' | 'commandCount' | 'mcpServerCount' | 'skillCount' | 'agentCount' +>; + /** * Keep the environment visible while it gates Start, without showing a default * before the query settles. Every state reserves the same two text lines. + * + * This module stays free of the app's icon barrel: the mounted suite that + * renders it cannot load `lucide-react-native`, so the tappable + * `NewSessionProfileRow` (which needs the chevron) lives with its screen. */ export function renderProfileRow({ t, @@ -32,12 +42,16 @@ export function renderProfileRow({ {t('agentChat.newSession.environment')} - {renderProfileBody({ t, profile, isProfileLoading, isProfileError, onRetryProfile })} + {renderProfileRowBody({ t, profile, isProfileLoading, isProfileError, onRetryProfile })} ); } -function renderProfileBody({ +/** + * The state body without the Environment label. `NewSessionProfileRow` reuses + * it for its loading and failure states so both rows reserve the same lines. + */ +export function renderProfileRowBody({ t, profile, isProfileLoading, diff --git a/apps/mobile/src/components/agents/new-session-screen-body.tsx b/apps/mobile/src/components/agents/new-session-screen-body.tsx index d1f9cef4c3b..a85dc21e979 100644 --- a/apps/mobile/src/components/agents/new-session-screen-body.tsx +++ b/apps/mobile/src/components/agents/new-session-screen-body.tsx @@ -2,7 +2,7 @@ import { useCallback, useEffect, useMemo, useRef, useState } from 'react'; import { View } from 'react-native'; import { useTranslation } from 'react-i18next'; -import { useLocalSearchParams, useNavigation } from 'expo-router'; +import { type Href, useLocalSearchParams, useNavigation, useRouter } from 'expo-router'; import { useActionSheet } from '@expo/react-native-action-sheet'; import { useQuery } from '@tanstack/react-query'; import { toast } from 'sonner-native'; @@ -27,6 +27,7 @@ import { readCloneSourceTitle, } from '@/components/agents/new-session-prefill'; import { useContinueCloudCreate } from '@/components/agents/use-continue-cloud-create'; +import { type VariableEdit } from '@/components/profiles/profile-variables-model'; import { ScreenHeader } from '@/components/screen-header'; import { Text } from '@/components/ui/text'; import { i18n } from '@/i18n'; @@ -51,6 +52,7 @@ import { resolveNewSessionStartDisabled, } from '@/lib/new-session-submit'; import { usePreventRemove } from '@/lib/navigation/prevent-remove'; +import { getRepoBindingsPath } from '@/lib/profile-agent-navigation'; import { clearDraft, NEW_SESSION_DRAFT_KEY, @@ -60,6 +62,7 @@ import { import { useDraftFlushOnBackground } from '@/lib/persist/use-draft-flush'; import { useFencedDraftLoad, useRemoteSpawnDraftCleanup } from '@/lib/persist/use-draft-load'; import { type InstancePickerInstance, type ModelPickerSelection } from '@/lib/picker-bridge'; +import { profilePickerSlot, UNFENCED_ROUTE_KEY } from '@/lib/route-registry'; import { PRESELECT_CLOUD_RUN_ON, readPreselectRunOn, @@ -97,6 +100,7 @@ function AndroidPendingPickerRecovery({ export function NewSessionScreenBody() { const { mode, setMode, model, setModel, variant, setVariant } = useNewSessionModelState(); const { t } = useTranslation(); + const router = useRouter(); const themedSheet = useThemedActionSheetOptions(); const { showActionSheetWithOptions } = useActionSheet(); const searchParams = useLocalSearchParams<{ @@ -126,6 +130,13 @@ export function NewSessionScreenBody() { // Start is never a silent no-op; a retryable one keeps the Retry control. const [cloudCreateError, setCloudCreateError] = useState(null); const [hasPrompt, setHasPrompt] = useState(false); + // The profile picked for THIS session; null keeps the effective default. + const [overrideProfileId, setOverrideProfileId] = useState(null); + // The manual env vars and setup commands typed under Advanced Configuration. + // Owned here (not by the panel) so the create body carries them even when the + // user starts without saving a profile. + const [manualVars, setManualVars] = useState([]); + const [manualCommands, setManualCommands] = useState([]); // Commit choice for the cloud session: Leave changes (false) is the default. const [autoCommit, setAutoCommit] = useState(false); // Relative launch folder the folder picker confirmed (`""` = launch directory). @@ -280,8 +291,29 @@ export function NewSessionScreenBody() { profileId, isLoading: isProfileLoading, isError: isProfileError, + overrideNeedsAttention: profileOverrideNeedsAttention, refetch: refetchProfile, - } = useEffectiveAgentProfile(organizationId); + } = useEffectiveAgentProfile(organizationId, overrideProfileId); + + // The picker opens as the app's standard native formSheet. The bridge carries + // the current pick; the sheet reports the new one back through `onSelect`. + const handleOpenProfilePicker = useCallback(() => { + profilePickerSlot.set(UNFENCED_ROUTE_KEY, { + organizationId, + selectedOverrideProfileId: overrideProfileId, + // The server resolves a repo's bound profile at session creation; the + // picker base layer stays empty here. + repoBindingProfileId: null, + onSelect: id => { + setOverrideProfileId(id); + }, + }); + router.push('/(app)/agent-chat/profile-picker' as Href); + }, [organizationId, overrideProfileId, router]); + + const handleOpenRepoDefaults = useCallback(() => { + router.push(getRepoBindingsPath(organizationId)); + }, [organizationId, router]); // Keep the inline selector and picker list in sync. const { @@ -391,6 +423,18 @@ export function NewSessionScreenBody() { cloneNavigateBypassRef.current = true; }, []); + // The manual draft as the create body carries it: one plaintext value per key + // (the editor already refuses a key that cleans onto an existing one) and the + // non-blank setup commands in list order. + const manualEnvVars = useMemo( + () => Object.fromEntries(manualVars.map(variable => [variable.key, variable.value])), + [manualVars] + ); + const setupCommands = useMemo( + () => manualCommands.filter(command => command.trim().length > 0), + [manualCommands] + ); + const { createSessionFromDraft, promptRef } = useNewSessionCreator({ attachments, mode, @@ -403,6 +447,8 @@ export function NewSessionScreenBody() { variant: displayVariant, autoCommit, profileId, + manualEnvVars, + setupCommands, }); // Seed the route-owned prompt state from the restored draft once the load @@ -777,7 +823,16 @@ export function NewSessionScreenBody() { profile={profile} isProfileLoading={isProfileLoading} isProfileError={isProfileError} + profileOverrideNeedsAttention={profileOverrideNeedsAttention} onRetryProfile={() => void refetchProfile()} + onOpenProfilePicker={handleOpenProfilePicker} + selectedProfileId={overrideProfileId} + onSelectProfile={setOverrideProfileId} + manualVars={manualVars} + manualCommands={manualCommands} + onManualVarsChange={setManualVars} + onManualCommandsChange={setManualCommands} + onOpenRepoDefaults={handleOpenRepoDefaults} autoCommit={autoCommit} onAutoCommitChange={setAutoCommit} isStartDisabled={isStartDisabled} diff --git a/apps/mobile/src/components/agents/profile-picker-sheet.mounted.test.tsx b/apps/mobile/src/components/agents/profile-picker-sheet.mounted.test.tsx new file mode 100644 index 00000000000..197f2a8211e --- /dev/null +++ b/apps/mobile/src/components/agents/profile-picker-sheet.mounted.test.tsx @@ -0,0 +1,233 @@ +import { createElement, type ReactNode } from 'react'; +import { act, TestRenderer } from '@/test/renderer'; +import { beforeEach, describe, expect, it, vi } from 'vitest'; + +import '@/i18n'; +import { ProfilePickerSheet, type ProfilePickerSheetProps } from './profile-picker-sheet'; +import { type SessionProfilePickerProfile } from './session-profile-picker-model'; + +vi.mock('react-native', () => ({ + Pressable: 'Pressable', + View: 'View', +})); +vi.mock('@/components/picker-sheet', () => ({ + PickerSheet: (props: { + title: string; + onDone: () => void; + onCancel?: () => void; + scrollable?: boolean; + children?: ReactNode; + }) => + createElement( + 'PickerSheet', + { + title: props.title, + scrollable: props.scrollable, + onCancel: props.onCancel, + onDone: props.onDone, + }, + props.children + ), +})); +vi.mock('@/components/empty-state', () => ({ + EmptyState: (props: { title: string; description: unknown; action?: ReactNode }) => + createElement('EmptyState', props, props.action), +})); +vi.mock('@/components/ui/button', () => ({ Button: 'Button' })); +vi.mock('@/components/ui/skeleton', () => ({ Skeleton: 'Skeleton' })); +vi.mock('@/components/ui/text', async () => { + const React = await import('react'); + return { Text: 'Text', TextClassContext: React.createContext(undefined) }; +}); +vi.mock('@/components/ui/icons', () => ({ + Check: 'Check', + Settings2: 'Settings2', + SlidersHorizontal: 'SlidersHorizontal', +})); +vi.mock('@/lib/hooks/use-theme-colors', () => ({ + useThemeColors: () => ({ primary: '#0a84ff', mutedForeground: '#666', warn: '#956011' }), +})); + +function profile( + overrides: Partial & { id: string; name: string } +): SessionProfilePickerProfile { + return { + varCount: 0, + mcpServerCount: 0, + skillCount: 0, + kiloCommandCount: 0, + ...overrides, + }; +} + +const BACKEND = profile({ id: 'backend', name: 'Backend', varCount: 3, mcpServerCount: 1 }); +const FRONTEND = profile({ id: 'frontend', name: 'Frontend', skillCount: 2 }); + +function defaults(overrides: Partial = {}): ProfilePickerSheetProps { + return { + candidates: [BACKEND, FRONTEND], + hasProfiles: true, + selectedOverrideProfileId: null, + defaultProfileApplies: false, + isLoading: false, + isError: false, + needsAttention: false, + onSelect: vi.fn<() => void>(), + onManageProfiles: vi.fn<() => void>(), + onRetry: vi.fn<() => void>(), + onClose: vi.fn<() => void>(), + ...overrides, + }; +} + +function mount(props: Partial = {}) { + const ref: { current: TestRenderer.ReactTestRenderer | null } = { current: null }; + act(() => { + ref.current = TestRenderer.create(createElement(ProfilePickerSheet, defaults(props))); + }); + const renderer = ref.current; + if (renderer === null) { + throw new Error('the picker did not render'); + } + return renderer; +} + +function radios(renderer: TestRenderer.ReactTestRenderer) { + return renderer.root.findAll(node => node.props.accessibilityRole === 'radio'); +} + +function radio(renderer: TestRenderer.ReactTestRenderer, label: string) { + return radios(renderer).find(node => node.props.accessibilityLabel === label); +} + +function checkedStates(renderer: TestRenderer.ReactTestRenderer): (boolean | undefined)[] { + return radios(renderer).map( + node => + (node.props as { accessibilityState?: { checked?: boolean } }).accessibilityState?.checked + ); +} + +function texts(renderer: TestRenderer.ReactTestRenderer): string[] { + return renderer.root + .findAllByType('Text' as never) + .flatMap(node => node.children) + .filter((child): child is string => typeof child === 'string'); +} + +function press(node: TestRenderer.ReactTestInstance | undefined) { + act(() => { + (node?.props.onPress as (() => void) | undefined)?.(); + }); +} + +beforeEach(() => { + (globalThis as { IS_REACT_ACT_ENVIRONMENT?: boolean }).IS_REACT_ACT_ENVIRONMENT = true; +}); + +describe('ProfilePickerSheet', () => { + it('offers a No profile row plus one checked row per candidate with counts', () => { + const renderer = mount(); + + expect(renderer.root.findByType('PickerSheet' as never).props.title).toBe('Pick a profile'); + expect(radios(renderer).map(node => node.props.accessibilityLabel)).toEqual([ + 'No profile', + 'Backend', + 'Frontend', + ]); + expect(texts(renderer)).toContain('3 vars · 1 MCP'); + expect(checkedStates(renderer)).toEqual([true, false, false]); + }); + + it('checks the picked override row', () => { + const renderer = mount({ selectedOverrideProfileId: 'frontend' }); + expect(checkedStates(renderer)).toEqual([false, false, true]); + }); + + it('selects a candidate, and clears it when the checked row is tapped again', () => { + const onSelect = vi.fn<() => void>(); + const renderer = mount({ selectedOverrideProfileId: 'backend', onSelect }); + + press(radio(renderer, 'Frontend')); + expect(onSelect).toHaveBeenLastCalledWith('frontend'); + + press(radio(renderer, 'Backend')); + expect(onSelect).toHaveBeenLastCalledWith(null); + }); + + it('selects No profile explicitly', () => { + const onSelect = vi.fn<() => void>(); + const renderer = mount({ selectedOverrideProfileId: 'backend', onSelect }); + + press(radio(renderer, 'No profile')); + expect(onSelect).toHaveBeenLastCalledWith(null); + }); + + it('names the effective default on the row that clears the override', () => { + const onSelect = vi.fn<() => void>(); + const renderer = mount({ + selectedOverrideProfileId: 'backend', + defaultProfileApplies: true, + onSelect, + }); + + // Clearing the override hands the session to the default, so the row says + // which profile that is instead of claiming no profile. + expect(radios(renderer).map(node => node.props.accessibilityLabel)).toEqual([ + 'Default profile', + 'Backend', + 'Frontend', + ]); + press(radio(renderer, 'Default profile')); + expect(onSelect).toHaveBeenLastCalledWith(null); + }); + + it('leaves every row unchecked and shows the attention copy for a stale override', () => { + const renderer = mount({ selectedOverrideProfileId: 'deleted', needsAttention: true }); + + expect(checkedStates(renderer)).toEqual([false, false, false]); + expect(texts(renderer)).toContain('Config needs attention'); + }); + + it('renders skeleton rows while the profiles load', () => { + const renderer = mount({ isLoading: true }); + + expect(renderer.root.findAllByType('Skeleton' as never)).toHaveLength(4); + expect(radios(renderer)).toHaveLength(0); + }); + + it('renders the retryable failure with Retry and refetches', () => { + const onRetry = vi.fn<() => void>(); + const renderer = mount({ isError: true, onRetry }); + + const empty = renderer.root.findByType('EmptyState' as never); + expect(empty.props.title).toBe("Couldn't load your environment"); + const retry = renderer.root.findByType('Button' as never); + expect(retry.props.accessibilityLabel).toBe('Retry'); + press(retry); + expect(onRetry).toHaveBeenCalledTimes(1); + expect(radios(renderer)).toHaveLength(0); + }); + + it('renders the empty state with the manage-profiles action', () => { + const onManageProfiles = vi.fn<() => void>(); + const renderer = mount({ hasProfiles: false, candidates: [], onManageProfiles }); + + const empty = renderer.root.findByType('EmptyState' as never); + expect(empty.props.description).toBe( + 'No profiles yet. Create one to add environment variables, MCP servers, and skills.' + ); + const manage = renderer.root.findByType('Button' as never); + expect(manage.props.accessibilityLabel).toBe('Manage profiles'); + press(manage); + expect(onManageProfiles).toHaveBeenCalledTimes(1); + expect(radios(renderer)).toHaveLength(0); + }); + + it('keeps the manage entry available in the populated list', () => { + const onManageProfiles = vi.fn<() => void>(); + const renderer = mount({ onManageProfiles }); + + press(renderer.root.findByType('Button' as never)); + expect(onManageProfiles).toHaveBeenCalledTimes(1); + }); +}); diff --git a/apps/mobile/src/components/agents/profile-picker-sheet.tsx b/apps/mobile/src/components/agents/profile-picker-sheet.tsx new file mode 100644 index 00000000000..029c846e9d0 --- /dev/null +++ b/apps/mobile/src/components/agents/profile-picker-sheet.tsx @@ -0,0 +1,190 @@ +import { Pressable, View } from 'react-native'; +import { useTranslation } from 'react-i18next'; + +import { EmptyState } from '@/components/empty-state'; +import { PickerSheet } from '@/components/picker-sheet'; +import { + sessionProfileCountItems, + type SessionProfilePickerProfile, +} from '@/components/agents/session-profile-picker-model'; +import { Button } from '@/components/ui/button'; +import { Check, Settings2 } from '@/components/ui/icons'; +import { RadioGroup, radioItemA11y } from '@/components/ui/radio-group'; +import { Skeleton } from '@/components/ui/skeleton'; +import { Text } from '@/components/ui/text'; +import { useThemeColors } from '@/lib/hooks/use-theme-colors'; +import { formatProfileCountItems } from '@/lib/profile-count-labels'; + +const SKELETON_ROW_COUNT = 4; + +export type ProfilePickerSheetProps = { + /** Rows offered as an override: every profile except the repo base. */ + candidates: readonly SessionProfilePickerProfile[]; + /** Whether any profile exists at all; false renders the empty state. */ + hasProfiles: boolean; + /** The user's explicit pick, or null when the effective default applies. */ + selectedOverrideProfileId: string | null; + /** + * Whether an effective default applies when no override is picked. The + * no-override row then clears the pick and that default is what the session + * runs on, so the row names the default instead of claiming no profile. + */ + defaultProfileApplies: boolean; + isLoading: boolean; + isError: boolean; + /** The picked id no longer resolves to a profile. */ + needsAttention: boolean; + onSelect: (id: string | null) => void; + onManageProfiles: () => void; + onRetry: () => void; + onClose: () => void; +}; + +/** + * The session-start profile picker body: the no-override row plus one row per + * candidate, with the manage-profiles entry. Presented by + * `agent-chat/profile-picker` inside the app's standard native formSheet, so + * dismissal is the sheet's swipe-down or this header's Cancel. + */ +export function ProfilePickerSheet({ + candidates, + hasProfiles, + defaultProfileApplies, + selectedOverrideProfileId, + isLoading, + isError, + needsAttention, + onSelect, + onManageProfiles, + onRetry, + onClose, +}: Readonly) { + const { t } = useTranslation(); + const colors = useThemeColors(); + const title = t('agentChat.newSession.pickProfile'); + // Clearing the override hands the session to the effective default, so the + // row names that default instead of claiming no profile is active. + const noOverrideLabelKey = defaultProfileApplies + ? 'profiles.defaultSectionTitle' + : 'agentChat.newSession.noProfile'; + + if (isLoading) { + return ( + + + {Array.from({ length: SKELETON_ROW_COUNT }, (_, index) => ( + + + + ))} + + + ); + } + + if (isError) { + return ( + + + {t('common.retry')} + + } + /> + + ); + } + + if (!hasProfiles) { + return ( + + + {t('agentChat.newSession.manageProfiles')} + + } + /> + + ); + } + + return ( + + {needsAttention ? ( + + {t('agentChat.newSession.configNeedsAttention')} + + ) : null} + + { + onSelect(null); + }} + {...radioItemA11y({ + label: t(noOverrideLabelKey), + checked: selectedOverrideProfileId === null && !needsAttention, + })} + > + + {t(noOverrideLabelKey)} + + {selectedOverrideProfileId === null && !needsAttention ? ( + + ) : null} + + + {candidates.map(candidate => { + const selected = candidate.id === selectedOverrideProfileId; + const counts = formatProfileCountItems(t, sessionProfileCountItems(candidate)).join( + ' · ' + ); + return ( + { + onSelect(candidate.id === selectedOverrideProfileId ? null : candidate.id); + }} + {...radioItemA11y({ label: candidate.name, checked: selected })} + > + + + {candidate.name} + + {counts ? ( + + {counts} + + ) : null} + + {selected ? : null} + + ); + })} + + + + + + + ); +} diff --git a/apps/mobile/src/components/agents/profile-selector-model.test.ts b/apps/mobile/src/components/agents/profile-selector-model.test.ts new file mode 100644 index 00000000000..38824a7c141 --- /dev/null +++ b/apps/mobile/src/components/agents/profile-selector-model.test.ts @@ -0,0 +1,189 @@ +import { describe, expect, it } from 'vitest'; + +import { + buildProfileSelectorState, + PROFILE_SELECTOR_KEYS as K, + profileSelectorCountItems, + type ProfileSelectorProfile, +} from './profile-selector-model'; + +function profile( + overrides: Partial & { id: string; name: string } +): ProfileSelectorProfile { + return { + varCount: 0, + commandCount: 0, + isDefault: false, + ownerType: 'user', + ...overrides, + }; +} + +const ORG_PROFILE = profile({ + id: 'org-1', + name: 'Org Backend', + ownerType: 'organization', + varCount: 3, + commandCount: 1, + isDefault: true, +}); +const PERSONAL_PROFILE = profile({ id: 'user-1', name: 'My Setup', isDefault: false }); +const PERSONAL_DEFAULT = profile({ id: 'user-2', name: 'My Default', isDefault: true }); + +const rowKinds = (state: ReturnType) => + state.rows.map(row => row.kind); + +const profileIds = (state: ReturnType) => + state.rows.flatMap(row => (row.kind === 'profile' ? [row.profile.id] : [])); + +describe('profileSelectorCountItems', () => { + it('resolves N vars and N cmds when either count is non-zero', () => { + expect( + profileSelectorCountItems(profile({ id: 'a', name: 'A', varCount: 3, commandCount: 1 })) + ).toEqual([ + { kind: 'vars', count: 3 }, + { kind: 'commands', count: 1 }, + ]); + }); + + it('resolves nothing when both counts are zero', () => { + expect(profileSelectorCountItems(profile({ id: 'a', name: 'A' }))).toEqual([]); + }); +}); + +describe('buildProfileSelectorState', () => { + it('offers only No profile, Manage and repo defaults when there are no profiles', () => { + const state = buildProfileSelectorState({ + organizationId: undefined, + orgProfiles: [], + personalProfiles: [], + effectiveDefaultId: null, + selectedProfileId: null, + }); + + expect(rowKinds(state)).toEqual(['none', 'manage', 'repo-defaults']); + expect(state.rows[0]).toMatchObject({ kind: 'none', labelKey: K.noProfile }); + expect(state.noOverrideLabelKey).toBe(K.noProfile); + expect(state.rows[1]).toMatchObject({ kind: 'manage', labelKey: K.manageProfiles }); + expect(state.rows[2]).toMatchObject({ + kind: 'repo-defaults', + labelKey: K.repoDefaults, + }); + expect(state.selectedProfile).toBeNull(); + expect(state.selectedIsEffectiveDefault).toBe(false); + }); + + it('groups org then personal profiles in org context with the effective-default star', () => { + const state = buildProfileSelectorState({ + organizationId: 'org-1', + orgProfiles: [ORG_PROFILE], + personalProfiles: [PERSONAL_PROFILE, PERSONAL_DEFAULT], + effectiveDefaultId: 'org-1', + selectedProfileId: null, + }); + + expect(rowKinds(state)).toEqual([ + 'none', + 'header', + 'profile', + 'header', + 'profile', + 'profile', + 'manage', + 'repo-defaults', + ]); + expect(state.rows[1]).toMatchObject({ kind: 'header', labelKey: K.organizationProfiles }); + expect(state.rows[3]).toMatchObject({ kind: 'header', labelKey: K.personalProfiles }); + expect(profileIds(state)).toEqual(['org-1', 'user-1', 'user-2']); + // Org context: the effective default id drives the star for both groups. + expect(state.rows[2]).toMatchObject({ kind: 'profile', isEffectiveDefault: true }); + expect(state.rows[4]).toMatchObject({ kind: 'profile', isEffectiveDefault: false }); + expect(state.rows[5]).toMatchObject({ kind: 'profile', isEffectiveDefault: false }); + }); + + it('uses the personal default flag in personal context with the Your Profiles title', () => { + const state = buildProfileSelectorState({ + organizationId: undefined, + orgProfiles: [ORG_PROFILE], + personalProfiles: [PERSONAL_PROFILE, PERSONAL_DEFAULT], + effectiveDefaultId: 'user-2', + selectedProfileId: null, + }); + + // Org profiles are ignored outside an organization scope. + expect(profileIds(state)).toEqual(['user-1', 'user-2']); + expect(state.rows[1]).toMatchObject({ kind: 'header', labelKey: K.yourProfiles }); + expect(state.rows[2]).toMatchObject({ kind: 'profile', isEffectiveDefault: false }); + expect(state.rows[3]).toMatchObject({ kind: 'profile', isEffectiveDefault: true }); + }); + + it('names the effective default in the no-override row instead of No profile', () => { + const personal = buildProfileSelectorState({ + organizationId: undefined, + orgProfiles: [], + personalProfiles: [PERSONAL_PROFILE, PERSONAL_DEFAULT], + effectiveDefaultId: 'user-2', + selectedProfileId: null, + }); + const organization = buildProfileSelectorState({ + organizationId: 'org-1', + orgProfiles: [ORG_PROFILE], + personalProfiles: [PERSONAL_PROFILE], + effectiveDefaultId: 'org-1', + selectedProfileId: null, + }); + // Personal context resolves the default from the personal list alone, so an + // org profile's own flag never names the default here. + const personalWithAnOrgDefaultFlag = buildProfileSelectorState({ + organizationId: undefined, + orgProfiles: [ORG_PROFILE], + personalProfiles: [PERSONAL_PROFILE], + effectiveDefaultId: 'org-1', + selectedProfileId: null, + }); + + expect(personal.rows[0]).toMatchObject({ kind: 'none', labelKey: K.noOverrideWithDefault }); + expect(personal.noOverrideLabelKey).toBe(K.noOverrideWithDefault); + expect(organization.noOverrideLabelKey).toBe(K.noOverrideWithDefault); + expect(personalWithAnOrgDefaultFlag.noOverrideLabelKey).toBe(K.noProfile); + }); + + it('resolves the selected profile and its effective-default star', () => { + const state = buildProfileSelectorState({ + organizationId: undefined, + orgProfiles: [], + personalProfiles: [PERSONAL_PROFILE, PERSONAL_DEFAULT], + effectiveDefaultId: 'user-2', + selectedProfileId: 'user-2', + }); + + expect(state.selectedProfile?.name).toBe('My Default'); + expect(state.selectedIsEffectiveDefault).toBe(true); + }); + + it('leaves the selected row empty when the id names nothing in the list', () => { + const state = buildProfileSelectorState({ + organizationId: undefined, + orgProfiles: [], + personalProfiles: [PERSONAL_PROFILE], + effectiveDefaultId: null, + selectedProfileId: 'deleted', + }); + + expect(state.selectedProfile).toBeNull(); + expect(state.selectedIsEffectiveDefault).toBe(false); + }); + + it('omits the repo-defaults entry when the caller does not offer it', () => { + const state = buildProfileSelectorState({ + organizationId: undefined, + orgProfiles: [], + personalProfiles: [], + effectiveDefaultId: null, + selectedProfileId: null, + includeRepoDefaults: false, + }); + + expect(rowKinds(state)).toEqual(['none', 'manage']); + }); +}); diff --git a/apps/mobile/src/components/agents/profile-selector-model.ts b/apps/mobile/src/components/agents/profile-selector-model.ts new file mode 100644 index 00000000000..f4d0f33d40f --- /dev/null +++ b/apps/mobile/src/components/agents/profile-selector-model.ts @@ -0,0 +1,189 @@ +import { type ProfileCountItem } from '@/lib/profile-count-labels'; + +/** + * Pure option model for the advanced-config profile selector. + * + * Ports the web `ProfileSelector` option list (`apps/web/src/components/ + * cloud-agent/ProfileSelector.tsx:84-102,173-222`) to the mobile selector + * sheet: the no-override row, an Organization Profiles group and a Personal + * Profiles group (owner icons, effective-default star, per-row var/command + * counts), then the `Manage profiles...` and `Default Profiles for Repos` + * entries. The no-override row drops the override and the effective default + * still applies, so it is labeled `Default profile` whenever the context has + * one and only says `No profile` when nothing applies at all. + * + * No React and no React Native imports: every function here is unit-tested + * directly in `profile-selector-model.test.ts`. The row owns rendering; this + * module owns the row list and the selected-row resolution. + */ + +export const PROFILE_SELECTOR_KEYS = { + /** + * The no-override row when an effective default applies: the row clears the + * pick and that default is what the session then runs on. + */ + noOverrideWithDefault: 'profiles.defaultSectionTitle', + /** The no-override row when no profile would apply: nothing is selected. */ + noProfile: 'agentChat.newSession.noProfile', + manageProfiles: 'agentChat.newSession.manageProfiles', + repoDefaults: 'profiles.repoBindings.title', + organizationProfiles: 'agentChat.newSession.organizationProfiles', + personalProfiles: 'agentChat.newSession.personalProfiles', + yourProfiles: 'agentChat.newSession.yourProfiles', +} as const; + +export type ProfileSelectorOwnerType = 'organization' | 'user'; + +/** One profile as the selector lists it. */ +export type ProfileSelectorProfile = Readonly<{ + id: string; + name: string; + varCount: number; + commandCount: number; + isDefault: boolean; + ownerType: ProfileSelectorOwnerType; +}>; + +/** + * One row of the selector sheet, in render order. A `header` row opens a + * group; `profile` rows are the choices; `none`, `manage` and `repo-defaults` + * are the fixed entries. The `none` row is the no-override choice: it clears + * the pick, so it names the effective default when the context has one. + */ +export type ProfileSelectorRow = + | Readonly<{ kind: 'none'; key: 'none'; labelKey: string }> + | Readonly<{ kind: 'header'; key: 'organization' | 'personal'; labelKey: string }> + | Readonly<{ + kind: 'profile'; + key: string; + profile: ProfileSelectorProfile; + isEffectiveDefault: boolean; + }> + | Readonly<{ kind: 'manage'; key: 'manage'; labelKey: string }> + | Readonly<{ kind: 'repo-defaults'; key: 'repo-defaults'; labelKey: string }>; + +export type ProfileSelectorState = Readonly<{ + rows: ProfileSelectorRow[]; + /** The selected profile, or null when the id names nothing in the list. */ + selectedProfile: ProfileSelectorProfile | null; + /** True when the selected profile is the effective default for the context. */ + selectedIsEffectiveDefault: boolean; + /** + * The label for the `none` row and for the closed selector row while no + * override is picked: `Default profile` when an effective default applies, + * `No profile` when nothing does. + */ + noOverrideLabelKey: string; +}>; + +/** + * The per-row count the web selector appends. Both counts render even at zero + * (web parity), in var-then-command order; the caller localizes and joins + * them. Empty when both counts are zero, so the row renders no count at all. + */ +export function profileSelectorCountItems(profile: ProfileSelectorProfile): ProfileCountItem[] { + if (profile.varCount === 0 && profile.commandCount === 0) { + return []; + } + return [ + { kind: 'vars', count: profile.varCount }, + { kind: 'commands', count: profile.commandCount }, + ]; +} + +export type BuildProfileSelectorStateInput = Readonly<{ + /** The route's organization scope; `undefined` is a personal context. */ + organizationId?: string; + /** Organization profiles; ignored in personal context. */ + orgProfiles: readonly ProfileSelectorProfile[]; + /** Personal profiles (the `list` result in personal context). */ + personalProfiles: readonly ProfileSelectorProfile[]; + /** `listCombined.effectiveDefaultId`, or the personal default's id. */ + effectiveDefaultId: string | null; + /** The profile the form currently holds as an override, or null for none. */ + selectedProfileId: string | null; + /** + * Whether to offer the `Default profiles for repos...` entry. The caller + * omits it until the repo-bindings surface exists, so no dead entry shows. + */ + includeRepoDefaults?: boolean; +}>; + +/** + * Build the selector's row list and selected row. In org context the effective + * default is `effectiveDefaultId` for both groups; in personal context it is + * the profile's own `isDefault` flag (matching the web selector). + */ +export function buildProfileSelectorState({ + organizationId, + orgProfiles, + personalProfiles, + effectiveDefaultId, + selectedProfileId, + includeRepoDefaults = true, +}: BuildProfileSelectorStateInput): ProfileSelectorState { + const isOrganization = organizationId !== undefined; + const allProfiles = [...orgProfiles, ...personalProfiles]; + const selectedProfile = allProfiles.find(profile => profile.id === selectedProfileId) ?? null; + const isEffectiveDefault = (profile: ProfileSelectorProfile): boolean => + isOrganization ? profile.id === effectiveDefaultId : profile.isDefault; + // The no-override row hands the session to the effective default, so it only + // claims `No profile` when the context resolves none. Personal context reads + // the personal list, the same one the star and the selection use. + const contextProfiles = isOrganization ? allProfiles : personalProfiles; + const noOverrideLabelKey = contextProfiles.some(profile => isEffectiveDefault(profile)) + ? PROFILE_SELECTOR_KEYS.noOverrideWithDefault + : PROFILE_SELECTOR_KEYS.noProfile; + + const rows: ProfileSelectorRow[] = [{ kind: 'none', key: 'none', labelKey: noOverrideLabelKey }]; + + if (isOrganization && orgProfiles.length > 0) { + rows.push({ + kind: 'header', + key: 'organization', + labelKey: PROFILE_SELECTOR_KEYS.organizationProfiles, + }); + for (const profile of orgProfiles) { + rows.push({ + kind: 'profile', + key: profile.id, + profile, + isEffectiveDefault: isEffectiveDefault(profile), + }); + } + } + + if (personalProfiles.length > 0) { + rows.push({ + kind: 'header', + key: 'personal', + labelKey: isOrganization + ? PROFILE_SELECTOR_KEYS.personalProfiles + : PROFILE_SELECTOR_KEYS.yourProfiles, + }); + for (const profile of personalProfiles) { + rows.push({ + kind: 'profile', + key: profile.id, + profile, + isEffectiveDefault: isEffectiveDefault(profile), + }); + } + } + + rows.push({ kind: 'manage', key: 'manage', labelKey: PROFILE_SELECTOR_KEYS.manageProfiles }); + if (includeRepoDefaults) { + rows.push({ + kind: 'repo-defaults', + key: 'repo-defaults', + labelKey: PROFILE_SELECTOR_KEYS.repoDefaults, + }); + } + + return { + rows, + selectedProfile, + selectedIsEffectiveDefault: selectedProfile !== null && isEffectiveDefault(selectedProfile), + noOverrideLabelKey, + }; +} diff --git a/apps/mobile/src/components/agents/profile-selector-row.tsx b/apps/mobile/src/components/agents/profile-selector-row.tsx new file mode 100644 index 00000000000..398a74a6c9d --- /dev/null +++ b/apps/mobile/src/components/agents/profile-selector-row.tsx @@ -0,0 +1,240 @@ +import { useState } from 'react'; +import { useTranslation } from 'react-i18next'; +import { Pressable, ScrollView, View } from 'react-native'; + +import { + profileSelectorCountItems, + type ProfileSelectorProfile, + type ProfileSelectorRow, + type ProfileSelectorState, +} from '@/components/agents/profile-selector-model'; +import { SessionPageSheet } from '@/components/agents/session-page-sheet'; +import { SheetHeader } from '@/components/sheet-header'; +import { Button } from '@/components/ui/button'; +import { + AlertCircle, + Building2, + ChevronDown, + GitBranch, + Settings, + Settings2, + Star, + User, +} from '@/components/ui/icons'; +import { RadioGroup, radioItemA11y } from '@/components/ui/radio-group'; +import { Skeleton } from '@/components/ui/skeleton'; +import { Text } from '@/components/ui/text'; +import { useThemeColors } from '@/lib/hooks/use-theme-colors'; +import { formatProfileCountItems } from '@/lib/profile-count-labels'; + +type ProfileSelectorRowProps = Readonly<{ + state: ProfileSelectorState; + /** The profiles query is in flight and holds no rows yet. */ + isLoading: boolean; + /** The profiles query failed; the row offers Retry. */ + isError: boolean; + disabled?: boolean; + onRetry: () => void; + onSelect: (profileId: string | null) => void; + onManageProfiles: () => void; + /** Opens the repo default-profile bindings; omitted until that surface exists. */ + onRepoDefaults?: () => void; +}>; + +/** + * The advanced-config profile selector: a closed row that opens the app's + * native page sheet with the profile option model. Mobile-first — the sheet is + * a tap list, never a select control that needs drag. + */ +export function ProfileSelectorRow({ + state, + isLoading, + isError, + disabled = false, + onRetry, + onSelect, + onManageProfiles, + onRepoDefaults, +}: Readonly) { + const { t } = useTranslation(); + const colors = useThemeColors(); + const [isOpen, setIsOpen] = useState(false); + + const close = () => { + setIsOpen(false); + }; + + const choose = (profileId: string | null) => { + onSelect(profileId); + close(); + }; + + const renderRow = (row: ProfileSelectorRow) => { + if (row.kind === 'header') { + return ( + + {t(row.labelKey)} + + ); + } + if (row.kind === 'none') { + return ( + { + choose(null); + }} + {...radioItemA11y({ label: t(row.labelKey), checked: state.selectedProfile === null })} + > + + {t(row.labelKey)} + + + ); + } + if (row.kind === 'manage' || row.kind === 'repo-defaults') { + // Rendered below the radio group as the fixed actions. + return null; + } + const profile = row.profile; + const counts = formatProfileCountItems(t, profileSelectorCountItems(profile)).join(', '); + return ( + { + choose(profile.id); + }} + {...radioItemA11y({ + label: profile.name, + checked: state.selectedProfile?.id === profile.id, + })} + > + + + + {profile.name} + + {row.isEffectiveDefault ? ( + + ) : null} + + {counts === '' ? null : {counts}} + + ); + }; + + if (isError) { + return ( + + + + {t('agentChat.newSession.failedToLoadProfiles')} + + + + ); + } + + if (isLoading) { + return ; + } + + const selected = state.selectedProfile; + + return ( + <> + { + setIsOpen(true); + }} + disabled={disabled} + accessibilityRole="button" + accessibilityLabel={t('agentChat.newSession.pickProfile')} + > + + {selected ? ( + + ) : ( + + )} + + {selected?.name ?? t(state.noOverrideLabelKey)} + + {selected && state.selectedIsEffectiveDefault ? ( + + ) : null} + + + + + + + + + {state.rows.map(renderRow)} + + + + { + close(); + onManageProfiles(); + }} + accessibilityRole="button" + accessibilityLabel={t('agentChat.newSession.manageProfiles')} + > + + + {t('agentChat.newSession.manageProfiles')} + + + {onRepoDefaults ? ( + { + close(); + onRepoDefaults(); + }} + accessibilityRole="button" + accessibilityLabel={t('profiles.repoBindings.title')} + > + + + {t('profiles.repoBindings.title')} + + + ) : null} + + + + + ); +} + +function OwnerIcon({ + ownerType, + color, +}: Readonly<{ ownerType: ProfileSelectorProfile['ownerType']; color: string }>) { + if (ownerType === 'organization') { + return ; + } + return ; +} diff --git a/apps/mobile/src/components/agents/save-profile-sheet.mounted.test.tsx b/apps/mobile/src/components/agents/save-profile-sheet.mounted.test.tsx new file mode 100644 index 00000000000..dba15ace76d --- /dev/null +++ b/apps/mobile/src/components/agents/save-profile-sheet.mounted.test.tsx @@ -0,0 +1,174 @@ +import { createElement, type ReactNode } from 'react'; +import { act, TestRenderer } from '@/test/renderer'; +import { beforeEach, describe, expect, it, vi } from 'vitest'; + +import '@/i18n'; +import { SaveProfileSheet, type SaveProfileSheetProps } from './save-profile-sheet'; + +vi.mock('react-native', () => ({ + ScrollView: 'ScrollView', + Switch: 'Switch', + View: 'View', +})); +vi.mock('react-native-safe-area-context', () => ({ + useSafeAreaInsets: () => ({ top: 0, bottom: 0, left: 0, right: 0 }), +})); +vi.mock('@/components/agents/session-page-sheet', () => ({ + SessionPageSheet: (props: { children?: ReactNode }) => + createElement('SessionPageSheet', null, props.children), +})); +vi.mock('@/components/sheet-header', () => ({ + SheetHeader: (props: Record) => createElement('SheetHeader', props), +})); +vi.mock('@/components/ui/form-field', () => ({ + FormField: (props: Record) => createElement('FormField', props), +})); +vi.mock('@/components/ui/text', async () => { + const React = await import('react'); + return { Text: 'Text', TextClassContext: React.createContext(undefined) }; +}); +vi.mock('@/components/ui/icons', () => ({ Lock: 'Lock' })); +vi.mock('@/lib/hooks/use-theme-colors', () => ({ + useThemeColors: () => ({ mutedForeground: '#666' }), +})); + +function defaults(overrides: Partial = {}): SaveProfileSheetProps { + return { + envVars: [ + { key: 'API_KEY', value: 'secret', isSecret: true }, + { key: 'REGION', value: 'eu', isSecret: false }, + ], + setupCommands: ['pnpm install'], + onClose: vi.fn<() => void>(), + onSave: vi.fn<() => Promise>().mockResolvedValue(true), + ...overrides, + }; +} + +function mount(props: Partial = {}) { + const ref: { current: TestRenderer.ReactTestRenderer | null } = { current: null }; + act(() => { + ref.current = TestRenderer.create(createElement(SaveProfileSheet, defaults(props))); + }); + const renderer = ref.current; + if (renderer === null) { + throw new Error('the save sheet did not render'); + } + return renderer; +} + +function field(renderer: TestRenderer.ReactTestRenderer, label: string) { + const node = renderer.root + .findAllByType('FormField' as never) + .find(instance => instance.props.label === label); + if (!node) { + throw new Error(`no FormField labelled ${label}`); + } + return node; +} + +function header(renderer: TestRenderer.ReactTestRenderer) { + return renderer.root.findByType('SheetHeader' as never); +} + +function texts(renderer: TestRenderer.ReactTestRenderer): string[] { + return renderer.root + .findAllByType('Text' as never) + .flatMap(node => node.children) + .filter((child): child is string => typeof child === 'string'); +} + +async function submit(renderer: TestRenderer.ReactTestRenderer) { + await act(async () => { + (header(renderer).props.onDone as () => void)(); + await Promise.resolve(); + }); +} + +beforeEach(() => { + (globalThis as { IS_REACT_ACT_ENVIRONMENT?: boolean }).IS_REACT_ACT_ENVIRONMENT = true; +}); + +describe('SaveProfileSheet', () => { + it('shows the summary counts and the secrets note', () => { + const renderer = mount(); + + expect(header(renderer).props.title).toBe('Save as Profile'); + expect(texts(renderer)).toContain('2 environment variables · 1 setup commands'); + expect(texts(renderer)).toContain('Secrets are encrypted before storage.'); + expect(renderer.root.findByType('Lock' as never)).toBeTruthy(); + }); + + it('hides the secrets note when no variable is a secret', () => { + const renderer = mount({ envVars: [{ key: 'REGION', value: 'eu', isSecret: false }] }); + + expect(texts(renderer)).not.toContain('Secrets are encrypted before storage.'); + expect(renderer.root.findAllByType('Lock' as never)).toHaveLength(0); + }); + + it('refuses an empty name with the required error and creates nothing', async () => { + const onSave = vi.fn<() => Promise>().mockResolvedValue(true); + const renderer = mount({ onSave }); + + await submit(renderer); + + expect(onSave).not.toHaveBeenCalled(); + expect(field(renderer, 'Profile name').props.error).toBe('required'); + }); + + it('refuses an over-long name with the matching error', async () => { + const onSave = vi.fn<() => Promise>().mockResolvedValue(true); + const renderer = mount({ onSave }); + + act(() => { + (field(renderer, 'Profile name').props.onChangeText as (value: string) => void)( + 'x'.repeat(101) + ); + }); + await submit(renderer); + + expect(onSave).not.toHaveBeenCalled(); + expect(field(renderer, 'Profile name').props.error).toBe('Profile name is too long'); + }); + + it('saves the trimmed fields and closes on success', async () => { + const onSave = vi.fn<() => Promise>().mockResolvedValue(true); + const onClose = vi.fn<() => void>(); + const renderer = mount({ onSave, onClose }); + + act(() => { + (field(renderer, 'Profile name').props.onChangeText as (value: string) => void)(' My AWS '); + (field(renderer, 'Profile description').props.onChangeText as (value: string) => void)( + ' eu-west ' + ); + (renderer.root.findByType('Switch' as never).props.onValueChange as (value: boolean) => void)( + true + ); + }); + await submit(renderer); + + expect(onSave).toHaveBeenCalledWith({ + name: 'My AWS', + description: 'eu-west', + setAsDefault: true, + }); + expect(onClose).toHaveBeenCalledTimes(1); + }); + + it('keeps the entered values and the sheet open when the save fails', async () => { + const onSave = vi.fn<() => Promise>().mockResolvedValue(false); + const onClose = vi.fn<() => void>(); + const renderer = mount({ onSave, onClose }); + + act(() => { + (field(renderer, 'Profile name').props.onChangeText as (value: string) => void)('Retry me'); + }); + await submit(renderer); + + expect(onSave).toHaveBeenCalledTimes(1); + expect(onClose).not.toHaveBeenCalled(); + // The sheet is still mounted with the same field instance, so the typed + // name is still on screen. + expect(field(renderer, 'Profile name')).toBeTruthy(); + }); +}); diff --git a/apps/mobile/src/components/agents/save-profile-sheet.tsx b/apps/mobile/src/components/agents/save-profile-sheet.tsx new file mode 100644 index 00000000000..6858a893eb2 --- /dev/null +++ b/apps/mobile/src/components/agents/save-profile-sheet.tsx @@ -0,0 +1,183 @@ +import { useRef, useState } from 'react'; +import { useTranslation } from 'react-i18next'; +import { ScrollView, Switch, View } from 'react-native'; +import { useSafeAreaInsets } from 'react-native-safe-area-context'; + +import { SessionPageSheet } from '@/components/agents/session-page-sheet'; +import { SheetHeader } from '@/components/sheet-header'; +import { FormField } from '@/components/ui/form-field'; +import { Lock } from '@/components/ui/icons'; +import { Text } from '@/components/ui/text'; +import { validateProfileName } from '@/lib/agent-profile-forms'; +import { useThemeColors } from '@/lib/hooks/use-theme-colors'; + +/** Server bound: `ProfileDescriptionSchema`'s `z.string().max(500)`. */ +const PROFILE_DESCRIPTION_MAX_LENGTH = 500; + +/** One manual environment variable as the sheet saves it. */ +type SaveProfileVar = Readonly<{ key: string; value: string; isSecret: boolean }>; + +export type SaveProfileSubmission = Readonly<{ + name: string; + description: string; + setAsDefault: boolean; +}>; + +export type SaveProfileSheetProps = Readonly<{ + envVars: readonly SaveProfileVar[]; + setupCommands: readonly string[]; + onClose: () => void; + /** + * Persist the profile. The caller owns create/setVar/setCommands/ + * setAsDefault and returns `true` on success (the sheet closes) or `false` + * on failure (the sheet stays open with the entered values). + */ + onSave: (submission: SaveProfileSubmission) => Promise; +}>; + +/** + * The `Save as Profile` sheet. Mounted only while open, so every open seeds + * fresh fields. Fields are uncontrolled (refs) per the app's iOS text-input + * rule; a refused name or an over-long description shows the matching inline + * error and persists nothing. A failed save keeps the name, description and + * rows on screen — the sheet never resets on error. + */ +export function SaveProfileSheet({ + envVars, + setupCommands, + onClose, + onSave, +}: Readonly) { + const { t } = useTranslation(); + const colors = useThemeColors(); + const insets = useSafeAreaInsets(); + const nameRef = useRef(''); + const descriptionRef = useRef(''); + const [setAsDefault, setSetAsDefault] = useState(false); + const [nameError, setNameError] = useState(null); + const [descriptionError, setDescriptionError] = useState(null); + const [isSubmitting, setIsSubmitting] = useState(false); + + const hasSecrets = envVars.some(envVar => envVar.isSecret); + + const submit = async () => { + const name = nameRef.current.trim(); + const description = descriptionRef.current.trim(); + + const nameIssue = validateProfileName(name); + if (nameIssue !== null) { + setNameError( + nameIssue === 'empty' ? t('common.required') : t('agentChat.newSession.nameTooLong') + ); + return; + } + if (description.length > PROFILE_DESCRIPTION_MAX_LENGTH) { + setDescriptionError(t('agentChat.newSession.descriptionTooLong')); + return; + } + setNameError(null); + setDescriptionError(null); + + setIsSubmitting(true); + try { + const saved = await onSave({ name, description, setAsDefault }); + if (saved) { + onClose(); + } + } finally { + setIsSubmitting(false); + } + }; + + return ( + + { + void submit(); + }} + onCancel={onClose} + doneLabel={t('common.save')} + disabled={isSubmitting} + topInset="ios-page-sheet" + /> + + {t('agentChat.newSession.saveProfileDescription')} + { + nameRef.current = value; + if (nameError !== null) { + setNameError(validateProfileName(value.trim()) === null ? null : nameError); + } + }} + /> + { + descriptionRef.current = value; + if ( + descriptionError !== null && + value.trim().length <= PROFILE_DESCRIPTION_MAX_LENGTH + ) { + setDescriptionError(null); + } + }} + /> + + + + {t('profiles.setAsDefault')} + + + + + + + {t('agentChat.newSession.profileSummary', { + vars: envVars.length, + commands: setupCommands.length, + })} + + {hasSecrets ? ( + + + + {t('agentChat.newSession.secretsEncrypted')} + + + ) : null} + + + + + ); +} diff --git a/apps/mobile/src/components/agents/session-detail-content.test.ts b/apps/mobile/src/components/agents/session-detail-content.test.ts index 672015d4391..42187560c3a 100644 --- a/apps/mobile/src/components/agents/session-detail-content.test.ts +++ b/apps/mobile/src/components/agents/session-detail-content.test.ts @@ -104,6 +104,16 @@ vi.mock('@/components/agents/user-web-connection-provider', () => ({ // Keep the actual detail/card/sheet/header callbacks and SDK. Replace native // rendering and unrelated composer, account, model-picker, and router dependencies. const navigationRoutes = vi.hoisted(() => ['session-detail']); +// The personal `agentProfiles.list` rows the header's active-profile chip +// reads; tests set it before mounting to drive the chip's presence. +const profileRowsState = vi.hoisted(() => ({ + personal: [] as unknown[], + combined: { + orgProfiles: [] as unknown[], + personalProfiles: [] as unknown[], + effectiveDefaultId: null as string | null, + }, +})); const routerSetParams = vi.hoisted(() => vi.fn()); const handoffAdvertiserCalls = vi.hoisted(() => ({ props: [] as { anchorMessageId?: string | null }[], @@ -196,6 +206,7 @@ vi.mock('@/components/ui/icons', () => ({ Link2: 'Link2', Loader2: 'Loader2', MessageSquare: 'MessageSquare', + SlidersHorizontal: 'SlidersHorizontal', })); vi.mock('@/components/ui/directional-icons', () => ({ DirectionalChevronLeft: 'ChevronLeft', @@ -483,6 +494,25 @@ vi.mock('@/lib/trpc', () => ({ }), }, }, + // The session header's active-profile chip reads the context profiles; the + // hoisted rows let a test resolve a default, and the empty default keeps + // the chip absent so the existing header assertions hold. + agentProfiles: { + list: { + queryOptions: () => ({ + queryKey: ['agentProfiles', 'list'], + queryFn: () => profileRowsState.personal, + initialData: profileRowsState.personal, + }), + }, + listCombined: { + queryOptions: () => ({ + queryKey: ['agentProfiles', 'listCombined'], + queryFn: () => profileRowsState.combined, + initialData: profileRowsState.combined, + }), + }, + }, // The real context sheet resolves the "running on" row from the connected // CLI instances; the row is inert here, so an empty instance list keeps the // sheet rendering without a network read. @@ -669,6 +699,8 @@ function transcriptKeys(renderer: ReactTestRenderer): string[] { beforeEach(() => { navigationRoutes.splice(0, navigationRoutes.length, 'session-detail'); + profileRowsState.personal = []; + profileRowsState.combined = { orgProfiles: [], personalProfiles: [], effectiveDefaultId: null }; openRenameModal.mockClear(); renameModalState.isOpen = false; renameModalState.initialValue = ''; @@ -699,6 +731,9 @@ type MountDetailsOptions = { cachedTitle?: string; /** The route's `?at=` param the screen mounts with. */ resumeAt?: string | null; + sessionOrganizationId?: string; + /** The profile id the session row recorded, as `fetchSession` reports it. */ + sessionProfileId?: string | null; }; async function mountDetails( @@ -776,7 +811,8 @@ async function mountDetails( kiloSessionId: id, cloudAgentSessionId: null, title: sessionTitleOverride ?? `Root ${id}`, - organizationId: null, + organizationId: options.sessionOrganizationId ?? null, + profileId: options.sessionProfileId ?? null, gitUrl: null, gitBranch: null, mode: null, @@ -980,6 +1016,126 @@ describe('SessionDetailContent display scope', () => { }); }); +describe('session detail active-profile indicator', () => { + const PROFILE_ROW = { + id: 'p1', + name: 'Production', + description: null, + isDefault: true, + createdAt: '2026-01-01T00:00:00.000Z', + updatedAt: '2026-01-01T00:00:00.000Z', + varCount: 2, + commandCount: 1, + mcpServerCount: 1, + skillCount: 1, + agentCount: 1, + kiloCommandCount: 1, + }; + + function findChip(renderer: ReactTestRenderer) { + return renderer.root.findAll( + node => + typeof node.props.accessibilityLabel === 'string' && + node.props.accessibilityLabel.startsWith(i18n.t('agentChat.newSession.profileActive')) + ); + } + + it('shows the chip for the session context effective default and opens its editor', async () => { + profileRowsState.personal = [PROFILE_ROW]; + const { renderer } = await mountDetails(); + + await waitFor(() => findChip(renderer).length > 0); + const [chip] = findChip(renderer); + if (chip === undefined) { + throw new Error('the active-profile chip did not render'); + } + expect(chip.props.accessibilityLabel).toContain('Production'); + act(() => { + (chip.props.onPress as () => void)(); + }); + expect(navigationRoutes.at(-1)).toBe('/(app)/(tabs)/(3_profile)/profiles/p1'); + }); + + it.each(['user', 'organization'] as const)( + 'opens a %s default from an organization session in its owner scope', + async ownerType => { + const profile = { ...PROFILE_ROW, ownerType }; + profileRowsState.combined = { + personalProfiles: ownerType === 'user' ? [profile] : [], + orgProfiles: ownerType === 'organization' ? [profile] : [], + effectiveDefaultId: profile.id, + }; + const { renderer } = await mountDetails([], { + sessionOrganizationId: 'org-a', + displayScope: { organizationId: 'org-a', isResolved: true }, + }); + await waitFor(() => findChip(renderer).length > 0); + const [chip] = findChip(renderer); + if (!chip) { + throw new Error('the active-profile chip did not render'); + } + act(() => { + (chip.props.onPress as () => void)(); + }); + expect(navigationRoutes.at(-1)).toBe( + `/(app)/(tabs)/(3_profile)/profiles/p1${ownerType === 'organization' ? '?organizationId=org-a' : ''}` + ); + } + ); + + it('renders no chip when the context has no profiles', async () => { + const { renderer } = await mountDetails(); + + expect(findChip(renderer)).toHaveLength(0); + }); + + it('names the profile the session recorded, not the context effective default', async () => { + profileRowsState.personal = [ + { ...PROFILE_ROW, id: 'p-default', name: 'Default', isDefault: true }, + { ...PROFILE_ROW, id: 'p-recorded', name: 'Recorded', isDefault: false }, + ]; + const { renderer } = await mountDetails([], { sessionProfileId: 'p-recorded' }); + + await waitFor(() => findChip(renderer).length > 0); + const [chip] = findChip(renderer); + if (chip === undefined) { + throw new Error('the active-profile chip did not render'); + } + expect(chip.props.accessibilityLabel).toContain('Recorded'); + expect(chip.props.accessibilityLabel).not.toContain('Default'); + act(() => { + (chip.props.onPress as () => void)(); + }); + expect(navigationRoutes.at(-1)).toBe('/(app)/(tabs)/(3_profile)/profiles/p-recorded'); + }); + + it('falls back to the effective default only when the session recorded no profile', async () => { + profileRowsState.personal = [ + { ...PROFILE_ROW, id: 'p-default', name: 'Default', isDefault: true }, + ]; + const { renderer } = await mountDetails([], { sessionProfileId: null }); + + await waitFor(() => findChip(renderer).length > 0); + const [chip] = findChip(renderer); + if (chip === undefined) { + throw new Error('the active-profile chip did not render'); + } + expect(chip.props.accessibilityLabel).toContain('Default'); + }); + + it('renders no chip when the session profile id no longer resolves', async () => { + profileRowsState.personal = [ + { ...PROFILE_ROW, id: 'p-default', name: 'Default', isDefault: true }, + ]; + const view = await mountDetails([], { sessionProfileId: 'p-deleted' }); + + // Wait for the session metadata read so the assertion is not merely the + // pre-load window; a fallback to the context default would surface here. + await waitFor(() => view.store.get(view.manager.atoms.fetchedSessionData) !== null); + expect(findChip(view.renderer)).toHaveLength(0); + }); +}); + describe('SessionDetailContent header title', () => { // The title shares its row with a 44pt context pill and a copy action, so on // a narrow phone the title column is a fraction of the row width. The header diff --git a/apps/mobile/src/components/agents/session-detail-content.tsx b/apps/mobile/src/components/agents/session-detail-content.tsx index bb9f8490f4e..0b719b2b46d 100644 --- a/apps/mobile/src/components/agents/session-detail-content.tsx +++ b/apps/mobile/src/components/agents/session-detail-content.tsx @@ -115,6 +115,11 @@ import { import { useInteractionHandlers } from '@/components/agents/use-interaction-handlers'; import { useSessionAutoApprove } from '@/components/agents/use-session-auto-approve'; import { useSessionConfigSync } from '@/components/agents/use-session-config-sync'; +import { ActiveProfileIndicator } from '@/components/agents/active-profile-indicator'; +import { buildActiveProfileIndicatorState } from '@/components/agents/active-profile-indicator-model'; +import { useEffectiveAgentProfile } from '@/components/agents/use-effective-agent-profile'; +import { getProfileOverviewPath } from '@/lib/profile-agent-navigation'; +import { profileOrganizationId } from '@/components/profiles/profile-owner-model'; import { SessionSkeletonMessages } from '@/components/agents/session-detail-skeleton'; import { SESSION_HEADER_TITLE_LINES } from '@/components/agents/session-header'; import { @@ -494,6 +499,53 @@ export function SessionDetailContent({ const organizationId = fetchedData?.organizationId ?? undefined; + // The session's active-profile chip: the profile this session runs on. It + // resolves from the session's own recorded `profileId`, so an explicit + // override or a repository-bound profile — not the context's current + // effective default — names it. Only a session that recorded none (created + // before profile recording, or one whose create resolved no profile) falls + // back to the effective default. A recorded id that no longer resolves shows + // no chip rather than naming a different profile. Tapping opens that + // profile's editor. + const { + allProfiles: sessionProfiles, + effectiveDefaultId, + isLoading: isSessionProfileLoading, + isError: isSessionProfileError, + } = useEffectiveAgentProfile(organizationId); + // `fetchSession` is the only source of the session's own profile id, and the + // atom can still hold the previous session's row. Until the CURRENT session's + // read resolves, the chip stays hidden rather than briefly naming the context + // default for a session that recorded a different profile. + const sessionDataLoaded = fetchedData?.kiloSessionId === sessionId; + const recordedSessionProfileId = sessionDataLoaded ? (fetchedData.profileId ?? null) : null; + const activeSessionProfileId = sessionDataLoaded + ? (recordedSessionProfileId ?? effectiveDefaultId) + : null; + const sessionProfile = + activeSessionProfileId === null + ? null + : (sessionProfiles.find(profile => profile.id === activeSessionProfileId) ?? null); + const sessionProfileIndicatorState = buildActiveProfileIndicatorState({ + selectedProfileName: sessionProfile?.name ?? null, + repoBoundProfileName: null, + hasManualEnvVars: false, + hasManualSetupCommands: false, + hasSelectedProfileId: sessionProfile !== null, + isProfilesLoading: isSessionProfileLoading || !sessionDataLoaded, + hasProfileError: isSessionProfileError, + }); + const openSessionProfileEditor = () => { + if (sessionProfile) { + router.push( + getProfileOverviewPath( + sessionProfile.id, + profileOrganizationId(organizationId, sessionProfile) + ) + ); + } + }; + const presenceSessionId = resolveLoadedCliSessionPresenceId( sessionId, fetchedData?.kiloSessionId @@ -2049,6 +2101,19 @@ export function SessionDetailContent({ } : {})} /> + {sessionProfileIndicatorState ? ( + + + + ) : null} {sessionGoal !== null || hasPrRow ? ( ({ vi.mock('@/lib/trpc', () => ({ useTRPC: () => ({ moderation: { reportContent: { mutationOptions: (options: unknown) => options } }, + // The session header's active-profile chip reads the context profiles; the + // mock `useQuery` below settles an empty list, so the chip stays absent. + agentProfiles: { + list: { queryOptions: (input: unknown) => ({ queryKey: ['profiles', 'list'], input }) }, + listCombined: { + queryOptions: (input: unknown) => ({ queryKey: ['profiles', 'listCombined'], input }), + }, + }, }), trpcClient: { cloudAgentNext: { @@ -156,7 +164,16 @@ vi.mock('react-native', () => ({ ScrollView: 'ScrollView', View: 'View', })); -vi.mock('@tanstack/react-query', () => ({ useMutation: () => ({ mutate: vi.fn() }) })); +vi.mock('@tanstack/react-query', () => ({ + useMutation: () => ({ mutate: vi.fn() }), + // The profile hook's queries settle empty, so the header shows no chip. + useQuery: () => ({ + data: [], + isPending: false, + isError: false, + refetch: vi.fn(), + }), +})); vi.mock('@/lib/hooks/use-theme-colors', () => ({ useThemeColors: () => ({ background: '#000', mutedForeground: '#999' }), })); diff --git a/apps/mobile/src/components/agents/session-profile-picker-model.test.ts b/apps/mobile/src/components/agents/session-profile-picker-model.test.ts new file mode 100644 index 00000000000..11f6b1bed12 --- /dev/null +++ b/apps/mobile/src/components/agents/session-profile-picker-model.test.ts @@ -0,0 +1,192 @@ +import { describe, expect, it } from 'vitest'; + +import { + resolveSessionProfileLayerIds, + resolveSessionProfilePicker, + sessionProfileCountItems, + type SessionProfilePickerProfile, +} from './session-profile-picker-model'; + +function profile( + overrides: Partial & { id: string; name: string } +): SessionProfilePickerProfile { + return { + varCount: 0, + mcpServerCount: 0, + skillCount: 0, + kiloCommandCount: 0, + ...overrides, + }; +} + +describe('resolveSessionProfileLayerIds', () => { + it('applies the effective default as the top layer when nothing is picked', () => { + expect( + resolveSessionProfileLayerIds({ + repoBindingProfileId: null, + effectiveDefaultProfileId: 'default', + explicitOverrideProfileId: null, + }) + ).toEqual({ baseProfileId: null, topProfileId: 'default', topSource: 'default' }); + }); + + it('lets the explicit pick replace the effective default in the top slot', () => { + expect( + resolveSessionProfileLayerIds({ + repoBindingProfileId: null, + effectiveDefaultProfileId: 'default', + explicitOverrideProfileId: 'picked', + }) + ).toEqual({ baseProfileId: null, topProfileId: 'picked', topSource: 'explicit' }); + }); + + it('keeps the repo binding as the base and layers the pick on top', () => { + expect( + resolveSessionProfileLayerIds({ + repoBindingProfileId: 'repo', + effectiveDefaultProfileId: 'default', + explicitOverrideProfileId: 'picked', + }) + ).toEqual({ baseProfileId: 'repo', topProfileId: 'picked', topSource: 'explicit' }); + }); + + it('drops the top layer when it duplicates the repo base', () => { + expect( + resolveSessionProfileLayerIds({ + repoBindingProfileId: 'same', + effectiveDefaultProfileId: 'same', + explicitOverrideProfileId: 'same', + }) + ).toEqual({ baseProfileId: 'same', topProfileId: null, topSource: null }); + }); + + it('resolves no layers when nothing applies', () => { + expect( + resolveSessionProfileLayerIds({ + repoBindingProfileId: null, + effectiveDefaultProfileId: null, + explicitOverrideProfileId: null, + }) + ).toEqual({ baseProfileId: null, topProfileId: null, topSource: null }); + }); +}); + +describe('sessionProfileCountItems', () => { + it('resolves the non-zero counts in web order', () => { + expect( + sessionProfileCountItems( + profile({ + id: 'p', + name: 'P', + varCount: 3, + mcpServerCount: 1, + skillCount: 2, + kiloCommandCount: 4, + }) + ) + ).toEqual([ + { kind: 'vars', count: 3 }, + { kind: 'mcp', count: 1 }, + { kind: 'skills', count: 2 }, + { kind: 'commands', count: 4 }, + ]); + }); + + it('omits zero counts, so an all-zero profile resolves no items', () => { + expect(sessionProfileCountItems(profile({ id: 'p', name: 'P', skillCount: 2 }))).toEqual([ + { kind: 'skills', count: 2 }, + ]); + expect(sessionProfileCountItems(profile({ id: 'p', name: 'P' }))).toEqual([]); + }); +}); + +describe('resolveSessionProfilePicker', () => { + const base = profile({ id: 'repo', name: 'Repo profile', varCount: 1 }); + const fallback = profile({ id: 'default', name: 'Default profile', mcpServerCount: 2 }); + const picked = profile({ + id: 'picked', + name: 'Picked profile', + skillCount: 3, + kiloCommandCount: 1, + }); + + it('shows the effective default with no override and no repo base', () => { + const state = resolveSessionProfilePicker({ + profiles: [fallback, picked], + repoBindingProfileId: null, + effectiveDefaultProfileId: 'default', + selectedOverrideProfileId: null, + }); + + expect(state.chipName).toBe('Default profile'); + expect(state.chipCountItems).toEqual([{ kind: 'mcp', count: 2 }]); + expect(state.hasOverride).toBe(false); + expect(state.topSource).toBe('default'); + expect(state.selectedProfileId).toBe('default'); + expect(state.overrideNeedsAttention).toBe(false); + expect(state.candidates.map(candidate => candidate.id)).toEqual(['default', 'picked']); + }); + + it('shows the picked override and offers it as selected', () => { + const state = resolveSessionProfilePicker({ + profiles: [fallback, picked], + repoBindingProfileId: null, + effectiveDefaultProfileId: 'default', + selectedOverrideProfileId: 'picked', + }); + + expect(state.chipName).toBe('Picked profile'); + expect(state.hasOverride).toBe(true); + expect(state.topSource).toBe('explicit'); + expect(state.selectedProfileId).toBe('picked'); + expect(state.overrideNeedsAttention).toBe(false); + }); + + it('layers the pick on the repo base and excludes the base from candidates', () => { + const state = resolveSessionProfilePicker({ + profiles: [base, fallback, picked], + repoBindingProfileId: 'repo', + effectiveDefaultProfileId: 'default', + selectedOverrideProfileId: 'picked', + }); + + expect(state.baseProfile?.id).toBe('repo'); + expect(state.topProfile?.id).toBe('picked'); + // vars take the larger layer; MCP/skills/cmds add across the pair. The + // override replaces the default, so the default's MCP count is not added. + expect(state.chipCountItems).toEqual([ + { kind: 'vars', count: 1 }, + { kind: 'skills', count: 3 }, + { kind: 'commands', count: 1 }, + ]); + expect(state.candidates.map(candidate => candidate.id)).toEqual(['default', 'picked']); + }); + + it('flags attention and drops the stale id when the override no longer resolves', () => { + const state = resolveSessionProfilePicker({ + profiles: [fallback], + repoBindingProfileId: null, + effectiveDefaultProfileId: 'default', + selectedOverrideProfileId: 'deleted', + }); + + expect(state.overrideNeedsAttention).toBe(true); + expect(state.selectedProfileId).toBeNull(); + expect(state.hasOverride).toBe(false); + }); + + it('resolves the empty state: no profiles, no default, no override', () => { + const state = resolveSessionProfilePicker({ + profiles: [], + repoBindingProfileId: null, + effectiveDefaultProfileId: null, + selectedOverrideProfileId: null, + }); + + expect(state.chipName).toBeNull(); + expect(state.chipCountItems).toEqual([]); + expect(state.selectedProfileId).toBeNull(); + expect(state.overrideNeedsAttention).toBe(false); + expect(state.candidates).toEqual([]); + }); +}); diff --git a/apps/mobile/src/components/agents/session-profile-picker-model.ts b/apps/mobile/src/components/agents/session-profile-picker-model.ts new file mode 100644 index 00000000000..ea79f0a7312 --- /dev/null +++ b/apps/mobile/src/components/agents/session-profile-picker-model.ts @@ -0,0 +1,182 @@ +import { type ProfileCountItem } from '@/lib/profile-count-labels'; + +/** + * Pure resolution of the session-start profile picker: which profile is shown, + * which one is submitted, and which rows the picker offers. + * + * Mirrors the shared `resolveProfileLayers` in + * `@kilocode/cloud-agent-profile` — the same two-layer merge the server and the + * web picker use. Mobile cannot import that package directly (it is a + * dev-dependency of `@kilocode/trpc`, and the mobile tRPC entry is type-only), + * so the rules are restated here with the same outcomes: a repo binding always + * claims the base slot, the explicit pick fills the top slot and replaces the + * effective default, and a top that duplicates the base is dropped. + */ + +type ProfileLayerSource = 'repo-binding' | 'default' | 'explicit'; + +/** One profile as the picker and the chip render it. */ +export type SessionProfilePickerProfile = { + id: string; + name: string; + varCount: number; + mcpServerCount: number; + skillCount: number; + kiloCommandCount: number; +}; + +export type SessionProfilePickerInput< + P extends SessionProfilePickerProfile = SessionProfilePickerProfile, +> = { + profiles: readonly P[]; + /** The profile bound to the target repo, when the caller knows it. */ + repoBindingProfileId: string | null; + /** The effective default for the context (personal default beats org default). */ + effectiveDefaultProfileId: string | null; + /** The profile the user picked for this task, or null for the default. */ + selectedOverrideProfileId: string | null; +}; + +export type SessionProfilePickerState< + P extends SessionProfilePickerProfile = SessionProfilePickerProfile, +> = { + /** The repo-bound profile, when present. */ + baseProfile: P | null; + /** The applied top layer: the explicit pick, else the effective default. */ + topProfile: P | null; + /** The active profile's name, or null when no profile applies. */ + chipName: string | null; + /** + * The active layer pair's non-zero counts, in web order (vars, MCP, skills, + * commands). The caller localizes them; empty when nothing is configured. + */ + chipCountItems: ProfileCountItem[]; + /** Where the top layer came from, or null when none applies. */ + topSource: ProfileLayerSource | null; + /** True when the top layer is the user's explicit pick. */ + hasOverride: boolean; + /** The id the create call should submit: the valid override, else the default. */ + selectedProfileId: string | null; + /** + * True when the user picked an override that no longer resolves to a + * profile. The screen shows attention and submits with no override id. + */ + overrideNeedsAttention: boolean; + /** Rows the picker offers: every profile except the base. */ + candidates: P[]; +}; + +/** The three candidate ids the layer resolver weighs. */ +export type SessionProfileLayerIdsInput = { + repoBindingProfileId: string | null; + effectiveDefaultProfileId: string | null; + explicitOverrideProfileId: string | null; +}; + +/** The resolved two-layer stack, as ids. */ +export type SessionProfileLayerIds = { + baseProfileId: string | null; + topProfileId: string | null; + topSource: ProfileLayerSource | null; +}; + +/** + * Resolve the repo base and the top layer from the three candidate ids, + * dropping a top that duplicates the base (the shared resolver's rule). + */ +export function resolveSessionProfileLayerIds({ + repoBindingProfileId, + effectiveDefaultProfileId, + explicitOverrideProfileId, +}: Readonly): SessionProfileLayerIds { + const baseProfileId = repoBindingProfileId; + let topProfileId: string | null = null; + let topSource: ProfileLayerSource | null = null; + if (explicitOverrideProfileId) { + topProfileId = explicitOverrideProfileId; + topSource = 'explicit'; + } else if (effectiveDefaultProfileId) { + topProfileId = effectiveDefaultProfileId; + topSource = 'default'; + } + if (topProfileId && baseProfileId && topProfileId === baseProfileId) { + topProfileId = null; + topSource = null; + } + return { baseProfileId, topProfileId, topSource }; +} + +/** + * The non-zero counts a picker row shows, in web order (vars, MCP, skills, + * commands), omitting zero counts. The caller localizes them, so the unit + * words live in the catalog. + */ +export function sessionProfileCountItems(profile: SessionProfilePickerProfile): ProfileCountItem[] { + const items: (ProfileCountItem | null)[] = [ + profile.varCount > 0 ? { kind: 'vars', count: profile.varCount } : null, + profile.mcpServerCount > 0 ? { kind: 'mcp', count: profile.mcpServerCount } : null, + profile.skillCount > 0 ? { kind: 'skills', count: profile.skillCount } : null, + profile.kiloCommandCount > 0 ? { kind: 'commands', count: profile.kiloCommandCount } : null, + ]; + return items.filter((item): item is ProfileCountItem => item !== null); +} + +/** + * The active pair's counts: vars take the larger layer (they merge), MCP, + * skills and commands add. Matches the web chip's arithmetic. Zero counts are + * omitted; the caller localizes the rest. + */ +function activeCountItems( + base: SessionProfilePickerProfile | null, + top: SessionProfilePickerProfile | null +): ProfileCountItem[] { + const vars = Math.max(base?.varCount ?? 0, top?.varCount ?? 0); + const mcps = (base?.mcpServerCount ?? 0) + (top?.mcpServerCount ?? 0); + const skills = (base?.skillCount ?? 0) + (top?.skillCount ?? 0); + const cmds = (base?.kiloCommandCount ?? 0) + (top?.kiloCommandCount ?? 0); + const items: (ProfileCountItem | null)[] = [ + vars > 0 ? { kind: 'vars', count: vars } : null, + mcps > 0 ? { kind: 'mcp', count: mcps } : null, + skills > 0 ? { kind: 'skills', count: skills } : null, + cmds > 0 ? { kind: 'commands', count: cmds } : null, + ]; + return items.filter((item): item is ProfileCountItem => item !== null); +} + +export function resolveSessionProfilePicker

( + input: SessionProfilePickerInput

+): SessionProfilePickerState

{ + const { profiles } = input; + const { baseProfileId, topProfileId, topSource } = resolveSessionProfileLayerIds({ + repoBindingProfileId: input.repoBindingProfileId, + effectiveDefaultProfileId: input.effectiveDefaultProfileId, + explicitOverrideProfileId: input.selectedOverrideProfileId, + }); + + const baseProfile = + baseProfileId === null + ? null + : (profiles.find(profile => profile.id === baseProfileId) ?? null); + const topProfile = + topProfileId === null ? null : (profiles.find(profile => profile.id === topProfileId) ?? null); + + // The user picked an id the list does not contain: never submit it again. + const overrideNeedsAttention = + input.selectedOverrideProfileId !== null && topProfile === null && topSource === 'explicit'; + + const selectedProfileId = overrideNeedsAttention + ? null + : (topProfile?.id ?? baseProfile?.id ?? null); + + return { + baseProfile, + topProfile, + chipName: topProfile?.name ?? baseProfile?.name ?? null, + chipCountItems: activeCountItems(baseProfile, topProfile), + topSource, + hasOverride: topSource === 'explicit' && topProfile !== null, + selectedProfileId, + overrideNeedsAttention, + candidates: profiles.filter(profile => profile.id !== baseProfile?.id), + }; +} diff --git a/apps/mobile/src/components/agents/use-effective-agent-profile.test.ts b/apps/mobile/src/components/agents/use-effective-agent-profile.test.ts index 85f91aa1265..50ccee6ba8a 100644 --- a/apps/mobile/src/components/agents/use-effective-agent-profile.test.ts +++ b/apps/mobile/src/components/agents/use-effective-agent-profile.test.ts @@ -116,20 +116,24 @@ type ProfileResult = ReturnType; function ProfileHarness({ organizationId, + overrideProfileId, resultRef, }: { organizationId?: string; + overrideProfileId?: string | null; resultRef: { current: ProfileResult | null }; }) { - const result = useEffectiveAgentProfile(organizationId); + const result = useEffectiveAgentProfile(organizationId, overrideProfileId); resultRef.current = result; return null; } -function mountProfile(organizationId?: string): ProfileResult { +function mountProfile(organizationId?: string, overrideProfileId?: string | null): ProfileResult { const resultRef: { current: ProfileResult | null } = { current: null }; act(() => { - TestRenderer.create(React.createElement(ProfileHarness, { organizationId, resultRef })); + TestRenderer.create( + React.createElement(ProfileHarness, { organizationId, overrideProfileId, resultRef }) + ); }); const result = resultRef.current; if (result === null) { @@ -203,4 +207,77 @@ describe('useEffectiveAgentProfile', () => { expect(result.profile).toBeNull(); expect(result.profileId).toBeNull(); }); + + it('submits the picked override and exposes it as the selection', () => { + // eslint-disable-next-line @typescript-eslint/consistent-type-assertions -- partial query result; the hook reads only data/isPending/isError/refetch + vi.mocked(useQuery).mockReturnValue({ + data: [ + profile({ id: 'default', name: 'Default', isDefault: true }), + profile({ id: 'picked', name: 'Picked' }), + ], + isPending: false, + isError: false, + refetch: vi.fn(), + } as never); + + const result = mountProfile(undefined, 'picked'); + expect(result.selectedProfileId).toBe('picked'); + expect(result.profileId).toBe('picked'); + expect(result.profile?.name).toBe('Picked'); + expect(result.hasOverride).toBe(true); + expect(result.overrideNeedsAttention).toBe(false); + }); + + it('drops a stale override and flags attention instead of submitting it', () => { + // eslint-disable-next-line @typescript-eslint/consistent-type-assertions -- partial query result; the hook reads only data/isPending/isError/refetch + vi.mocked(useQuery).mockReturnValue({ + data: [profile({ id: 'default', isDefault: true })], + isPending: false, + isError: false, + refetch: vi.fn(), + } as never); + + const result = mountProfile(undefined, 'deleted'); + // The picker can still see which id was picked, but nothing stale is sent. + expect(result.selectedProfileId).toBe('deleted'); + expect(result.profileId).toBeNull(); + expect(result.profile).toBeNull(); + expect(result.hasOverride).toBe(false); + expect(result.overrideNeedsAttention).toBe(true); + }); + + it('exposes every profile and the effective default for the picker', () => { + // eslint-disable-next-line @typescript-eslint/consistent-type-assertions -- partial query result; the hook reads only data/isPending/isError/refetch + vi.mocked(useQuery).mockReturnValue({ + data: [profile({ id: 'a' }), profile({ id: 'default', isDefault: true })], + isPending: false, + isError: false, + refetch: vi.fn(), + } as never); + + const result = mountProfile(); + expect(result.effectiveDefaultId).toBe('default'); + expect(result.allProfiles.map(p => p.id)).toEqual(['a', 'default']); + expect(result.profileId).toBe('default'); + expect(result.hasOverride).toBe(false); + }); + + it('composes org and personal profiles for the org picker', () => { + // eslint-disable-next-line @typescript-eslint/consistent-type-assertions -- partial query result; the hook reads only data/isPending/isError/refetch + vi.mocked(useQuery).mockReturnValue({ + data: { + orgProfiles: [withOwner(profile({ id: 'org' }), 'organization')], + personalProfiles: [withOwner(profile({ id: 'personal', isDefault: true }), 'user')], + effectiveDefaultId: 'personal', + }, + isPending: false, + isError: false, + refetch: vi.fn(), + } as never); + + const result = mountProfile('org-1'); + expect(result.effectiveDefaultId).toBe('personal'); + expect(result.allProfiles.map(p => p.id)).toEqual(['org', 'personal']); + expect(result.profileId).toBe('personal'); + }); }); diff --git a/apps/mobile/src/components/agents/use-effective-agent-profile.ts b/apps/mobile/src/components/agents/use-effective-agent-profile.ts index 0f687cf3109..1b65374cfb5 100644 --- a/apps/mobile/src/components/agents/use-effective-agent-profile.ts +++ b/apps/mobile/src/components/agents/use-effective-agent-profile.ts @@ -2,28 +2,33 @@ import { useMemo } from 'react'; import { useQuery } from '@tanstack/react-query'; import { type inferRouterOutputs, type MobileRouter } from '@kilocode/trpc/mobile'; +import { + resolveSessionProfilePicker, + type SessionProfilePickerProfile, +} from '@/components/agents/session-profile-picker-model'; import { useTRPC } from '@/lib/trpc'; +import { type AgentProfileListItem } from '@/lib/hooks/agent-profile-types'; type ProfileSummary = inferRouterOutputs['agentProfiles']['list'][number]; type CombinedProfiles = inferRouterOutputs['agentProfiles']['listCombined']; /** The read-only capability view the new-session form renders. */ -export type EffectiveAgentProfile = { - id: string; - name: string; +export type EffectiveAgentProfile = SessionProfilePickerProfile & { commandCount: number; - mcpServerCount: number; - skillCount: number; agentCount: number; + ownerType?: AgentProfileListItem['ownerType']; }; -function toEffective(profile: ProfileSummary): EffectiveAgentProfile { +function toEffective(profile: AgentProfileListItem): EffectiveAgentProfile { return { id: profile.id, name: profile.name, - commandCount: profile.commandCount, + ownerType: profile.ownerType, + varCount: profile.varCount, mcpServerCount: profile.mcpServerCount, skillCount: profile.skillCount, + kiloCommandCount: profile.kiloCommandCount, + commandCount: profile.commandCount, agentCount: profile.agentCount, }; } @@ -57,8 +62,17 @@ export function resolveCombinedDefault(combined: CombinedProfiles): ProfileSumma * profile. Personal context reads `agentProfiles.list`; org context reads * `agentProfiles.listCombined` (whose `effectiveDefaultId` already encodes * "personal default > org default > none"). + * + * `overrideProfileId` is the profile the user picked for this task. When it is + * null the effective default resolves as before; when it names a profile it + * replaces the default in the top layer; when it no longer resolves the hook + * reports `overrideNeedsAttention` and returns no id to submit, so a stale pick + * is never sent. */ -export function useEffectiveAgentProfile(organizationId?: string) { +export function useEffectiveAgentProfile( + organizationId?: string, + overrideProfileId?: string | null +) { const trpc = useTRPC(); const personal = useQuery({ @@ -74,23 +88,54 @@ export function useEffectiveAgentProfile(organizationId?: string) { const isOrg = organizationId !== undefined; const query = isOrg ? combined : personal; - const profile = useMemo(() => { - // A failed profile query must not leak a cached profile into the form: - // the error row is shown and Start submits with no effective profile id. - if (query.isError) { - return null; - } + const { allProfiles, effectiveDefaultId } = useMemo(() => { if (isOrg) { - const resolved = combined.data ? resolveCombinedDefault(combined.data) : null; - return resolved ? toEffective(resolved) : null; + const data = combined.data; + if (!data || !Array.isArray(data.orgProfiles) || !Array.isArray(data.personalProfiles)) { + return { allProfiles: [] as EffectiveAgentProfile[], effectiveDefaultId: null }; + } + return { + allProfiles: [...data.orgProfiles, ...data.personalProfiles].map(profile => + toEffective(profile) + ), + effectiveDefaultId: data.effectiveDefaultId, + }; } - const resolved = personal.data ? resolvePersonalDefault(personal.data) : null; - return resolved ? toEffective(resolved) : null; - }, [isOrg, personal.data, combined.data, query.isError]); + const profiles = personal.data ?? []; + return { + allProfiles: profiles.map(profile => toEffective(profile)), + effectiveDefaultId: profiles.find(profile => profile.isDefault)?.id ?? null, + }; + }, [isOrg, personal.data, combined.data]); + + const picker = useMemo( + () => + resolveSessionProfilePicker({ + profiles: allProfiles, + // The server resolves a repo's bound profile from the submitted + // repository at session creation (profile-session-config), so the + // mobile picker shows only the default and override layers. + repoBindingProfileId: null, + effectiveDefaultProfileId: effectiveDefaultId, + selectedOverrideProfileId: overrideProfileId ?? null, + }), + [allProfiles, effectiveDefaultId, overrideProfileId] + ); + + // A failed profile query must not leak a cached profile into the form: the + // error row is shown and Start submits with no effective profile id. + const profile = query.isError ? null : (picker.topProfile ?? picker.baseProfile); + const profileId = query.isError ? null : picker.selectedProfileId; return { profile, - profileId: profile?.id ?? null, + profileId, + allProfiles, + effectiveDefaultId, + // The user's explicit pick, echoed for the picker's selected row. + selectedProfileId: overrideProfileId ?? null, + hasOverride: query.isError ? false : picker.hasOverride, + overrideNeedsAttention: query.isError ? false : picker.overrideNeedsAttention, // Gate on `isPending`, not `isLoading`: in React Query v5 `isLoading` is // `isPending && isFetching`, so a paused (offline) first fetch has // `isLoading: false` while still unsettled. `isPending` stays true until diff --git a/apps/mobile/src/components/agents/use-new-session-creator.test.ts b/apps/mobile/src/components/agents/use-new-session-creator.test.ts index 99b40325a2b..2a19da4dcfc 100644 --- a/apps/mobile/src/components/agents/use-new-session-creator.test.ts +++ b/apps/mobile/src/components/agents/use-new-session-creator.test.ts @@ -336,6 +336,8 @@ function runCreator(args: { selectedRepository?: NewSessionRepository | null; autoCommit?: boolean; profileId?: string | null; + manualEnvVars?: Record; + setupCommands?: string[]; }): CreatorResult { const reactInternals = React as typeof React & ReactInternals; const refs: { current: unknown }[] = []; @@ -384,6 +386,8 @@ function runCreator(args: { variant: args.variant ?? 'v1', autoCommit: args.autoCommit ?? false, profileId: args.profileId, + manualEnvVars: args.manualEnvVars, + setupCommands: args.setupCommands, }); } finally { reactInternals.__CLIENT_INTERNALS_DO_NOT_USE_OR_WARN_USERS_THEY_CANNOT_UPGRADE.H = @@ -1077,6 +1081,36 @@ describe('useNewSessionCreator profileId', () => { }); }); +describe('useNewSessionCreator manual configuration', () => { + it('sends the manual env vars and setup commands with the create', async () => { + prepareSessionMutate.mockResolvedValue(sessionResult()); + const creator = runCreator({ + manualEnvVars: { API_KEY: 'sk-1' }, + setupCommands: ['pnpm install'], + }); + + creator.promptRef.current = 'hello'; + await creator.createSessionFromDraft(); + + expect(prepareSessionMutate.mock.calls[0]?.[0]).toMatchObject({ + envVars: { API_KEY: 'sk-1' }, + setupCommands: ['pnpm install'], + }); + }); + + it('omits the manual config fields when the draft is empty', async () => { + prepareSessionMutate.mockResolvedValue(sessionResult()); + const creator = runCreator({ manualEnvVars: {}, setupCommands: [] }); + + creator.promptRef.current = 'hello'; + await creator.createSessionFromDraft(); + + const payload = prepareSessionMutate.mock.calls[0]?.[0] as Record; + expect(payload).not.toHaveProperty('envVars'); + expect(payload).not.toHaveProperty('setupCommands'); + }); +}); + describe('useNewSessionCreator autoCommit', () => { it('sends autoCommit false (Leave changes) by default', async () => { prepareSessionMutate.mockResolvedValue(sessionResult()); diff --git a/apps/mobile/src/components/agents/use-new-session-creator.ts b/apps/mobile/src/components/agents/use-new-session-creator.ts index 54edc0dcb02..2fa958ed720 100644 --- a/apps/mobile/src/components/agents/use-new-session-creator.ts +++ b/apps/mobile/src/components/agents/use-new-session-creator.ts @@ -47,6 +47,10 @@ type UseNewSessionCreatorInput = { autoCommit: boolean; /** Effective environment profile id; omitted from the create body when unset. */ profileId?: string | null; + /** Manual env vars from Advanced Configuration; omitted from the body when empty. */ + manualEnvVars?: Record; + /** Manual setup commands from Advanced Configuration; omitted from the body when empty. */ + setupCommands?: string[]; }; type UseNewSessionCreatorResult = { @@ -88,6 +92,8 @@ export function useNewSessionCreator({ variant, autoCommit, profileId, + manualEnvVars, + setupCommands, }: UseNewSessionCreatorInput): UseNewSessionCreatorResult { const router = useStackSafeReplace(); const queryClient = useQueryClient(); @@ -188,6 +194,8 @@ export function useNewSessionCreator({ model, variant, profileId, + envVars: manualEnvVars, + setupCommands, autoCommit, attachments: uploaded.wire, organizationId, @@ -282,6 +290,8 @@ export function useNewSessionCreator({ autoCommit, organizationId, profileId, + manualEnvVars, + setupCommands, queryClient, trpc, router, diff --git a/apps/mobile/src/components/profile-screen.queries.mounted.test.tsx b/apps/mobile/src/components/profile-screen.queries.mounted.test.tsx index 8685c30f024..7307adaf548 100644 --- a/apps/mobile/src/components/profile-screen.queries.mounted.test.tsx +++ b/apps/mobile/src/components/profile-screen.queries.mounted.test.tsx @@ -334,7 +334,6 @@ describe('ProfileScreen deferred queries', () => { nodeCount(renderer.root, 'Skeleton') === 0 ); - expect(nodeCount(renderer.root, 'Skeleton')).toBe(0); expect(nodeCountWithChildren(renderer.root, 'Text', 'Linked accounts')).toBe(0); expectAlignedContent(renderer.root); diff --git a/apps/mobile/src/components/profile-screen.tsx b/apps/mobile/src/components/profile-screen.tsx index 5592f5fda37..86fd9d3f724 100644 --- a/apps/mobile/src/components/profile-screen.tsx +++ b/apps/mobile/src/components/profile-screen.tsx @@ -40,6 +40,7 @@ import { useOrganization } from '@/lib/organization-context'; import { getCodeReviewerProfilePath, getProfileAgentScope, + getProfilesPath, getPrReviewEntryPath, } from '@/lib/profile-agent-navigation'; import { useScreenSideInsets } from '@/lib/screen-insets'; @@ -190,13 +191,22 @@ export function ProfileScreen() { hue="honey" className="rounded-lg bg-secondary px-3" disabled={!agentScope} - last onPress={() => { if (agentScope) { router.push(getSecurityAgentPath(agentScope)); } }} /> + { + router.push(getProfilesPath()); + }} + /> {/* PR Review */} diff --git a/apps/mobile/src/components/profiles/agent-form-sheet.mounted.test.tsx b/apps/mobile/src/components/profiles/agent-form-sheet.mounted.test.tsx new file mode 100644 index 00000000000..952e2d4617b --- /dev/null +++ b/apps/mobile/src/components/profiles/agent-form-sheet.mounted.test.tsx @@ -0,0 +1,213 @@ +import { createElement } from 'react'; +import { beforeEach, describe, expect, it, vi } from 'vitest'; + +import '@/i18n'; +import { AgentFormSheet } from '@/components/profiles/agent-form-sheet'; +import { type AgentSource } from '@/components/profiles/profile-agents-model'; +import { act, type ReactTestInstance } from '@/test/renderer'; +import { renderWithProviders } from '@/test/render-with-providers'; + +const h = vi.hoisted(() => ({ + useAvailableModels: vi.fn(), + onSave: vi.fn(), + onClose: vi.fn(), +})); + +vi.mock('@/lib/hooks/use-available-models', () => ({ + useAvailableModels: (...args: unknown[]) => h.useAvailableModels(...args), + thinkingEffortLabel: (variant: string) => variant, +})); +vi.mock('react-native', () => ({ + View: 'View', + ScrollView: 'ScrollView', + Pressable: 'Pressable', + Switch: 'Switch', +})); +vi.mock('react-native-safe-area-context', () => ({ + useSafeAreaInsets: () => ({ top: 0, bottom: 0, left: 0, right: 0 }), +})); +vi.mock('@/components/agents/session-page-sheet', () => ({ SessionPageSheet: 'SessionPageSheet' })); +vi.mock('@/components/sheet-header', () => ({ SheetHeader: 'SheetHeader' })); +vi.mock('@/components/ui/form-field', () => ({ FormField: 'FormField' })); +vi.mock('@/components/ui/text', () => ({ Text: 'Text' })); + +function agentSource(overrides: Partial = {}): AgentSource { + return { + id: 'agent-1', + slug: 'reviewer', + name: 'Reviewer', + config: { + description: 'Reviews code', + mode: 'primary', + model: 'anthropic/claude', + prompt: 'You review', + }, + ...overrides, + }; +} + +function findAll(root: ReactTestInstance, type: string): ReactTestInstance[] { + return root.findAll(node => typeof node.type === 'string' && node.type === type); +} + +function findField(root: ReactTestInstance, label: string): ReactTestInstance { + const field = findAll(root, 'FormField').find(node => node.props.label === label); + if (!field) { + throw new Error(`field ${label} was not rendered`); + } + return field; +} + +function findOne(root: ReactTestInstance, type: string): ReactTestInstance { + const node = findAll(root, type)[0]; + if (!node) { + throw new Error(`${type} was not rendered`); + } + return node; +} + +function changeText(root: ReactTestInstance, label: string, value: string): void { + act(() => { + (findField(root, label).props as { onChangeText: (v: string) => void }).onChangeText(value); + }); +} + +function pressVariant(root: ReactTestInstance, variant: string): void { + const pressable = findAll(root, 'Pressable').find(node => + String(node.props.accessibilityLabel).startsWith(`${variant} thinking effort`) + ); + if (!pressable) { + throw new Error(`variant ${variant} was not rendered`); + } + act(() => { + (pressable.props as { onPress: () => void }).onPress(); + }); +} + +function pressDone(root: ReactTestInstance): void { + act(() => { + (findOne(root, 'SheetHeader').props as { onDone: () => void }).onDone(); + }); +} + +function modelOption(id: string, variants: string[]) { + return { id, name: id, variants, isPreferred: false }; +} + +// eslint-disable-next-line typescript-eslint/promise-function-async -- returning the harness promise unchanged +function mountSheet(agent: AgentSource | null) { + return renderWithProviders( + createElement(AgentFormSheet, { + agent, + organizationId: undefined, + isSaving: false, + onClose: () => { + h.onClose(); + }, + onSave: payload => { + h.onSave(payload); + }, + }) + ); +} + +describe('AgentFormSheet parity fields', () => { + beforeEach(() => { + vi.clearAllMocks(); + h.useAvailableModels.mockReturnValue({ models: [] }); + }); + + it('renders the sampling labels alongside the model field', async () => { + const { renderer, unmount } = await mountSheet(null); + + expect(findField(renderer.root, 'Max steps')).toBeTruthy(); + expect(findField(renderer.root, 'Temperature')).toBeTruthy(); + expect(findField(renderer.root, 'top_p')).toBeTruthy(); + + unmount(); + }); + + it('happy: lists the model variants and saves the picked effort', async () => { + h.useAvailableModels.mockReturnValue({ + models: [modelOption('anthropic/claude', ['low', 'high'])], + }); + + const { renderer, unmount } = await mountSheet(agentSource()); + + pressVariant(renderer.root, 'high'); + pressDone(renderer.root); + + expect(h.onSave).toHaveBeenCalledTimes(1); + expect(h.onSave.mock.calls[0]?.[0]).toMatchObject({ + slug: 'reviewer', + name: 'Reviewer', + config: { model: 'anthropic/claude', variant: 'high' }, + }); + + unmount(); + }); + + it('hides the effort control when the model list errors and still saves the typed values', async () => { + h.useAvailableModels.mockReturnValue({ models: [], isError: true }); + + const { renderer, unmount } = await mountSheet(agentSource()); + + expect( + findAll(renderer.root, 'Pressable').some(node => + String(node.props.accessibilityLabel).includes('thinking effort') + ) + ).toBe(false); + + changeText(renderer.root, 'Max steps', '50'); + changeText(renderer.root, 'Temperature', '0.2'); + changeText(renderer.root, 'top_p', '0.95'); + pressDone(renderer.root); + + expect(h.onSave).toHaveBeenCalledTimes(1); + expect(h.onSave.mock.calls[0]?.[0]).toMatchObject({ + slug: 'reviewer', + config: { steps: 50, temperature: 0.2, top_p: 0.95 }, + }); + + unmount(); + }); + + it('hides the effort control while the model list loads and still saves the typed values', async () => { + h.useAvailableModels.mockReturnValue({ models: [], isLoading: true }); + + const { renderer, unmount } = await mountSheet(agentSource()); + + expect( + findAll(renderer.root, 'Pressable').some(node => + String(node.props.accessibilityLabel).includes('thinking effort') + ) + ).toBe(false); + + changeText(renderer.root, 'Max steps', '40'); + pressDone(renderer.root); + + expect(h.onSave).toHaveBeenCalledTimes(1); + expect(h.onSave.mock.calls[0]?.[0]).toMatchObject({ + slug: 'reviewer', + config: { steps: 40 }, + }); + + unmount(); + }); + + it('hides the effort control when the typed model has no variants', async () => { + h.useAvailableModels.mockReturnValue({ + models: [modelOption('anthropic/claude', [])], + }); + + const { renderer, unmount } = await mountSheet(agentSource()); + + expect( + findAll(renderer.root, 'Pressable').some(node => + String(node.props.accessibilityLabel).includes('thinking effort') + ) + ).toBe(false); + + unmount(); + }); +}); diff --git a/apps/mobile/src/components/profiles/agent-form-sheet.tsx b/apps/mobile/src/components/profiles/agent-form-sheet.tsx new file mode 100644 index 00000000000..d5af0be1987 --- /dev/null +++ b/apps/mobile/src/components/profiles/agent-form-sheet.tsx @@ -0,0 +1,304 @@ +import { useRef, useState } from 'react'; +import { useTranslation } from 'react-i18next'; +import { Pressable, ScrollView, Switch, View } from 'react-native'; +import { useSafeAreaInsets } from 'react-native-safe-area-context'; + +import { SessionPageSheet } from '@/components/agents/session-page-sheet'; +import { AgentVariantPicker } from '@/components/profiles/agent-variant-picker'; +import { + type AgentFormError, + type AgentFormState, + type AgentSource, + type AgentVisibility, + buildAgentPayload, + initialAgentFormState, + PERMISSION_TOOLS, + validateAgentForm, +} from '@/components/profiles/profile-agents-model'; +import { SheetHeader } from '@/components/sheet-header'; +import { FormField } from '@/components/ui/form-field'; +import { Text } from '@/components/ui/text'; +import { useAvailableModels } from '@/lib/hooks/use-available-models'; +import { cn } from '@/lib/utils'; + +/** Catalog key for each refused field, so the message lands under the input at fault. */ +const ERROR_KEYS = { + 'slug-required': 'profiles.agents.slugRequired', + 'slug-invalid': 'profiles.agents.slugInvalid', + 'slug-conflict': 'profiles.agents.slugConflict', + 'name-required': 'profiles.agents.nameRequired', +} satisfies Record; + +const VISIBILITY_OPTIONS: readonly { value: AgentVisibility; labelKey: string }[] = [ + { value: 'primary', labelKey: 'profiles.agents.visibilityPrimary' }, + { value: 'subagent', labelKey: 'profiles.agents.visibilitySubagent' }, + { value: 'all', labelKey: 'profiles.agents.visibilityAll' }, +]; + +type AgentFormSheetProps = Readonly<{ + agent: AgentSource | null; + organizationId?: string; + isSaving: boolean; + onClose: () => void; + onSave: (payload: { slug: string; name: string; config: Record }) => void; +}>; + +/** + * The add/edit agent sheet. Mounted only while open, so every open seeds fresh + * fields from the agent it edits. Text fields are uncontrolled (refs), per the + * app's iOS text-input rule; visibility and the tool toggles are state. + * + * Save validates through `validateAgentForm`; a refused input shows the + * matching `profiles.agents.*` message under the field at fault. Every tool is + * allowed unless its switch is off, which writes `deny` for that tool only. + * + * Sampling (max steps, temperature, top_p) mirrors the web editor. The effort + * variant control appears only when the typed model's catalogue entry lists + * variants — the same `availableVariants` gate web uses — and never blocks a + * save: an unavailable model list only hides the control. + */ +export function AgentFormSheet({ + agent, + organizationId, + isSaving, + onClose, + onSave, +}: Readonly) { + const { t } = useTranslation(); + const insets = useSafeAreaInsets(); + const initial = initialAgentFormState(agent ?? undefined); + const slugRef = useRef(initial.slug); + const nameRef = useRef(initial.name); + const descriptionRef = useRef(initial.description); + const promptRef = useRef(initial.prompt); + const modelRef = useRef(initial.model); + const stepsRef = useRef(initial.steps); + const temperatureRef = useRef(initial.temperature); + const topPRef = useRef(initial.topP); + // The typed model is text in `modelRef`; this state exists only to derive the + // variant options the catalogue publishes for it. + const [modelValue, setModelValue] = useState(initial.model); + const [visibility, setVisibility] = useState(initial.visibility); + const [variant, setVariant] = useState(initial.variant); + const [disabledTools, setDisabledTools] = useState(initial.disabledTools); + const [error, setError] = useState(null); + const { models } = useAvailableModels(organizationId); + + const variantsFor = (model: string): readonly string[] => + models.find(option => option.id === model.trim())?.variants ?? []; + const variants = variantsFor(modelValue); + + const submit = () => { + const state: AgentFormState = { + slug: slugRef.current, + name: nameRef.current, + description: descriptionRef.current, + prompt: promptRef.current, + visibility, + model: modelRef.current, + steps: stepsRef.current, + temperature: temperatureRef.current, + topP: topPRef.current, + variant, + disabledTools, + }; + const problem = validateAgentForm(state); + if (problem !== null) { + setError(problem); + return; + } + setError(null); + onSave(buildAgentPayload(state, agent?.config)); + }; + + return ( + + + + { + slugRef.current = value.toLowerCase().replaceAll(/[^a-z0-9-]/g, ''); + setError(null); + }} + /> + { + nameRef.current = value; + setError(null); + }} + /> + { + descriptionRef.current = value; + }} + /> + { + promptRef.current = value; + }} + /> + { + modelRef.current = value; + setModelValue(value); + // Drop a stale effort value whenever the new model does not offer it. + const next = variantsFor(value); + setVariant(current => (next.includes(current) ? current : '')); + }} + /> + + {variants.length > 0 ? ( + + ) : null} + + { + stepsRef.current = value; + }} + /> + { + temperatureRef.current = value; + }} + /> + { + topPRef.current = value; + }} + /> + + + + {t('profiles.agents.visibility')} + + + {VISIBILITY_OPTIONS.map(option => { + const selected = visibility === option.value; + return ( + { + setVisibility(option.value); + }} + > + + {t(option.labelKey)} + + + ); + })} + + + + + {t('profiles.agents.tools')} + + {PERMISSION_TOOLS.map(tool => { + const enabled = !disabledTools.includes(tool); + return ( + + + {tool} + + { + setDisabledTools(current => + next ? current.filter(item => item !== tool) : [...current, tool] + ); + }} + /> + + ); + })} + + + + + + ); +} diff --git a/apps/mobile/src/components/profiles/agent-variant-picker.tsx b/apps/mobile/src/components/profiles/agent-variant-picker.tsx new file mode 100644 index 00000000000..ff2a1878ad7 --- /dev/null +++ b/apps/mobile/src/components/profiles/agent-variant-picker.tsx @@ -0,0 +1,73 @@ +import { useTranslation } from 'react-i18next'; +import { Pressable, View } from 'react-native'; + +import { Text } from '@/components/ui/text'; +import { thinkingEffortLabel } from '@/lib/hooks/use-available-models'; +import { cn } from '@/lib/utils'; + +type AgentVariantPickerProps = Readonly<{ + /** The catalogue variants published for the currently typed model. */ + variants: readonly string[]; + value: string; + disabled?: boolean; + onChange: (variant: string) => void; +}>; + +/** + * Tap list of a model's thinking-effort variants, mirroring the web editor's + * `VariantCombobox`. The caller renders it only when the typed model publishes + * variants, so an unavailable model list never shows an empty control. + */ +export function AgentVariantPicker({ + variants, + value, + disabled, + onChange, +}: Readonly) { + const { t } = useTranslation(); + return ( + + + {t('agentChat.modelSelector.thinkingEffort')} + + + {variants.map(option => { + const selected = value === option; + return ( + { + onChange(option); + }} + > + + {thinkingEffortLabel(option)} + + + ); + })} + + + ); +} diff --git a/apps/mobile/src/components/profiles/kilo-command-form-sheet.mounted.test.tsx b/apps/mobile/src/components/profiles/kilo-command-form-sheet.mounted.test.tsx new file mode 100644 index 00000000000..8b4203e457c --- /dev/null +++ b/apps/mobile/src/components/profiles/kilo-command-form-sheet.mounted.test.tsx @@ -0,0 +1,57 @@ +import { createElement } from 'react'; +import { describe, expect, it, vi } from 'vitest'; + +import '@/i18n'; +import { KiloCommandFormSheet } from '@/components/profiles/kilo-command-form-sheet'; +import { type ReactTestInstance } from '@/test/renderer'; +import { renderWithProviders } from '@/test/render-with-providers'; + +vi.mock('react-native', () => ({ + View: 'View', + ScrollView: 'ScrollView', + Switch: 'Switch', +})); +vi.mock('react-native-safe-area-context', () => ({ + useSafeAreaInsets: () => ({ top: 0, bottom: 0, left: 0, right: 0 }), +})); +vi.mock('@/components/agents/session-page-sheet', () => ({ SessionPageSheet: 'SessionPageSheet' })); +vi.mock('@/components/sheet-header', () => ({ SheetHeader: 'SheetHeader' })); +vi.mock('@/components/ui/form-field', () => ({ FormField: 'FormField' })); +vi.mock('@/components/ui/text', () => ({ Text: 'Text' })); + +function findAll(root: ReactTestInstance, type: string): ReactTestInstance[] { + return root.findAll(node => typeof node.type === 'string' && node.type === type); +} + +function findField(root: ReactTestInstance, label: string): ReactTestInstance { + const field = findAll(root, 'FormField').find(node => node.props.label === label); + if (!field) { + throw new Error(`field ${label} was not rendered`); + } + return field; +} + +// eslint-disable-next-line typescript-eslint/promise-function-async -- returning the harness promise unchanged +function mountSheet() { + return renderWithProviders( + createElement(KiloCommandFormSheet, { + command: null, + isSaving: false, + onClose: () => undefined, + onCreate: () => undefined, + onUpdate: () => undefined, + }) + ); +} + +describe('KiloCommandFormSheet server bounds', () => { + it('caps the name, description, and template fields at the server limits', async () => { + const { renderer, unmount } = await mountSheet(); + + expect(findField(renderer.root, 'Command name').props.maxLength).toBe(50); + expect(findField(renderer.root, 'Command description').props.maxLength).toBe(2000); + expect(findField(renderer.root, 'Template').props.maxLength).toBe(100_000); + + unmount(); + }); +}); diff --git a/apps/mobile/src/components/profiles/kilo-command-form-sheet.tsx b/apps/mobile/src/components/profiles/kilo-command-form-sheet.tsx new file mode 100644 index 00000000000..4a955a4a36a --- /dev/null +++ b/apps/mobile/src/components/profiles/kilo-command-form-sheet.tsx @@ -0,0 +1,193 @@ +import { useRef, useState } from 'react'; +import { useTranslation } from 'react-i18next'; +import { ScrollView, Switch, View } from 'react-native'; +import { useSafeAreaInsets } from 'react-native-safe-area-context'; + +import { SessionPageSheet } from '@/components/agents/session-page-sheet'; +import { + buildKiloCommandCreatePayload, + buildKiloCommandUpdatePayload, + initialKiloCommandFormState, + type KiloCommandFormError, + type KiloCommandFormState, + type KiloCommandSource, + MAX_KILO_COMMAND_DESCRIPTION_LENGTH, + MAX_KILO_COMMAND_NAME_LENGTH, + MAX_KILO_COMMAND_TEMPLATE_LENGTH, + validateKiloCommandForm, +} from '@/components/profiles/profile-kilo-commands-model'; +import { SheetHeader } from '@/components/sheet-header'; +import { FormField } from '@/components/ui/form-field'; +import { Text } from '@/components/ui/text'; + +/** Catalog key for each refused field, so the message lands under the input at fault. */ +const ERROR_KEYS = { + 'name-required': 'profiles.slashCommands.nameRequired', + 'name-invalid': 'profiles.slashCommands.nameInvalid', + 'name-conflict': 'profiles.slashCommands.nameConflict', + 'template-required': 'profiles.slashCommands.templateRequired', +} satisfies Record; + +/** The create payload the form hands back for a new command. */ +export type KiloCommandSubmission = ReturnType; + +type KiloCommandFormSheetProps = Readonly<{ + command: KiloCommandSource | null; + isSaving: boolean; + onClose: () => void; + onCreate: (payload: KiloCommandSubmission) => void; + onUpdate: (payload: ReturnType) => void; +}>; + +/** + * The add/edit slash command sheet. Mounted only while open, so every open + * seeds fresh fields from the command it edits. Text fields are uncontrolled + * (refs), per the app's iOS text-input rule; the subtask flag is state. + * + * Save validates through `validateKiloCommandForm`; a refused input shows the + * matching `profiles.slashCommands.*` message under the field at fault. Create + * omits empty optional fields so the DB default applies; update sends `null` so + * an emptied field clears. + */ +export function KiloCommandFormSheet({ + command, + isSaving, + onClose, + onCreate, + onUpdate, +}: KiloCommandFormSheetProps) { + const { t } = useTranslation(); + const insets = useSafeAreaInsets(); + const initial = initialKiloCommandFormState(command ?? undefined); + const nameRef = useRef(initial.name); + const descriptionRef = useRef(initial.description); + const templateRef = useRef(initial.template); + const agentRef = useRef(initial.agent); + const modelRef = useRef(initial.model); + const [subtask, setSubtask] = useState(initial.subtask); + const [error, setError] = useState(null); + + const submit = () => { + const state: KiloCommandFormState = { + name: nameRef.current, + description: descriptionRef.current, + template: templateRef.current, + agent: agentRef.current, + model: modelRef.current, + subtask, + }; + const problem = validateKiloCommandForm(state); + if (problem !== null) { + setError(problem); + return; + } + setError(null); + if (command === null) { + onCreate(buildKiloCommandCreatePayload(state)); + } else { + onUpdate(buildKiloCommandUpdatePayload(state)); + } + }; + + return ( + + + + { + nameRef.current = value.toLowerCase().replaceAll(/[^a-z0-9-]/g, ''); + setError(null); + }} + /> + { + descriptionRef.current = value; + }} + /> + { + templateRef.current = value; + setError(null); + }} + /> + { + agentRef.current = value; + }} + /> + { + modelRef.current = value; + }} + /> + + + {t('profiles.slashCommands.subtask')} + + + + + + + ); +} diff --git a/apps/mobile/src/components/profiles/kilo-command-row.tsx b/apps/mobile/src/components/profiles/kilo-command-row.tsx new file mode 100644 index 00000000000..1f4cb893206 --- /dev/null +++ b/apps/mobile/src/components/profiles/kilo-command-row.tsx @@ -0,0 +1,126 @@ +import { useTranslation } from 'react-i18next'; +import { Pressable, Switch, View } from 'react-native'; + +import { ChevronDown, ChevronUp, Pencil, Terminal, Trash2 } from '@/components/ui/icons'; +import { Text } from '@/components/ui/text'; +import { useThemeColors } from '@/lib/hooks/use-theme-colors'; +import { cn } from '@/lib/utils'; + +type KiloCommandRowProps = Readonly<{ + name: string; + description: string; + template: string; + subtask: boolean; + agent: string; + model: string; + enabled: boolean; + isFirst: boolean; + isLast: boolean; + onToggle: (enabled: boolean) => void; + onMoveUp: () => void; + onMoveDown: () => void; + onEdit: () => void; + onDelete: () => void; +}>; + +/** + * One slash command row: the `/name`, optional subtask/agent/model metadata, the + * template preview, the enabled switch, and up/down/edit/delete controls. The + * row carries no container `accessibilityLabel`, so the switch's label stays the + * only element a screen reader matches by the command's name. + */ +export function KiloCommandRowView({ + name, + description, + template, + subtask, + agent, + model, + enabled, + isFirst, + isLast, + onToggle, + onMoveUp, + onMoveDown, + onEdit, + onDelete, +}: KiloCommandRowProps) { + const { t } = useTranslation(); + const colors = useThemeColors(); + const controlClass = (disabled: boolean) => + cn('h-11 w-11 items-center justify-center active:opacity-70', disabled && 'opacity-40'); + return ( + + + + + + + {`/${name}`} + + {subtask ? ( + + {t('profiles.slashCommands.subtask')} + + ) : null} + + {description.length > 0 ? ( + + {description} + + ) : null} + + {template} + + {agent.length > 0 || model.length > 0 ? ( + + {[agent.length > 0 ? `${t('profiles.slashCommands.agent')}: ${agent}` : '', model] + .filter(Boolean) + .join(' ')} + + ) : null} + + + {enabled ? t('common.enabled') : t('common.disabled')} + + + + + + + + + + + + + + + + + + + ); +} diff --git a/apps/mobile/src/components/profiles/mcp-form-sheet.tsx b/apps/mobile/src/components/profiles/mcp-form-sheet.tsx new file mode 100644 index 00000000000..e39d3006d96 --- /dev/null +++ b/apps/mobile/src/components/profiles/mcp-form-sheet.tsx @@ -0,0 +1,222 @@ +import { useRef, useState } from 'react'; +import { useTranslation } from 'react-i18next'; +import { ScrollView, Switch, View } from 'react-native'; +import { useSafeAreaInsets } from 'react-native-safe-area-context'; + +import { SessionPageSheet } from '@/components/agents/session-page-sheet'; +import { + buildMcpServerPayload, + initialMcpFormState, + type McpFormError, + type McpFormType, + type McpServerPayload, + type McpServerSource, + validateMcpForm, +} from '@/components/profiles/profile-mcp-model'; +import { SheetHeader } from '@/components/sheet-header'; +import { FormField } from '@/components/ui/form-field'; +import { SegmentedControl } from '@/components/ui/segmented-control'; +import { Text } from '@/components/ui/text'; + +/** Catalog key for each refused field, so the message lands under the input at fault. */ +const ERROR_KEYS = { + 'name-required': 'profiles.mcp.nameRequired', + 'name-invalid': 'profiles.mcp.nameInvalid', + 'command-required': 'profiles.mcp.commandRequired', + 'command-too-long': 'profiles.mcp.commandTooLong', + 'url-required': 'profiles.mcp.urlRequired', + 'url-invalid': 'profiles.mcp.urlInvalid', + 'url-too-long': 'profiles.mcp.urlTooLong', + 'timeout-invalid': 'profiles.mcp.timeoutInvalid', + 'json-invalid': 'profiles.mcp.jsonInvalid', + 'record-too-large': 'profiles.mcp.jsonTooLarge', +} satisfies Record; + +function errorKeyFor( + t: (key: string) => string, + error: McpFormError | null, + match: readonly McpFormError[] +): string | undefined { + return error !== null && match.includes(error) ? t(ERROR_KEYS[error]) : undefined; +} + +type McpFormSheetProps = Readonly<{ + /** The server being edited, or `null` when adding one. */ + server: McpServerSource | null; + isSaving: boolean; + onClose: () => void; + /** + * Persist the validated payload. The caller owns create-vs-update and closes + * the sheet on success; on failure it returns with the sheet still open. + */ + onSave: (payload: McpServerPayload) => void; +}>; + +/** + * The add/edit MCP server sheet. Mounted only while open, so every open seeds + * fresh fields from the server it edits. Text fields are uncontrolled (refs), + * per the app's iOS text-input rule; the type and enabled controls are state. + * + * Save validates through `validateMcpForm`; a refused input shows the matching + * `profiles.mcp.*` message under the field at fault and persists nothing. A + * local server's env values arrive masked, so the untouched keys round-trip and + * only retyped values rotate. + */ +export function McpFormSheet({ server, isSaving, onClose, onSave }: Readonly) { + const { t } = useTranslation(); + const insets = useSafeAreaInsets(); + const initial = initialMcpFormState(server ?? undefined); + const nameRef = useRef(initial.name); + const commandRef = useRef(initial.command); + const urlRef = useRef(initial.url); + const configRef = useRef(initial.configJson); + const timeoutRef = useRef(initial.timeout); + const [type, setType] = useState(initial.type); + const [enabled, setEnabled] = useState(initial.enabled); + const [error, setError] = useState(null); + + const submit = () => { + const state = { + name: nameRef.current, + type, + enabled, + command: commandRef.current, + url: urlRef.current, + configJson: configRef.current, + timeout: timeoutRef.current, + }; + const problem = validateMcpForm(state); + if (problem !== null) { + setError(problem); + return; + } + setError(null); + onSave(buildMcpServerPayload(state)); + }; + + const typeOptions = [ + { value: 'local' as const, label: t('profiles.mcp.localType') }, + { value: 'remote' as const, label: t('profiles.mcp.remoteType') }, + ]; + + return ( + + + + {server === null ? ( + + {t('profiles.mcp.type')} + + + ) : null} + + { + nameRef.current = value; + setError(null); + }} + /> + + {type === 'local' ? ( + { + commandRef.current = value; + setError(null); + }} + /> + ) : ( + { + urlRef.current = value; + setError(null); + }} + /> + )} + + { + configRef.current = value; + setError(null); + }} + /> + + { + timeoutRef.current = value; + setError(null); + }} + /> + + + {t('common.enabled')} + + + + + + ); +} diff --git a/apps/mobile/src/components/profiles/mcp-json.test.ts b/apps/mobile/src/components/profiles/mcp-json.test.ts new file mode 100644 index 00000000000..8c40aeea5e4 --- /dev/null +++ b/apps/mobile/src/components/profiles/mcp-json.test.ts @@ -0,0 +1,65 @@ +import { describe, expect, it } from 'vitest'; + +import { formatRecord, MASKED_MCP_VALUE, parseRecord } from '@/components/profiles/mcp-json'; + +describe('MASKED_MCP_VALUE', () => { + it('is the four-bullet placeholder the server round-trips', () => { + expect(MASKED_MCP_VALUE).toBe('\u2022\u2022\u2022\u2022'); + }); +}); + +describe('parseRecord', () => { + it('treats blank text as an empty fragment', () => { + expect(parseRecord('')).toEqual({ ok: true, value: undefined }); + expect(parseRecord(' \n ')).toEqual({ ok: true, value: undefined }); + }); + + it('parses a JSON object of strings', () => { + expect(parseRecord('{"API_KEY":"sk-1","NODE_ENV":"production"}')).toEqual({ + ok: true, + value: { API_KEY: 'sk-1', NODE_ENV: 'production' }, + }); + }); + + it('treats an empty object as an empty fragment', () => { + expect(parseRecord('{}')).toEqual({ ok: true, value: undefined }); + }); + + it('refuses invalid JSON', () => { + const result = parseRecord('{'); + expect(result.ok).toBe(false); + }); + + it('refuses a non-object', () => { + expect(parseRecord('"secret"').ok).toBe(false); + expect(parseRecord('[1,2]').ok).toBe(false); + expect(parseRecord('null').ok).toBe(false); + }); + + it('refuses a non-string value', () => { + const result = parseRecord('{"PORT":8080}'); + expect(result.ok).toBe(false); + if (!result.ok) { + expect(result.error).toContain('PORT'); + } + }); +}); + +describe('formatRecord', () => { + it('renders an empty fragment as blank text', () => { + expect(formatRecord(undefined)).toBe(''); + expect(formatRecord({})).toBe(''); + }); + + it('pretty-prints the record keys and masked values', () => { + expect(formatRecord({ API_KEY: MASKED_MCP_VALUE })).toBe( + JSON.stringify({ API_KEY: MASKED_MCP_VALUE }, null, 2) + ); + }); + + it('round-trips through parseRecord unchanged', () => { + const record = { API_KEY: MASKED_MCP_VALUE, NODE_ENV: 'production' }; + const parsed = parseRecord(formatRecord(record)); + expect(parsed).toEqual({ ok: true, value: record }); + }); +}); diff --git a/apps/mobile/src/components/profiles/mcp-json.ts b/apps/mobile/src/components/profiles/mcp-json.ts new file mode 100644 index 00000000000..8109b5ce5c7 --- /dev/null +++ b/apps/mobile/src/components/profiles/mcp-json.ts @@ -0,0 +1,79 @@ +/** + * Pure helpers for the JSON fragments an MCP server's environment/headers are + * typed as. No React and no React Native imports: every function is unit-tested + * directly in `mcp-json.test.ts`. + * + * Ported from the web editor's `parseRecord`/env-JSON handling + * (`apps/web/src/components/cloud-agent/profile-editor/McpServersTab.tsx`), so + * a fragment the web accepts is accepted here with the same validation. + */ + +/** + * Placeholder the server returns in place of each stored env/header value. + * Mirrors `MASKED_SECRET_VALUE` in `@kilocode/cloud-agent-profile`; the mobile + * app does not depend on that server-only package, so the value is repeated + * here. The MCP update procedure reuses a stored secret only when the input + * value is exactly this string, so an edit must send it back unchanged for the + * keys the user is not rotating. + */ +export const MASKED_MCP_VALUE = '\u2022\u2022\u2022\u2022'; + +/** The parse result: a `Record`, or why the text is not one. */ +export type ParsedRecord = + | { ok: true; value: Record | undefined } + | { ok: false; error: string }; + +type DecodedJson = { ok: true; value: unknown } | { ok: false; error: string }; + +/** Decode the text the user typed; the JSON error message explains a refusal. */ +function decodeJson(text: string): DecodedJson { + try { + return { ok: true, value: JSON.parse(text) }; + } catch (error) { + return { ok: false, error: error instanceof Error ? error.message : 'Invalid JSON' }; + } +} + +/** + * Parse a JSON object whose values are all strings. Blank text is a valid, + * empty fragment (`value: undefined`), matching the web editor. An array or a + * non-string value is refused so the form cannot send a shape the server's + * `z.record(z.string(), z.string())` would reject. + */ +export function parseRecord(raw: string): ParsedRecord { + const trimmed = raw.trim(); + if (trimmed.length === 0) { + return { ok: true, value: undefined }; + } + const decoded = decodeJson(trimmed); + if (!decoded.ok) { + return { ok: false, error: decoded.error }; + } + const parsed = decoded.value; + // oxlint-disable-next-line anti-slop/no-runtime-typeof -- user-typed JSON is decoded at this boundary; the object/array shape cannot be established statically + if (typeof parsed !== 'object' || parsed === null || Array.isArray(parsed)) { + return { ok: false, error: 'Must be a JSON object of string → string' }; + } + const out: Record = {}; + for (const [key, value] of Object.entries(parsed)) { + // oxlint-disable-next-line anti-slop/no-runtime-typeof -- each decoded JSON value is unknown input; a non-string is refused below + if (typeof value !== 'string') { + return { ok: false, error: `Value for "${key}" must be a string` }; + } + out[key] = value; + } + return { ok: true, value: Object.keys(out).length > 0 ? out : undefined }; +} + +/** + * Render a masked fragment back into the text area. A server's masked values + * round-trip verbatim: the update procedure keeps each stored secret whose + * input is `MASKED_MCP_VALUE`, so the keys stay visible and the user can rotate + * only the ones they retype. + */ +export function formatRecord(record: Record | undefined): string { + if (record === undefined || Object.keys(record).length === 0) { + return ''; + } + return JSON.stringify(record, null, 2); +} diff --git a/apps/mobile/src/components/profiles/new-profile-screen.mounted.test.tsx b/apps/mobile/src/components/profiles/new-profile-screen.mounted.test.tsx new file mode 100644 index 00000000000..f225bb16a11 --- /dev/null +++ b/apps/mobile/src/components/profiles/new-profile-screen.mounted.test.tsx @@ -0,0 +1,221 @@ +import { createElement } from 'react'; +import { act, type ReactTestInstance } from '@/test/renderer'; +import { beforeEach, describe, expect, it, vi } from 'vitest'; + +import '@/i18n'; +import { NewProfileScreen } from '@/components/profiles/new-profile-screen'; +import { renderWithProviders, waitFor } from '@/test/render-with-providers'; + +// ── Hoisted mocks ────────────────────────────────────────────────────────── + +const createMutate = vi.hoisted(() => vi.fn()); +const toastSuccess = vi.hoisted(() => vi.fn()); +const toastError = vi.hoisted(() => vi.fn()); +const routerReplace = vi.hoisted(() => vi.fn()); +const organizationState = vi.hoisted(() => ({ organizationId: 'org-1' as string | null })); +const mutationState = vi.hoisted(() => ({ + create: { mutateAsync: createMutate, isPending: false }, +})); + +vi.mock('@/lib/hooks/use-agent-profiles', () => ({ + useAgentProfileMutations: () => mutationState, +})); + +vi.mock('sonner-native', () => ({ + toast: { success: toastSuccess, error: toastError }, +})); + +vi.mock('expo-router', () => ({ + useRouter: () => ({ replace: routerReplace }), +})); + +vi.mock('@/lib/organization-context', () => ({ + useOrganization: () => ({ + organizationId: organizationState.organizationId, + isLoaded: true, + }), +})); + +vi.mock('@/lib/profile-agent-navigation', () => ({ + getProfileOverviewPath: (profileId: string, organizationId?: string) => + organizationId ? `/profiles/${profileId}?org=${organizationId}` : `/profiles/${profileId}`, +})); + +vi.mock('react-native', () => ({ + View: 'View', + ScrollView: 'ScrollView', +})); + +vi.mock('@/components/screen-header', () => ({ ScreenHeader: () => null })); +vi.mock('@/components/ui/button', () => ({ Button: 'Button' })); +vi.mock('@/components/ui/form-field', () => ({ FormField: 'FormField' })); +vi.mock('@/components/ui/segmented-control', () => ({ SegmentedControl: 'SegmentedControl' })); +vi.mock('@/components/ui/text', () => ({ Text: 'Text' })); + +// ── Helpers ──────────────────────────────────────────────────────────────── + +function findAll(root: ReactTestInstance, type: string): ReactTestInstance[] { + return root.findAll(node => typeof node.type === 'string' && node.type === type); +} + +function findField(root: ReactTestInstance, label: string): ReactTestInstance { + const field = findAll(root, 'FormField').find(node => node.props.label === label); + if (!field) { + throw new Error(`field ${label} was not rendered`); + } + return field; +} + +function findOwnerControl(root: ReactTestInstance): ReactTestInstance { + const control = findAll(root, 'SegmentedControl')[0]; + if (!control) { + throw new Error('owner control was not rendered'); + } + return control; +} + +function typeName(root: ReactTestInstance, name: string): void { + act(() => { + (findField(root, 'Profile name').props as { onChangeText: (v: string) => void }).onChangeText( + name + ); + }); +} + +async function mount() { + const result = await renderWithProviders(createElement(NewProfileScreen)); + return result; +} + +async function pressCreate(root: ReactTestInstance): Promise { + const button = findAll(root, 'Button')[0]; + if (!button) { + throw new Error('create button was not rendered'); + } + await act(async () => { + (button.props as { onPress: () => void }).onPress(); + await Promise.resolve(); + }); +} + +// ── Tests ────────────────────────────────────────────────────────────────── + +describe('NewProfileScreen', () => { + beforeEach(() => { + createMutate.mockReset(); + toastSuccess.mockReset(); + toastError.mockReset(); + routerReplace.mockReset(); + createMutate.mockResolvedValue({ id: 'profile-1' }); + organizationState.organizationId = 'org-1'; + mutationState.create.isPending = false; + }); + + it('non-retryable: an empty name shows the inline error and persists nothing', async () => { + const { renderer, unmount } = await mount(); + + await pressCreate(renderer.root); + + expect(findField(renderer.root, 'Profile name').props.error).toBe('Enter a profile name'); + expect(createMutate).not.toHaveBeenCalled(); + + unmount(); + }); + + it('happy: a personal create toasts and replaces to the overview', async () => { + const { renderer, unmount } = await mount(); + + typeName(renderer.root, 'Backend debugging'); + await pressCreate(renderer.root); + + await waitFor(() => createMutate.mock.calls.length > 0); + expect(createMutate.mock.calls[0]?.[0]).toEqual({ name: 'Backend debugging' }); + await waitFor(() => toastSuccess.mock.calls.length > 0); + expect(toastSuccess).toHaveBeenCalledWith('Profile "Backend debugging" created'); + expect(routerReplace).toHaveBeenCalledWith('/profiles/profile-1'); + + unmount(); + }); + + it('happy: choosing Organization carries the organization id into the create', async () => { + const { renderer, unmount } = await mount(); + + act(() => { + (findOwnerControl(renderer.root).props as { onChange: (v: string) => void }).onChange( + 'organization' + ); + }); + typeName(renderer.root, 'Org profile'); + await pressCreate(renderer.root); + + await waitFor(() => createMutate.mock.calls.length > 0); + expect(createMutate.mock.calls[0]?.[0]).toEqual({ + name: 'Org profile', + organizationId: 'org-1', + }); + expect(routerReplace).toHaveBeenCalledWith('/profiles/profile-1?org=org-1'); + + unmount(); + }); + + it('personal context: renders no owner control', async () => { + organizationState.organizationId = null; + const { renderer, unmount } = await mount(); + + expect(findAll(renderer.root, 'SegmentedControl')).toHaveLength(0); + + unmount(); + }); + + it('layout stability: switching owner keeps the typed name', async () => { + const { renderer, unmount } = await mount(); + + typeName(renderer.root, 'Backend debugging'); + act(() => { + (findOwnerControl(renderer.root).props as { onChange: (v: string) => void }).onChange( + 'organization' + ); + }); + await pressCreate(renderer.root); + + await waitFor(() => createMutate.mock.calls.length > 0); + expect(createMutate.mock.calls[0]?.[0]).toEqual({ + name: 'Backend debugging', + organizationId: 'org-1', + }); + + unmount(); + }); + + it('retryable: a failed create falls back to createFailed when the error has no message', async () => { + const errorWithoutMessage = new Error('placeholder'); + errorWithoutMessage.message = ''; + createMutate.mockRejectedValue(errorWithoutMessage); + const { renderer, unmount } = await mount(); + + typeName(renderer.root, 'Backend debugging'); + await pressCreate(renderer.root); + + await waitFor(() => toastError.mock.calls.length > 0); + expect(toastError).toHaveBeenCalledWith("Couldn't create profile"); + expect(routerReplace).not.toHaveBeenCalled(); + + unmount(); + }); + + it('retryable: keeps the hook message and the typed name on a failed create', async () => { + createMutate.mockRejectedValue(new Error('A profile with that name already exists')); + const { renderer, unmount } = await mount(); + + typeName(renderer.root, 'Backend debugging'); + await pressCreate(renderer.root); + + await waitFor(() => createMutate.mock.calls.length > 0); + // The mutation hook owns the server-message toast; the screen adds none. + expect(toastError).not.toHaveBeenCalled(); + expect(findField(renderer.root, 'Profile name').props.error).toBeUndefined(); + expect(routerReplace).not.toHaveBeenCalled(); + + unmount(); + }); +}); diff --git a/apps/mobile/src/components/profiles/new-profile-screen.tsx b/apps/mobile/src/components/profiles/new-profile-screen.tsx new file mode 100644 index 00000000000..ebf59db172b --- /dev/null +++ b/apps/mobile/src/components/profiles/new-profile-screen.tsx @@ -0,0 +1,151 @@ +import { useRouter } from 'expo-router'; +import { useRef, useState } from 'react'; +import { useTranslation } from 'react-i18next'; +import { ScrollView, View } from 'react-native'; +import { toast } from 'sonner-native'; + +import { + createOrganizationId, + hasOrganizationContext, + ownerChoices, + type ProfileOwnerChoice, +} from '@/components/profiles/profile-owner-model'; +import { ScreenHeader } from '@/components/screen-header'; +import { Button } from '@/components/ui/button'; +import { FormField } from '@/components/ui/form-field'; +import { SegmentedControl } from '@/components/ui/segmented-control'; +import { Text } from '@/components/ui/text'; +import { validateProfileName } from '@/lib/agent-profile-forms'; +import { useAgentProfileMutations } from '@/lib/hooks/use-agent-profiles'; +import { useOrganization } from '@/lib/organization-context'; +import { getProfileOverviewPath } from '@/lib/profile-agent-navigation'; + +/** + * Copy keys for the owner control, named here so the catalog entries land with + * the translation slice. The reviewed English fallbacks keep the control + * readable until then, and naming the keys in constants (rather than inline in + * `t()`) is how the catalog check sees them as living keys. + */ +const OWNER_LABEL_KEY = 'profiles.owner.owner'; +const OWNER_LABEL_FALLBACK_KEY = 'profiles.ownerLabel'; +const OWNER_CHOICE_LABEL_KEYS = { + personal: 'profiles.owner.personal', + organization: 'profiles.owner.organization', +} as const satisfies Record; +const OWNER_CHOICE_FALLBACK_KEYS = { + personal: 'common.personal', + organization: 'common.organization', +} as const satisfies Record; + +/** + * The mutation hook toasts `error.message`; this screen only supplies a + * fallback when the server sent nothing readable. + */ +function hasUsableMessage(error: unknown): boolean { + return error instanceof Error && error.message.trim().length > 0; +} + +export function NewProfileScreen() { + const router = useRouter(); + const { t } = useTranslation(); + const { organizationId } = useOrganization(); + // No organization context on the hook: the owner choice below decides which + // `organizationId` each create carries, so a personal create is never + // rewritten to the selected organization. + const { create } = useAgentProfileMutations(); + const nameRef = useRef(''); + const descriptionRef = useRef(''); + const [owner, setOwner] = useState('personal'); + const [nameError, setNameError] = useState(null); + + const submit = async () => { + const name = nameRef.current.trim(); + if (validateProfileName(name) !== null) { + setNameError(t('profiles.nameRequired')); + return; + } + setNameError(null); + + const description = descriptionRef.current.trim(); + const ownerOrganizationId = createOrganizationId(organizationId, owner); + try { + const created = await create.mutateAsync({ + name, + ...(description.length > 0 ? { description } : {}), + ...(ownerOrganizationId === undefined ? {} : { organizationId: ownerOrganizationId }), + }); + toast.success(t('profiles.createdToast', { name })); + router.replace(getProfileOverviewPath(created.id, ownerOrganizationId)); + } catch (error) { + if (!hasUsableMessage(error)) { + toast.error(t('profiles.createFailed')); + } + } + }; + + return ( + + + + { + nameRef.current = value; + if (nameError !== null && validateProfileName(value) === null) { + setNameError(null); + } + }} + /> + { + descriptionRef.current = value; + }} + /> + + {hasOrganizationContext(organizationId) ? ( + + + {t(OWNER_LABEL_KEY, { defaultValue: t(OWNER_LABEL_FALLBACK_KEY) })} + + ({ + value: choice, + label: t(OWNER_CHOICE_LABEL_KEYS[choice], { + defaultValue: t(OWNER_CHOICE_FALLBACK_KEYS[choice]), + }), + }))} + value={owner} + onChange={setOwner} + accessibilityLabel={t(OWNER_LABEL_KEY, { defaultValue: t(OWNER_LABEL_FALLBACK_KEY) })} + /> + + ) : null} + + + + + ); +} diff --git a/apps/mobile/src/components/profiles/profile-agents-model.test.ts b/apps/mobile/src/components/profiles/profile-agents-model.test.ts new file mode 100644 index 00000000000..a7dd081b5aa --- /dev/null +++ b/apps/mobile/src/components/profiles/profile-agents-model.test.ts @@ -0,0 +1,287 @@ +import { describe, expect, it } from 'vitest'; + +import { + type AgentFormState, + type AgentPermissionMap, + agentRows, + type AgentSource, + buildAgentPayload, + initialAgentFormState, + mergePermissions, + readDisabledTools, + validateAgentForm, +} from '@/components/profiles/profile-agents-model'; + +function agentSource(overrides: Partial = {}): AgentSource { + return { + id: 'agent-1', + slug: 'reviewer', + name: 'Reviewer', + config: { + description: 'Reviews code', + mode: 'primary', + model: 'anthropic/claude', + prompt: 'You review', + permission: { bash: 'deny', read: 'allow' }, + }, + ...overrides, + }; +} + +describe('agentRows', () => { + it('projects the row fields with primary as the default visibility', () => { + expect(agentRows([agentSource(), agentSource({ config: {} })])).toEqual([ + { + id: 'agent-1', + slug: 'reviewer', + name: 'Reviewer', + visibility: 'primary', + description: 'Reviews code', + model: 'anthropic/claude', + }, + { + id: 'agent-1', + slug: 'reviewer', + name: 'Reviewer', + visibility: 'primary', + description: '', + model: '', + }, + ]); + }); +}); + +describe('readDisabledTools', () => { + it('returns only the tools with a simple deny rule', () => { + expect(readDisabledTools({ bash: 'deny', read: 'allow', glob: { '**/*.ts': 'deny' } })).toEqual( + ['bash'] + ); + }); + + it('returns nothing for a non-object permission', () => { + expect(readDisabledTools(undefined)).toEqual([]); + expect(readDisabledTools(null)).toEqual([]); + expect(readDisabledTools('allow')).toEqual([]); + }); +}); + +describe('mergePermissions', () => { + it('writes deny for a disabled tool and clears every other simple rule', () => { + expect(mergePermissions({ bash: 'deny', read: 'allow' }, ['read'])).toEqual({ read: 'deny' }); + expect(mergePermissions({ bash: 'deny', read: 'deny' }, [])).toBeUndefined(); + }); + + it('preserves a per-pattern rule when a tool is re-enabled', () => { + const perPattern: AgentPermissionMap = { glob: { '**/*.ts': 'deny' } }; + expect(mergePermissions(perPattern, [])).toEqual(perPattern); + }); + + it('returns undefined when nothing is left', () => { + expect(mergePermissions(undefined, [])).toBeUndefined(); + expect(mergePermissions({ bash: 'deny' }, [])).toBeUndefined(); + }); +}); + +describe('initialAgentFormState', () => { + it('seeds blank defaults for an add', () => { + expect(initialAgentFormState()).toEqual({ + slug: '', + name: '', + description: '', + prompt: '', + visibility: 'primary', + model: '', + steps: '', + temperature: '', + topP: '', + variant: '', + disabledTools: [], + }); + }); + + it('seeds an agent with its config and denied tools', () => { + expect(initialAgentFormState(agentSource())).toEqual({ + slug: 'reviewer', + name: 'Reviewer', + description: 'Reviews code', + prompt: 'You review', + visibility: 'primary', + model: 'anthropic/claude', + steps: '', + temperature: '', + topP: '', + variant: '', + disabledTools: ['bash'], + }); + }); + + it('seeds the sampling fields and variant from the agent config', () => { + expect( + initialAgentFormState( + agentSource({ + config: { + model: 'anthropic/claude', + steps: 50, + temperature: 0.2, + top_p: 0.95, + variant: 'high', + }, + }) + ) + ).toMatchObject({ + steps: '50', + temperature: '0.2', + topP: '0.95', + variant: 'high', + }); + }); +}); + +function formState(overrides: Partial = {}): AgentFormState { + return { + slug: 'reviewer', + name: 'Reviewer', + description: '', + prompt: '', + visibility: 'primary', + model: '', + steps: '', + temperature: '', + topP: '', + variant: '', + disabledTools: [], + ...overrides, + }; +} + +describe('validateAgentForm', () => { + it('requires a slug', () => { + expect(validateAgentForm(formState({ slug: ' ' }))).toBe('slug-required'); + }); + + it('refuses a slug outside the server pattern', () => { + expect(validateAgentForm(formState({ slug: 'Bad Slug' }))).toBe('slug-invalid'); + expect(validateAgentForm(formState({ slug: '1agent' }))).toBe('slug-invalid'); + }); + + it('refuses a slug reserved by a built-in agent', () => { + expect(validateAgentForm(formState({ slug: 'code' }))).toBe('slug-conflict'); + }); + + it('requires a display name', () => { + expect(validateAgentForm(formState({ name: ' ' }))).toBe('name-required'); + }); + + it('accepts a valid agent', () => { + expect(validateAgentForm(formState())).toBeNull(); + }); +}); + +describe('buildAgentPayload', () => { + it('builds the payload and merges the tool toggles', () => { + expect( + buildAgentPayload( + formState({ + name: 'Reviewer', + description: 'Reviews', + prompt: 'You review', + model: 'x', + disabledTools: ['bash'], + }), + agentSource().config + ) + ).toEqual({ + slug: 'reviewer', + name: 'Reviewer', + config: { + description: 'Reviews', + mode: 'primary', + model: 'x', + prompt: 'You review', + permission: { bash: 'deny' }, + }, + }); + }); + + it('sends the sampling fields as numbers', () => { + const payload = buildAgentPayload( + formState({ model: 'x', steps: '50', temperature: '0.2', topP: '0.95' }) + ); + expect(payload.config.steps).toBe(50); + expect(payload.config.temperature).toBe(0.2); + expect(payload.config.top_p).toBe(0.95); + }); + + it('clears the sampling fields when the form is blank', () => { + const existing = { + ...agentSource().config, + steps: 50, + temperature: 0.2, + top_p: 0.95, + }; + const payload = buildAgentPayload(formState({ temperature: ' ' }), existing); + expect(payload.config.steps).toBeUndefined(); + expect(payload.config.temperature).toBeUndefined(); + expect(payload.config.top_p).toBeUndefined(); + }); + + it('drops a zero, negative, or non-numeric step count', () => { + expect(buildAgentPayload(formState({ steps: '0' })).config.steps).toBeUndefined(); + expect(buildAgentPayload(formState({ steps: '-5' })).config.steps).toBeUndefined(); + expect(buildAgentPayload(formState({ steps: 'abc' })).config.steps).toBeUndefined(); + expect(buildAgentPayload(formState({ steps: '25' })).config.steps).toBe(25); + }); + + it('drops a non-numeric temperature or top_p', () => { + expect(buildAgentPayload(formState({ temperature: 'hot' })).config.temperature).toBeUndefined(); + expect(buildAgentPayload(formState({ topP: 'x' })).config.top_p).toBeUndefined(); + expect(buildAgentPayload(formState({ temperature: '1.5' })).config.temperature).toBe(1.5); + }); + + it('preserves fields the form does not surface', () => { + const existing = { ...agentSource().config, hidden: true, color: '#112233' }; + const payload = buildAgentPayload(formState({ steps: '50', temperature: '0.2' }), existing); + expect(payload.config.hidden).toBe(true); + expect(payload.config.color).toBe('#112233'); + }); + + it('keeps the effort variant when the model is unchanged', () => { + const existing = { ...agentSource().config, variant: 'high' }; + expect( + buildAgentPayload(formState({ model: 'anthropic/claude', variant: 'high' }), existing).config + .variant + ).toBe('high'); + }); + + it('keeps the effort variant when the model changes to one that shares it', () => { + // The sheet clears `state.variant` only when the typed model does not offer + // it (`agent-form-sheet.tsx`), so a non-empty variant the form still carries + // belongs to the new model and must survive the switch. + const existing = { ...agentSource().config, variant: 'high' }; + expect( + buildAgentPayload(formState({ model: 'openai/gpt', variant: 'high' }), existing).config + .variant + ).toBe('high'); + }); + + it('sends a picked variant for a new agent once a model is typed', () => { + expect( + buildAgentPayload(formState({ model: 'anthropic/claude', variant: 'low' })).config.variant + ).toBe('low'); + }); + + it('drops the effort variant when the model is cleared or the form carries none', () => { + const existing = { ...agentSource().config, variant: 'high' }; + expect( + buildAgentPayload(formState({ model: '', variant: 'high' }), existing).config.variant + ).toBeUndefined(); + expect( + buildAgentPayload(formState({ model: 'openai/gpt', variant: '' }), existing).config.variant + ).toBeUndefined(); + }); + + it('omits an empty permission map', () => { + const payload = buildAgentPayload(formState(), { permission: { bash: 'deny' } }); + expect(payload.config.permission).toBeUndefined(); + }); +}); diff --git a/apps/mobile/src/components/profiles/profile-agents-model.ts b/apps/mobile/src/components/profiles/profile-agents-model.ts new file mode 100644 index 00000000000..1c5e553556b --- /dev/null +++ b/apps/mobile/src/components/profiles/profile-agents-model.ts @@ -0,0 +1,293 @@ +/** + * Pure view-model helpers for the profile agents screen. + * + * No React and no React Native imports: every function here is unit-tested + * directly in `profile-agents-model.test.ts`. The screen owns rendering and the + * network calls; this module owns the row projection, the add/edit form state, + * the client-side validation that runs before a `createAgent`/`updateAgent`, + * and the agent config the mutations send. + * + * Ported from the web editor (`ProfileAgentsTab.tsx`): the same reserved slugs, + * the same disableable tool list, and the same rule that a permission map is + * preserved except for the tools the user explicitly switches off. + */ + +import { type AgentProfileDetail } from '@/lib/hooks/agent-profile-types'; + +/** + * Built-in agent slugs reserved by cloud-agent-next. Mirrors + * `BUILTIN_AGENT_SLUGS` in `@kilocode/cloud-agent-profile`; the mobile app does + * not depend on that server package, so the set is repeated here. The server + * rejects a create or rename that collides, so the form refuses it first. + */ +const BUILTIN_AGENT_SLUGS = new Set([ + 'code', + 'plan', + 'debug', + 'orchestrator', + 'ask', + 'build', + 'architect', + 'custom', +]); + +/** + * Tools the UI exposes as disableable per agent. Cloud sessions allow every + * tool by default; the only customization surfaced is "turn this tool off", + * which emits `deny`. Mirrors `PERMISSION_TOOLS` in the web editor. + */ +export const PERMISSION_TOOLS: readonly string[] = [ + 'read', + 'edit', + 'bash', + 'glob', + 'grep', + 'list', + 'task', + 'skill', + 'webfetch', + 'websearch', + 'codesearch', + 'mcp', +]; + +/** The agent config exactly as `agentProfiles.get` returns it. */ +type ProfileAgentConfig = AgentProfileDetail['agents'][number]['config']; + +export type AgentVisibility = NonNullable; + +/** + * The permission map an agent config carries. Derived from the tRPC output, so + * the CLI's bare-action shorthand (`allow`/`ask`/`deny`) is part of the union + * alongside the per-tool map; `null` is accepted defensively for hand-built + * values. + */ +export type AgentPermissionMap = ProfileAgentConfig['permission'] | null; + +/** The fields of an agent the screen reads. Structural, so tests are easy. */ +export type AgentSource = Readonly<{ + id: string; + slug: string; + name: string; + config: ProfileAgentConfig; +}>; + +/** One agent row as the screen renders it. */ +export type AgentRow = Readonly<{ + id: string; + slug: string; + name: string; + visibility: AgentVisibility; + description: string; + model: string; +}>; + +/** Project the profile's agents into the rows the list renders. */ +export function agentRows(agents: readonly AgentSource[]): AgentRow[] { + return agents.map(agent => ({ + id: agent.id, + slug: agent.slug, + name: agent.name, + visibility: agent.config.mode ?? 'primary', + description: agent.config.description ?? '', + model: agent.config.model ?? '', + })); +} + +/** + * The per-tool map when the value is one, or an empty map when the permission + * is the CLI's bare-action shorthand (`allow`/`ask`/`deny`) or absent. Mirrors + * the web editor's `typeof permission !== 'object'` guard: a bare action means + * "every tool at this level" and carries no per-tool rules. + */ +function permissionMapOf(permission: AgentPermissionMap): Readonly> { + if ( + permission == null || + permission === 'allow' || + permission === 'ask' || + permission === 'deny' + ) { + return {}; + } + return permission; +} + +/** + * The tools denied for this agent. Only a simple string rule of `deny` counts; + * per-pattern maps and other actions are left alone, matching the web editor. + */ +export function readDisabledTools(permission: AgentPermissionMap): string[] { + const map = permissionMapOf(permission); + return PERMISSION_TOOLS.filter(tool => map[tool] === 'deny'); +} + +/** + * Apply the UI's disable toggles onto the existing permission map, preserving + * any per-pattern rules and unrelated tool keys. Toggling a tool off writes + * `deny`; toggling it back on clears a simple string rule but leaves a + * per-pattern map alone. An empty result becomes `undefined` so the config + * carries no empty permission object. + */ +export function mergePermissions( + existing: AgentPermissionMap, + disabledTools: readonly string[] +): Record | undefined { + const base = new Map(Object.entries(permissionMapOf(existing))); + for (const tool of PERMISSION_TOOLS) { + if (disabledTools.includes(tool)) { + base.set(tool, 'deny'); + // oxlint-disable-next-line anti-slop/no-runtime-typeof -- permission values are an open rule union; a string arm is the only clearable one + } else if (typeof base.get(tool) === 'string') { + base.delete(tool); + } + } + return base.size > 0 ? Object.fromEntries(base) : undefined; +} + +/** The add/edit form fields. Every numeric field is text; blank means "inherit". */ +export type AgentFormState = Readonly<{ + slug: string; + name: string; + description: string; + prompt: string; + visibility: AgentVisibility; + model: string; + steps: string; + temperature: string; + topP: string; + variant: string; + disabledTools: readonly string[]; +}>; + +/** Render an optional config number as form text, so blank means "not set". */ +function numberToField(value: number | undefined): string { + return value === undefined ? '' : String(value); +} + +/** Seed the form from the agent being edited, or blank defaults for an add. */ +export function initialAgentFormState(agent?: AgentSource): AgentFormState { + if (agent === undefined) { + return { + slug: '', + name: '', + description: '', + prompt: '', + visibility: 'primary', + model: '', + steps: '', + temperature: '', + topP: '', + variant: '', + disabledTools: [], + }; + } + return { + slug: agent.slug, + name: agent.name, + description: agent.config.description ?? '', + prompt: agent.config.prompt ?? '', + visibility: agent.config.mode ?? 'primary', + model: agent.config.model ?? '', + steps: numberToField(agent.config.steps), + temperature: numberToField(agent.config.temperature), + topP: numberToField(agent.config.top_p), + variant: agent.config.variant ?? '', + disabledTools: readDisabledTools(agent.config.permission), + }; +} + +const AGENT_SLUG_PATTERN = /^[a-z][a-z0-9-]*$/; + +export type AgentFormError = 'slug-required' | 'slug-invalid' | 'slug-conflict' | 'name-required'; + +/** + * Validate the form before a save. Returns the field at fault, or `null` when + * valid. Mirrors the server's `agentSlugSchema`/`agentNameSchema`: a non-empty + * display name, and a slug that starts with a lowercase letter, stays in + * `[a-z0-9-]`, and does not collide with a built-in agent. + */ +export function validateAgentForm(state: AgentFormState): AgentFormError | null { + const slug = state.slug.trim(); + if (slug.length === 0) { + return 'slug-required'; + } + if (!AGENT_SLUG_PATTERN.test(slug)) { + return 'slug-invalid'; + } + if (BUILTIN_AGENT_SLUGS.has(slug)) { + return 'slug-conflict'; + } + if (state.name.trim().length === 0) { + return 'name-required'; + } + return null; +} + +/** The config shape the agent mutations send; mirrors the server's `AgentConfigSchema`. */ +type AgentConfigPayload = Record; + +/** The payload the agent mutations send. */ +export type AgentPayload = { + slug: string; + name: string; + config: AgentConfigPayload; +}; + +/** A trimmed blank string is "inherit"; otherwise the finite float it parses to. */ +function parseNumber(value: string): number | undefined { + const trimmed = value.trim(); + if (trimmed.length === 0) { + return undefined; + } + const parsed = Number.parseFloat(trimmed); + return Number.isFinite(parsed) ? parsed : undefined; +} + +/** A blank, non-numeric, zero, or negative step count is "inherit". */ +function parseInt10(value: string): number | undefined { + const trimmed = value.trim(); + if (trimmed.length === 0) { + return undefined; + } + const parsed = Number.parseInt(trimmed, 10); + return Number.isFinite(parsed) && parsed > 0 ? parsed : undefined; +} + +/** + * Build the mutation payload from a validated form. Only call after + * `validateAgentForm` returns `null`. + * + * The agent's existing config is spread first, so fields the form does not + * surface (hidden/disable flags, `color`) survive an edit. Sampling fields + * mirror web: a blank string clears the override, `steps` only when it is a + * positive integer. The effort variant is dropped when the model is cleared or + * the form carries none — the server rejects a variant without a matching + * model, and `agent-form-sheet.tsx` clears `state.variant` whenever the typed + * model does not offer it, so a non-empty variant belongs to the new model even + * when the model string changed. + */ +export function buildAgentPayload( + state: AgentFormState, + existing?: AgentSource['config'] +): AgentPayload { + const model = state.model.trim(); + const variant = state.variant.trim(); + const keepVariant = variant.length > 0 && model.length > 0; + const config = { + ...existing, + prompt: state.prompt.trim() || undefined, + description: state.description.trim() || undefined, + mode: state.visibility, + model: model.length > 0 ? model : undefined, + temperature: parseNumber(state.temperature), + top_p: parseNumber(state.topP), + steps: parseInt10(state.steps), + permission: mergePermissions(existing?.permission, state.disabledTools), + } satisfies AgentConfigPayload; + if (keepVariant) { + config.variant = variant; + } else { + delete config.variant; + } + return { slug: state.slug.trim(), name: state.name.trim(), config }; +} diff --git a/apps/mobile/src/components/profiles/profile-agents-screen.tsx b/apps/mobile/src/components/profiles/profile-agents-screen.tsx new file mode 100644 index 00000000000..2ffcb996530 --- /dev/null +++ b/apps/mobile/src/components/profiles/profile-agents-screen.tsx @@ -0,0 +1,255 @@ +import { type ReactNode, useState } from 'react'; +import { useTranslation } from 'react-i18next'; +import { Alert, Pressable, ScrollView, View } from 'react-native'; +import { toast } from 'sonner-native'; + +import { EmptyState } from '@/components/empty-state'; +import { AgentFormSheet } from '@/components/profiles/agent-form-sheet'; +import { agentRows, type AgentSource } from '@/components/profiles/profile-agents-model'; +import { QueryError } from '@/components/query-error'; +import { ScreenHeader } from '@/components/screen-header'; +import { Button } from '@/components/ui/button'; +import { Bot, Pencil, Trash2 } from '@/components/ui/icons'; +import { Skeleton } from '@/components/ui/skeleton'; +import { Text } from '@/components/ui/text'; +import { + type AgentProfileDetail, + useAgentProfile, + useAgentProfileSectionMutations, +} from '@/lib/hooks/use-agent-profiles'; +import { useThemeColors } from '@/lib/hooks/use-theme-colors'; + +/** Stable empty fallback so the derived list is not a new array each render. */ +const NO_AGENTS: AgentProfileDetail['agents'] = []; + +/** The mutation hook toasts `error.message`; the screen supplies a fallback otherwise. */ +function hasUsableMessage(error: unknown): boolean { + return error instanceof Error && error.message.trim().length > 0; +} + +type AgentRowViewProps = Readonly<{ + name: string; + slug: string; + visibility: string; + description: string; + model: string; + onEdit: () => void; + onDelete: () => void; +}>; + +/** One agent row: name, slug, visibility, optional model and description, edit/delete. */ +function AgentRowView({ + name, + slug, + visibility, + description, + model, + onEdit, + onDelete, +}: AgentRowViewProps) { + const { t } = useTranslation(); + const colors = useThemeColors(); + return ( + + + + + + {name} + + + {slug} + + + {visibility} + + + {model.length > 0 ? ( + + {model} + + ) : null} + {description.length > 0 ? ( + + {description} + + ) : null} + + + + + + + + + ); +} + +/** Content-shaped rows in the same slots as a loaded agent row. */ +function AgentsSkeleton() { + return ( + + {[0, 1, 2].map(index => ( + + ))} + + ); +} + +export function ProfileAgentsScreen({ + profileId, + organizationId, +}: Readonly<{ profileId: string; organizationId?: string }>) { + const { t } = useTranslation(); + const profileQuery = useAgentProfile(profileId, organizationId); + const { createAgent, updateAgent, deleteAgent } = useAgentProfileSectionMutations(organizationId); + + const agents = profileQuery.data?.agents ?? NO_AGENTS; + const rows = agentRows(agents); + // `null` = closed, `{ source: null }` = add, `{ source }` = edit that agent. + const [form, setForm] = useState<{ source: AgentSource | null } | null>(null); + + const startAdd = () => { + setForm({ source: null }); + }; + + const closeForm = () => { + setForm(null); + }; + + const saveAgent = async ( + target: AgentSource | null, + payload: { slug: string; name: string; config: Record } + ) => { + try { + await (target === null + ? createAgent.mutateAsync({ profileId, ...payload }) + : updateAgent.mutateAsync({ profileId, agentId: target.id, ...payload })); + } catch (error) { + if (!hasUsableMessage(error)) { + toast.error(t('profiles.agents.saveFailed')); + } + return; + } + closeForm(); + }; + + const runDelete = async (agentId: string) => { + try { + await deleteAgent.mutateAsync({ profileId, agentId }); + } catch (error) { + if (!hasUsableMessage(error)) { + toast.error(t('profiles.agents.saveFailed')); + } + } + }; + + const confirmDelete = (target: AgentSource) => { + Alert.alert(t('common.delete'), target.name, [ + { text: t('common.cancel'), style: 'cancel' }, + { + text: t('common.delete'), + style: 'destructive', + onPress: () => { + void runDelete(target.id); + }, + }, + ]); + }; + + let content: ReactNode = null; + if (profileQuery.isError) { + content = ( + void profileQuery.refetch()} + isRetrying={profileQuery.isRefetching} + /> + ); + } else if (profileQuery.isPending) { + content = ; + } else if (rows.length === 0) { + content = ( + + {t('profiles.agents.add')} + + } + /> + ); + } else { + content = ( + <> + {rows.map((row, index) => { + const source = agents[index]; + if (source === undefined) { + return null; + } + return ( + { + setForm({ source }); + }} + onDelete={() => { + confirmDelete(source); + }} + /> + ); + })} + + + ); + } + + return ( + + + + {content} + + + {form !== null ? ( + { + void saveAgent(form.source, payload); + }} + /> + ) : null} + + ); +} diff --git a/apps/mobile/src/components/profiles/profile-commands-model.test.ts b/apps/mobile/src/components/profiles/profile-commands-model.test.ts new file mode 100644 index 00000000000..0956b4dfb76 --- /dev/null +++ b/apps/mobile/src/components/profiles/profile-commands-model.test.ts @@ -0,0 +1,63 @@ +import { describe, expect, it } from 'vitest'; + +import { + addCommand, + commandRowA11yLabel, + MAX_SETUP_COMMANDS, + moveCommand, + removeCommand, + replaceCommand, +} from '@/components/profiles/profile-commands-model'; + +describe('setup command list operations', () => { + const commands = ['pnpm install', 'pnpm build', 'pnpm test']; + + it('appends a blank command to an empty and a populated list', () => { + expect(addCommand(commands)).toEqual(['pnpm install', 'pnpm build', 'pnpm test', '']); + expect(addCommand([])).toEqual(['']); + }); + + it('refuses a command beyond the server list cap', () => { + const atCap = Array.from({ length: MAX_SETUP_COMMANDS }, (_, index) => `cmd ${index}`); + expect(addCommand(atCap)).toEqual(atCap); + }); + + it('replaces the command at an index', () => { + expect(replaceCommand(commands, 1, 'pnpm lint')).toEqual([ + 'pnpm install', + 'pnpm lint', + 'pnpm test', + ]); + expect(replaceCommand(commands, 7, 'ignored')).toEqual(commands); + }); + + it('removes the command at an index without touching the source list', () => { + expect(removeCommand(commands, 0)).toEqual(['pnpm build', 'pnpm test']); + expect(removeCommand(commands, 7)).toEqual(commands); + expect(commands).toEqual(['pnpm install', 'pnpm build', 'pnpm test']); + }); + + it('moves a command up and down', () => { + expect(moveCommand(commands, 2, -1)).toEqual(['pnpm install', 'pnpm test', 'pnpm build']); + expect(moveCommand(commands, 0, 1)).toEqual(['pnpm build', 'pnpm install', 'pnpm test']); + }); + + it('clamps a move at both edges', () => { + expect(moveCommand(commands, 0, -1)).toEqual(commands); + expect(moveCommand(commands, 2, 1)).toEqual(commands); + expect(moveCommand(commands, 5, -1)).toEqual(commands); + }); + + it('clamps a single-command list in both directions', () => { + const single = ['pnpm install']; + expect(moveCommand(single, 0, -1)).toEqual(single); + expect(moveCommand(single, 0, 1)).toEqual(single); + }); +}); + +describe('commandRowA11yLabel', () => { + it('labels a row with its 1-based position and the list length', () => { + expect(commandRowA11yLabel(0, 1)).toBe('1 / 1'); + expect(commandRowA11yLabel(2, 3)).toBe('3 / 3'); + }); +}); diff --git a/apps/mobile/src/components/profiles/profile-commands-model.ts b/apps/mobile/src/components/profiles/profile-commands-model.ts new file mode 100644 index 00000000000..0b7315c5688 --- /dev/null +++ b/apps/mobile/src/components/profiles/profile-commands-model.ts @@ -0,0 +1,27 @@ +/** + * Pure view-model helpers for the profile Setup commands screen. + * + * No React and no React Native imports. `setCommands` replaces the whole list, + * so the screen edits an ordered `string[]` and this module is the list's only + * entry point: it re-exports the s2 list operations (`addCommand`, + * `replaceCommand`, `removeCommand`, `moveCommand`) and the server's list/row + * bounds (`MAX_SETUP_COMMANDS`, `MAX_SETUP_COMMAND_LENGTH`), and adds the + * per-row position label the row's controls carry. + */ + +export { + addCommand, + MAX_SETUP_COMMAND_LENGTH, + MAX_SETUP_COMMANDS, + moveCommand, + removeCommand, + replaceCommand, +} from '@/lib/agent-profile-forms'; + +/** + * Position label for a command row's controls, e.g. `2 / 3`. Digits only, so + * no catalog entry is needed and the reader hears the row's place in the list. + */ +export function commandRowA11yLabel(index: number, total: number): string { + return `${index + 1} / ${total}`; +} diff --git a/apps/mobile/src/components/profiles/profile-commands-screen.mounted.test.tsx b/apps/mobile/src/components/profiles/profile-commands-screen.mounted.test.tsx new file mode 100644 index 00000000000..6af1ca39ca1 --- /dev/null +++ b/apps/mobile/src/components/profiles/profile-commands-screen.mounted.test.tsx @@ -0,0 +1,322 @@ +import { beforeEach, describe, expect, it, vi } from 'vitest'; + +import '@/i18n'; +import { + changeRowText, + commitRow, + findAll, + findFields, + findOne, + mountScreen, + pressableDisabled, + pressPressable, + rerenderScreen, + testProfile, + type TestProfileDetail, +} from '@/components/profiles/profile-commands-screen.test-helpers'; +import { act } from '@/test/renderer'; +import { waitFor } from '@/test/render-with-providers'; + +const h = vi.hoisted(() => ({ + error: vi.fn(), + query: { + data: undefined as TestProfileDetail | undefined, + isError: false, + isPending: true, + isRefetching: false, + refetch: vi.fn(), + }, + mutations: { + setCommands: { mutateAsync: vi.fn(), isPending: false }, + }, +})); + +vi.mock('@/lib/hooks/use-agent-profiles', () => ({ + useAgentProfile: () => h.query, + useAgentProfileMutations: () => h.mutations, +})); +vi.mock('sonner-native', () => ({ toast: { error: h.error } })); +vi.mock('react-native', () => ({ + View: 'View', + ScrollView: 'ScrollView', + Pressable: 'Pressable', +})); +vi.mock('@/lib/hooks/use-theme-colors', () => ({ + useThemeColors: () => ({ mutedForeground: '#000000', destructive: '#FF0000' }), +})); +vi.mock('@/components/screen-header', () => ({ ScreenHeader: 'ScreenHeader' })); +vi.mock('@/components/query-error', () => ({ QueryError: 'QueryError' })); +vi.mock('@/components/empty-state', () => ({ EmptyState: 'EmptyState' })); +vi.mock('@/components/ui/button', () => ({ Button: 'Button' })); +vi.mock('@/components/ui/form-field', async () => { + const { createElement, useRef } = await import('react'); + return { + FormField: (props: { defaultValue?: string; onChangeText?: (value: string) => void }) => { + // Native uncontrolled text only accepts defaultValue when mounted. + const nativeText = useRef(props.defaultValue); + return createElement('FormField', { + ...props, + nativeText: nativeText.current, + onChangeText: (value: string) => { + nativeText.current = value; + props.onChangeText?.(value); + }, + }); + }, + }; +}); +vi.mock('@/components/ui/skeleton', () => ({ Skeleton: 'Skeleton' })); +vi.mock('@/components/ui/text', () => ({ Text: 'Text' })); +vi.mock('@/components/ui/icons', () => ({ + ChevronDown: 'ChevronDown', + ChevronUp: 'ChevronUp', + Terminal: 'Terminal', + Trash2: 'Trash2', +})); + +/** Press the add CTA the empty state renders through its `action` prop. */ +function pressEmptyAction(renderer: Awaited>['renderer']): void { + const action = findOne(renderer.root, 'EmptyState').props.action as { + props: { onPress: () => void }; + }; + act(() => { + action.props.onPress(); + }); +} + +describe('ProfileCommandsScreen', () => { + beforeEach(() => { + vi.clearAllMocks(); + Object.assign(h.query, { + data: undefined, + isError: false, + isPending: true, + isRefetching: false, + }); + Object.assign(h.mutations.setCommands, { isPending: false }); + }); + + it('loading: renders skeletons and no rows', async () => { + const { renderer, unmount } = await mountScreen(); + + expect(findAll(renderer.root, 'Skeleton').length).toBeGreaterThan(0); + expect(findFields(renderer.root)).toHaveLength(0); + + unmount(); + }); + + it('error: renders QueryError and Retry refetches', async () => { + Object.assign(h.query, { isError: true, isPending: false }); + + const { renderer, unmount } = await mountScreen(); + + const queryError = findOne(renderer.root, 'QueryError'); + act(() => { + (queryError.props as { onRetry: () => void }).onRetry(); + }); + expect(h.query.refetch).toHaveBeenCalledTimes(1); + + unmount(); + }); + + it('empty: shows commandsEmpty and the add CTA opens an empty row', async () => { + h.query.data = testProfile(); + h.query.isPending = false; + + const { renderer, unmount } = await mountScreen(); + + expect(findOne(renderer.root, 'EmptyState').props.title).toBe('No setup commands yet'); + pressEmptyAction(renderer); + expect(findFields(renderer.root)[0]?.props.placeholder).toBe('e.g. pnpm install'); + + unmount(); + }); + + it('happy: a committed row persists the whole list', async () => { + h.query.data = testProfile(); + h.query.isPending = false; + h.mutations.setCommands.mutateAsync.mockResolvedValue({ success: true }); + + const { renderer, unmount } = await mountScreen(); + pressEmptyAction(renderer); + changeRowText(renderer.root, 0, 'pnpm install'); + commitRow(renderer.root, 0); + + await waitFor(() => h.mutations.setCommands.mutateAsync.mock.calls.length > 0); + expect(h.mutations.setCommands.mutateAsync.mock.calls[0]?.[0]).toEqual({ + profileId: 'profile-1', + commands: ['pnpm install'], + }); + + unmount(); + }); + + it('happy: moving a row persists the reordered list', async () => { + h.query.data = testProfile(['a', 'b']); + h.query.isPending = false; + h.mutations.setCommands.mutateAsync.mockResolvedValue({ success: true }); + + const { renderer, unmount } = await mountScreen(); + pressPressable(renderer.root, 'Move down', 0); + + await waitFor(() => h.mutations.setCommands.mutateAsync.mock.calls.length > 0); + expect(h.mutations.setCommands.mutateAsync.mock.calls[0]?.[0]).toEqual({ + profileId: 'profile-1', + commands: ['b', 'a'], + }); + + unmount(); + }); + + it('edge: the move buttons are disabled at both ends', async () => { + h.query.data = testProfile(['a', 'b', 'c']); + h.query.isPending = false; + + const { renderer, unmount } = await mountScreen(); + + expect(pressableDisabled(renderer.root, 'Move up', 0)).toBe(true); + expect(pressableDisabled(renderer.root, 'Move down', 0)).toBe(false); + expect(pressableDisabled(renderer.root, 'Move down', 2)).toBe(true); + expect(pressableDisabled(renderer.root, 'Move up', 2)).toBe(false); + + unmount(); + }); + + it('happy: deleting a row persists the remaining list', async () => { + h.query.data = testProfile(['a', 'b']); + h.query.isPending = false; + h.mutations.setCommands.mutateAsync.mockResolvedValue({ success: true }); + + const { renderer, unmount } = await mountScreen(); + pressPressable(renderer.root, 'Delete', 0); + + await waitFor(() => h.mutations.setCommands.mutateAsync.mock.calls.length > 0); + expect(h.mutations.setCommands.mutateAsync.mock.calls[0]?.[0]).toEqual({ + profileId: 'profile-1', + commands: ['b'], + }); + + unmount(); + }); + + it('never persists the blank draft row add() opens', async () => { + h.query.data = testProfile(['a']); + h.query.isPending = false; + h.mutations.setCommands.mutateAsync.mockResolvedValue({ success: true }); + + const { renderer, unmount } = await mountScreen(); + const addButton = findOne(renderer.root, 'Button'); + act(() => { + (addButton.props as { onPress: () => void }).onPress(); + }); + pressPressable(renderer.root, 'Delete', 0); + + await waitFor(() => h.mutations.setCommands.mutateAsync.mock.calls.length > 0); + expect(h.mutations.setCommands.mutateAsync.mock.calls[0]?.[0]).toEqual({ + profileId: 'profile-1', + commands: [], + }); + + unmount(); + }); + + it('retryable: a failure with no message toasts the fallback and keeps the edit', async () => { + h.query.data = testProfile(['a']); + h.query.isPending = false; + const error = new Error('placeholder'); + error.message = ''; + h.mutations.setCommands.mutateAsync.mockRejectedValue(error); + + const { renderer, unmount } = await mountScreen(); + changeRowText(renderer.root, 0, 'pnpm install'); + commitRow(renderer.root, 0); + + await waitFor(() => h.error.mock.calls.length > 0); + expect(h.error).toHaveBeenCalledWith("Couldn't save setup commands"); + expect(findFields(renderer.root)[0]?.props.defaultValue).toBe('pnpm install'); + + unmount(); + }); + + it('preserves edits typed while the saved list refetches', async () => { + h.query.data = testProfile(['a']); + h.query.isPending = false; + h.mutations.setCommands.mutateAsync.mockResolvedValue({ success: true }); + const { renderer, queryClient, unmount } = await mountScreen(); + changeRowText(renderer.root, 0, 'saved'); + commitRow(renderer.root, 0); + changeRowText(renderer.root, 0, 'new draft'); + h.query.data = testProfile(['saved']); + act(() => { + rerenderScreen(renderer, queryClient); + }); + expect(findFields(renderer.root)[0]?.props.nativeText).toBe('new draft'); + commitRow(renderer.root, 0); + expect(h.mutations.setCommands.mutateAsync).toHaveBeenLastCalledWith({ + profileId: 'profile-1', + commands: ['new draft'], + }); + unmount(); + }); + + it('remounts clean uncontrolled fields when refreshed server values change', async () => { + h.query.data = testProfile(['old']); + h.query.isPending = false; + const { renderer, queryClient, unmount } = await mountScreen(); + h.query.data = testProfile(['remote edit']); + act(() => { + rerenderScreen(renderer, queryClient); + }); + expect(findFields(renderer.root)[0]?.props.nativeText).toBe('remote edit'); + unmount(); + }); + + it('ignores a stale refetch until a pending save settles without discarding newer text', async () => { + h.query.data = testProfile(['old']); + h.query.isPending = false; + const pending = Promise.withResolvers<{ success: boolean }>(); + h.mutations.setCommands.mutateAsync.mockReturnValueOnce(pending.promise); + const { renderer, queryClient, unmount } = await mountScreen(); + changeRowText(renderer.root, 0, 'submitted'); + commitRow(renderer.root, 0); + h.mutations.setCommands.isPending = true; + changeRowText(renderer.root, 0, 'newer draft'); + h.query.data = testProfile(['old']); + act(() => { + rerenderScreen(renderer, queryClient); + }); + expect(findFields(renderer.root)[0]?.props.nativeText).toBe('newer draft'); + await act(async () => { + pending.resolve({ success: true }); + await pending.promise; + }); + h.mutations.setCommands.isPending = false; + h.query.data = testProfile(['submitted']); + act(() => { + rerenderScreen(renderer, queryClient); + }); + commitRow(renderer.root, 0); + expect(h.mutations.setCommands.mutateAsync).toHaveBeenLastCalledWith({ + profileId: 'profile-1', + commands: ['newer draft'], + }); + unmount(); + }); + + it('keeps the rows while a refetch is in flight', async () => { + h.query.data = testProfile(['pnpm install']); + h.query.isPending = false; + + const { renderer, queryClient, unmount } = await mountScreen(); + + h.query.isRefetching = true; + await act(async () => { + rerenderScreen(renderer, queryClient); + await Promise.resolve(); + }); + + expect(findFields(renderer.root)[0]?.props.defaultValue).toBe('pnpm install'); + + unmount(); + }); +}); diff --git a/apps/mobile/src/components/profiles/profile-commands-screen.test-helpers.ts b/apps/mobile/src/components/profiles/profile-commands-screen.test-helpers.ts new file mode 100644 index 00000000000..13a8fae44bd --- /dev/null +++ b/apps/mobile/src/components/profiles/profile-commands-screen.test-helpers.ts @@ -0,0 +1,103 @@ +import { createElement } from 'react'; +import { type QueryClient, QueryClientProvider } from '@tanstack/react-query'; + +import { ProfileCommandsScreen } from '@/components/profiles/profile-commands-screen'; +import { act, type ReactTestInstance, type ReactTestRenderer } from '@/test/renderer'; +import { renderWithProviders } from '@/test/render-with-providers'; + +/** + * Shared harness for the Profile Setup Commands mounted tests. The `vi.mock` + * calls stay in the test file (they must be hoisted there); everything that + * does not depend on the hoisted mock state lives here. + */ + +type TestCommand = { sequence: number; command: string }; + +/** The profile detail the mocked `useAgentProfile` returns. */ +export type TestProfileDetail = { id: string; commands: TestCommand[] }; + +export function testProfile(commands: string[] = []): TestProfileDetail { + return { + id: 'profile-1', + commands: commands.map((command, sequence) => ({ sequence, command })), + }; +} + +export function findAll(root: ReactTestInstance, type: string): ReactTestInstance[] { + return root.findAll(node => typeof node.type === 'string' && node.type === type); +} + +export function findOne(root: ReactTestInstance, type: string): ReactTestInstance { + const node = findAll(root, type)[0]; + if (!node) { + throw new Error(`${type} was not rendered`); + } + return node; +} + +export function findFields(root: ReactTestInstance): ReactTestInstance[] { + return findAll(root, 'FormField'); +} + +function findPressable(root: ReactTestInstance, label: string, index: number): ReactTestInstance { + const match = findAll(root, 'Pressable').filter(node => node.props.accessibilityLabel === label)[ + index + ]; + if (!match) { + throw new Error(`pressable ${label} at ${index} was not rendered`); + } + return match; +} + +export function pressPressable(root: ReactTestInstance, label: string, index = 0): void { + const pressable = findPressable(root, label, index); + act(() => { + (pressable.props as { onPress: () => void }).onPress(); + }); +} + +export function pressableDisabled(root: ReactTestInstance, label: string, index = 0): boolean { + return findPressable(root, label, index).props.disabled === true; +} + +export function changeRowText(root: ReactTestInstance, index: number, value: string): void { + const field = findFields(root)[index]; + if (!field) { + throw new Error(`row ${index} was not rendered`); + } + act(() => { + (field.props as { onChangeText: (v: string) => void }).onChangeText(value); + }); +} + +/** Commit a row the way the keyboard's return key does. */ +export function commitRow(root: ReactTestInstance, index: number): void { + const field = findFields(root)[index]; + if (!field) { + throw new Error(`row ${index} was not rendered`); + } + act(() => { + (field.props as { onSubmitEditing: () => void }).onSubmitEditing(); + }); +} + +// eslint-disable-next-line typescript-eslint/promise-function-async -- returning the harness promise unchanged +export function mountScreen(organizationId?: string) { + return renderWithProviders( + createElement(ProfileCommandsScreen, { profileId: 'profile-1', organizationId }) + ); +} + +export function rerenderScreen( + renderer: ReactTestRenderer, + queryClient: QueryClient, + organizationId?: string +): void { + renderer.update( + createElement( + QueryClientProvider, + { client: queryClient }, + createElement(ProfileCommandsScreen, { profileId: 'profile-1', organizationId }) + ) + ); +} diff --git a/apps/mobile/src/components/profiles/profile-commands-screen.tsx b/apps/mobile/src/components/profiles/profile-commands-screen.tsx new file mode 100644 index 00000000000..def7cc6edf3 --- /dev/null +++ b/apps/mobile/src/components/profiles/profile-commands-screen.tsx @@ -0,0 +1,278 @@ +import { type ReactNode, useMemo, useRef, useState } from 'react'; +import { useTranslation } from 'react-i18next'; +import { Pressable, ScrollView, View } from 'react-native'; +import { toast } from 'sonner-native'; + +import { EmptyState } from '@/components/empty-state'; +import { + addCommand, + commandRowA11yLabel, + MAX_SETUP_COMMAND_LENGTH, + MAX_SETUP_COMMANDS, + moveCommand, + removeCommand, + replaceCommand, +} from '@/components/profiles/profile-commands-model'; +import { QueryError } from '@/components/query-error'; +import { ScreenHeader } from '@/components/screen-header'; +import { Button } from '@/components/ui/button'; +import { FormField } from '@/components/ui/form-field'; +import { ChevronDown, ChevronUp, Terminal, Trash2 } from '@/components/ui/icons'; +import { Skeleton } from '@/components/ui/skeleton'; +import { Text } from '@/components/ui/text'; +import { useAgentProfile, useAgentProfileMutations } from '@/lib/hooks/use-agent-profiles'; +import { useThemeColors } from '@/lib/hooks/use-theme-colors'; +import { cn } from '@/lib/utils'; + +type CommandRow = Readonly<{ sequence: number; command: string }>; + +/** Stable empty fallback so the derived list is not a new array each render. */ +const NO_COMMAND_ROWS: readonly CommandRow[] = []; + +/** + * The mutation hook toasts `error.message`; the screen only supplies a + * fallback when the server sent nothing readable. + */ +function hasUsableMessage(error: unknown): boolean { + return error instanceof Error && error.message.trim().length > 0; +} + +function sameCommands(a: readonly string[], b: readonly string[]): boolean { + return a.length === b.length && a.every((value, index) => value === b[index]); +} + +/** + * The payload `setCommands` stores. A blank row is the draft slot `add()` + * opens, not a command, so whitespace-only entries never reach the server. + */ +function nonBlankCommands(commands: readonly string[]): string[] { + return commands.filter(command => command.trim().length > 0); +} + +/** Content-shaped cards in the same slots as a loaded command row. */ +function CommandsSkeleton() { + return ( + + {[0, 1, 2].map(index => ( + + + + + + + + ))} + + ); +} + +export function ProfileCommandsScreen({ + profileId, + organizationId, +}: Readonly<{ profileId: string; organizationId?: string }>) { + const { t } = useTranslation(); + const colors = useThemeColors(); + const profileQuery = useAgentProfile(profileId, organizationId); + const { setCommands: saveCommands } = useAgentProfileMutations(organizationId); + + const profile = profileQuery.data; + const serverRows = profile?.commands ?? NO_COMMAND_ROWS; + const serverCommands = useMemo(() => serverRows.map(row => row.command), [serverRows]); + + // The screen owns the ordered list — `setCommands` replaces the whole list, + // so a move/delete/edit works on a local copy. Refetches may replace a clean + // list, but must not discard a draft typed after the last submitted payload. + const [commands, setCommands] = useState(serverCommands); + const [syncedCommands, setSyncedCommands] = useState(serverCommands); + const persistedRef = useRef(serverCommands); + // Remount uncontrolled fields for reorders and accepted server changes; + // their text belongs to the position, not the input instance. + const [generation, setGeneration] = useState(0); + if (serverCommands !== syncedCommands && !saveCommands.isPending) { + setSyncedCommands(serverCommands); + if (sameCommands(commands, persistedRef.current) && !sameCommands(commands, serverCommands)) { + setCommands(serverCommands); + setGeneration(current => current + 1); + } + persistedRef.current = serverCommands; + } + + const persist = async (next: string[]) => { + const payload = nonBlankCommands(next); + // `setCommands` replaces the whole list, so an unchanged payload has + // nothing to send — e.g. moving a draft row that carries no text. + if (sameCommands(payload, persistedRef.current)) { + return; + } + const previous = persistedRef.current; + // Mark the list persisted before the await: a submit followed by a blur + // (or the reverse) must not send the same list twice. + persistedRef.current = payload; + try { + await saveCommands.mutateAsync({ profileId, commands: payload }); + } catch (error) { + persistedRef.current = previous; + if (!hasUsableMessage(error)) { + toast.error(t('profiles.commandsSaveFailed')); + } + } + }; + + const commit = (index: number) => { + const value = commands[index]?.trim() ?? ''; + if (value.length === 0) { + // A blank draft row is not a command: leave it local until it has text. + return; + } + if (sameCommands(nonBlankCommands(commands), persistedRef.current)) { + return; + } + void persist(commands); + }; + + const move = (index: number, delta: number) => { + const next = moveCommand(commands, index, delta); + if (sameCommands(next, commands)) { + return; + } + setCommands(next); + setGeneration(current => current + 1); + void persist(next); + }; + + const remove = (index: number) => { + const next = removeCommand(commands, index); + if (next.length === commands.length) { + return; + } + setCommands(next); + setGeneration(current => current + 1); + void persist(next); + }; + + const add = () => { + setCommands(addCommand(commands)); + setGeneration(current => current + 1); + }; + + const controlClass = (disabled: boolean) => + cn('h-11 w-11 items-center justify-center active:opacity-70', disabled && 'opacity-40'); + + const renderCommandRow = (command: string, index: number) => { + const isFirst = index === 0; + const isLast = index === commands.length - 1; + return ( + + { + setCommands(current => replaceCommand(current, index, value)); + }} + onEndEditing={() => { + commit(index); + }} + onSubmitEditing={() => { + commit(index); + }} + /> + + { + move(index, -1); + }} + > + + + { + move(index, 1); + }} + > + + + { + remove(index); + }} + > + + + + + ); + }; + + let content: ReactNode = null; + if (profileQuery.isError) { + content = ( + void profileQuery.refetch()} + isRetrying={profileQuery.isRefetching} + /> + ); + } else if (profileQuery.isPending) { + content = ; + } else if (commands.length === 0) { + content = ( + + {t('profiles.addCommand')} + + } + /> + ); + } else { + content = ( + <> + {commands.map((command, index) => renderCommandRow(command, index))} + + + ); + } + + return ( + + + + {content} + + + ); +} diff --git a/apps/mobile/src/components/profiles/profile-kilo-commands-model.test.ts b/apps/mobile/src/components/profiles/profile-kilo-commands-model.test.ts new file mode 100644 index 00000000000..baf7533af1f --- /dev/null +++ b/apps/mobile/src/components/profiles/profile-kilo-commands-model.test.ts @@ -0,0 +1,181 @@ +import { describe, expect, it } from 'vitest'; + +import { + buildKiloCommandCreatePayload, + buildKiloCommandUpdatePayload, + initialKiloCommandFormState, + type KiloCommandFormState, + kiloCommandOrderAfterMove, + kiloCommandRows, + type KiloCommandSource, + validateKiloCommandForm, +} from '@/components/profiles/profile-kilo-commands-model'; + +function source(overrides: Partial = {}): KiloCommandSource { + return { + id: 'cmd-1', + name: 'explain', + description: 'Explains code', + template: 'Explain $ARGUMENTS', + agent: 'code', + model: null, + subtask: true, + enabled: true, + sortOrder: 0, + ...overrides, + }; +} + +describe('kiloCommandRows', () => { + it('projects the row fields and normalizes nulls to empty strings', () => { + expect( + kiloCommandRows([source(), source({ id: 'cmd-2', agent: null, description: null })]) + ).toEqual([ + { + id: 'cmd-1', + name: 'explain', + description: 'Explains code', + template: 'Explain $ARGUMENTS', + subtask: true, + agent: 'code', + model: '', + enabled: true, + }, + { + id: 'cmd-2', + name: 'explain', + description: '', + template: 'Explain $ARGUMENTS', + subtask: true, + agent: '', + model: '', + enabled: true, + }, + ]); + }); +}); + +describe('kiloCommandOrderAfterMove', () => { + const commands = [source({ id: 'a' }), source({ id: 'b' }), source({ id: 'c' })]; + + it('moves an item down and up', () => { + expect(kiloCommandOrderAfterMove(commands, 0, 1)).toEqual(['b', 'a', 'c']); + expect(kiloCommandOrderAfterMove(commands, 2, -1)).toEqual(['a', 'c', 'b']); + }); + + it('clamps at the edges', () => { + expect(kiloCommandOrderAfterMove(commands, 0, -1)).toEqual(['a', 'b', 'c']); + expect(kiloCommandOrderAfterMove(commands, 2, 1)).toEqual(['a', 'b', 'c']); + }); + + it('ignores an out-of-range index', () => { + expect(kiloCommandOrderAfterMove(commands, 9, 1)).toEqual(['a', 'b', 'c']); + }); +}); + +describe('initialKiloCommandFormState', () => { + it('seeds blank defaults for an add', () => { + expect(initialKiloCommandFormState()).toEqual({ + name: '', + description: '', + template: '', + agent: '', + model: '', + subtask: false, + }); + }); + + it('seeds a command with its nullable fields normalized', () => { + expect(initialKiloCommandFormState(source())).toEqual({ + name: 'explain', + description: 'Explains code', + template: 'Explain $ARGUMENTS', + agent: 'code', + model: '', + subtask: true, + }); + }); +}); + +function formState(overrides: Partial = {}): KiloCommandFormState { + return { + name: 'explain', + description: '', + template: 'Explain $ARGUMENTS', + agent: '', + model: '', + subtask: false, + ...overrides, + }; +} + +describe('validateKiloCommandForm', () => { + it('requires a name and a template', () => { + expect(validateKiloCommandForm(formState({ name: ' ' }))).toBe('name-required'); + expect(validateKiloCommandForm(formState({ template: ' ' }))).toBe('template-required'); + }); + + it('refuses a name outside the server pattern', () => { + expect(validateKiloCommandForm(formState({ name: 'Bad Name' }))).toBe('name-invalid'); + expect(validateKiloCommandForm(formState({ name: '1cmd' }))).toBe('name-invalid'); + }); + + it('refuses a name reserved by a built-in command', () => { + expect(validateKiloCommandForm(formState({ name: 'review' }))).toBe('name-conflict'); + }); + + it('accepts a valid command', () => { + expect(validateKiloCommandForm(formState())).toBeNull(); + }); +}); + +describe('buildKiloCommandCreatePayload', () => { + it('omits empty optional fields', () => { + expect(buildKiloCommandCreatePayload(formState())).toEqual({ + name: 'explain', + template: 'Explain $ARGUMENTS', + subtask: false, + }); + }); + + it('trims and includes the optionals when set', () => { + expect( + buildKiloCommandCreatePayload( + formState({ description: ' Explains ', agent: 'code', model: 'x', subtask: true }) + ) + ).toEqual({ + name: 'explain', + description: 'Explains', + template: 'Explain $ARGUMENTS', + agent: 'code', + model: 'x', + subtask: true, + }); + }); +}); + +describe('buildKiloCommandUpdatePayload', () => { + it('sends null for emptied optional fields', () => { + expect(buildKiloCommandUpdatePayload(formState())).toEqual({ + name: 'explain', + description: null, + template: 'Explain $ARGUMENTS', + agent: null, + model: null, + subtask: false, + }); + }); + + it('keeps set optional fields', () => { + expect( + buildKiloCommandUpdatePayload(formState({ description: 'Explains', agent: 'code' })) + ).toEqual({ + name: 'explain', + description: 'Explains', + template: 'Explain $ARGUMENTS', + agent: 'code', + model: null, + subtask: false, + }); + }); +}); diff --git a/apps/mobile/src/components/profiles/profile-kilo-commands-model.ts b/apps/mobile/src/components/profiles/profile-kilo-commands-model.ts new file mode 100644 index 00000000000..b0e2e488cb9 --- /dev/null +++ b/apps/mobile/src/components/profiles/profile-kilo-commands-model.ts @@ -0,0 +1,213 @@ +/** + * Pure view-model helpers for the profile slash (kilo) commands screen. + * + * No React and no React Native imports: every function here is unit-tested + * directly in `profile-kilo-commands-model.test.ts`. The screen owns rendering + * and the network calls; this module owns the row projection, the add/edit form + * state, the client-side validation that runs before a create/update, the + * create-vs-update payload difference, and the up/down reorder the screen + * persists through `reorderKiloCommands`. + * + * Ported from the web editor (`KiloCommandsTab.tsx`): the same reserved command + * names, the same name pattern, and the same "undefined on create, null on + * update" optional-field rule. + */ + +import { moveCommand } from '@/lib/agent-profile-forms'; + +/** + * Built-in command names reserved by the CLI. Mirrors `BUILTIN_COMMAND_NAMES` + * in `@kilocode/cloud-agent-profile`; the mobile app does not depend on that + * server package, so the set is repeated here. The server refuses a create or + * rename that collides, so the form refuses it first. + */ +const BUILTIN_COMMAND_NAMES = new Set([ + 'init', + 'review', + 'local-review', + 'local-review-uncommitted', +]); + +const COMMAND_NAME_PATTERN = /^[a-z][a-z0-9-]*$/; + +/** + * Server bounds, mirrored so the form can refuse an over-long value before it + * reaches the API: `kiloCommandNameSchema` caps the name at 50, + * `kiloCommandCreateInputSchema` caps the description at 2000 and the template + * at 100,000 (`packages/cloud-agent-profile/src/profile-kilo-commands-service.ts`). + * The form passes these to the fields' `maxLength`, matching web's + * `KiloCommandsTab` maxLength inputs, so the over-long value is never typed. + */ +export const MAX_KILO_COMMAND_NAME_LENGTH = 50; +export const MAX_KILO_COMMAND_DESCRIPTION_LENGTH = 2000; +export const MAX_KILO_COMMAND_TEMPLATE_LENGTH = 100_000; + +/** The fields of a kilo command the screen reads. Structural, so tests are easy. */ +export type KiloCommandSource = Readonly<{ + id: string; + name: string; + description: string | null; + template: string; + agent: string | null; + model: string | null; + subtask: boolean; + enabled: boolean; + sortOrder: number; +}>; + +/** One slash command row as the screen renders it. */ +export type KiloCommandRow = Readonly<{ + id: string; + name: string; + description: string; + template: string; + subtask: boolean; + agent: string; + model: string; + enabled: boolean; +}>; + +/** Project the profile's kilo commands into the rows the list renders. */ +export function kiloCommandRows(commands: readonly KiloCommandSource[]): KiloCommandRow[] { + return commands.map(command => ({ + id: command.id, + name: command.name, + description: command.description ?? '', + template: command.template, + subtask: command.subtask, + agent: command.agent ?? '', + model: command.model ?? '', + enabled: command.enabled, + })); +} + +/** + * The ordered ids after moving the command at `index` by `delta`, clamped to + * the list's edges. Reuses the s2 list move the setup-commands screen uses, so + * both reorder surfaces behave identically. The screen sends the whole order to + * `reorderKiloCommands`; order is the only state. + */ +export function kiloCommandOrderAfterMove( + commands: readonly KiloCommandSource[], + index: number, + delta: number +): string[] { + return moveCommand( + commands.map(command => command.id), + index, + delta + ); +} + +/** The add/edit form fields. */ +export type KiloCommandFormState = Readonly<{ + name: string; + description: string; + template: string; + agent: string; + model: string; + subtask: boolean; +}>; + +/** Seed the form from the command being edited, or blank defaults for an add. */ +export function initialKiloCommandFormState(command?: KiloCommandSource): KiloCommandFormState { + if (command === undefined) { + return { name: '', description: '', template: '', agent: '', model: '', subtask: false }; + } + return { + name: command.name, + description: command.description ?? '', + template: command.template, + agent: command.agent ?? '', + model: command.model ?? '', + subtask: command.subtask, + }; +} + +export type KiloCommandFormError = + | 'name-required' + | 'name-invalid' + | 'name-conflict' + | 'template-required'; + +/** + * Validate the form before a save. Returns the field at fault, or `null` when + * valid. Mirrors the server's `kiloCommandNameSchema` and template minimum: a + * non-empty name that starts with a lowercase letter, stays in `[a-z0-9-]`, + * does not collide with a built-in command, and a non-empty template. The + * server's name/template/description length bounds are enforced by the form + * fields' `maxLength` (see the `MAX_KILO_COMMAND_*` constants), so an over-long + * value cannot be typed in the first place. + */ +export function validateKiloCommandForm(state: KiloCommandFormState): KiloCommandFormError | null { + const name = state.name.trim(); + if (name.length === 0) { + return 'name-required'; + } + if (!COMMAND_NAME_PATTERN.test(name)) { + return 'name-invalid'; + } + if (BUILTIN_COMMAND_NAMES.has(name)) { + return 'name-conflict'; + } + if (state.template.trim().length === 0) { + return 'template-required'; + } + return null; +} + +/** The create payload: optional fields are omitted so the DB default applies. */ +export type KiloCommandCreatePayload = { + name: string; + description?: string; + template: string; + agent?: string; + model?: string; + subtask: boolean; +}; + +/** The update payload: optional fields are `null` so an emptied field clears. */ +export type KiloCommandUpdatePayload = { + name: string; + description: string | null; + template: string; + agent: string | null; + model: string | null; + subtask: boolean; +}; + +function optional(value: string): string | undefined { + const trimmed = value.trim(); + return trimmed.length === 0 ? undefined : trimmed; +} + +/** Build the create payload from a validated form. */ +export function buildKiloCommandCreatePayload( + state: KiloCommandFormState +): KiloCommandCreatePayload { + const description = optional(state.description); + const agent = optional(state.agent); + const model = optional(state.model); + return { + name: state.name.trim(), + ...(description === undefined ? {} : { description }), + template: state.template, + ...(agent === undefined ? {} : { agent }), + ...(model === undefined ? {} : { model }), + subtask: state.subtask, + }; +} + +/** Build the update payload from a validated form; empty optionals become `null`. */ +export function buildKiloCommandUpdatePayload( + state: KiloCommandFormState +): KiloCommandUpdatePayload { + return { + name: state.name.trim(), + description: optional(state.description) ?? null, + template: state.template, + agent: optional(state.agent) ?? null, + model: optional(state.model) ?? null, + subtask: state.subtask, + }; +} diff --git a/apps/mobile/src/components/profiles/profile-kilo-commands-screen.tsx b/apps/mobile/src/components/profiles/profile-kilo-commands-screen.tsx new file mode 100644 index 00000000000..c6733b36d5c --- /dev/null +++ b/apps/mobile/src/components/profiles/profile-kilo-commands-screen.tsx @@ -0,0 +1,252 @@ +import { type ReactNode, useState } from 'react'; +import { useTranslation } from 'react-i18next'; +import { Alert, ScrollView, View } from 'react-native'; +import { toast } from 'sonner-native'; + +import { EmptyState } from '@/components/empty-state'; +import { + KiloCommandFormSheet, + type KiloCommandSubmission, +} from '@/components/profiles/kilo-command-form-sheet'; +import { KiloCommandRowView } from '@/components/profiles/kilo-command-row'; +import { + kiloCommandOrderAfterMove, + kiloCommandRows, + type KiloCommandSource, + type KiloCommandUpdatePayload, +} from '@/components/profiles/profile-kilo-commands-model'; +import { QueryError } from '@/components/query-error'; +import { ScreenHeader } from '@/components/screen-header'; +import { Button } from '@/components/ui/button'; +import { Terminal } from '@/components/ui/icons'; +import { Skeleton } from '@/components/ui/skeleton'; +import { Text } from '@/components/ui/text'; +import { + type AgentProfileDetail, + useAgentProfile, + useAgentProfileSectionMutations, +} from '@/lib/hooks/use-agent-profiles'; + +/** Stable empty fallback so the derived list is not a new array each render. */ +const NO_COMMANDS: AgentProfileDetail['kiloCommands'] = []; + +/** The mutation hook toasts `error.message`; the screen supplies a fallback otherwise. */ +function hasUsableMessage(error: unknown): boolean { + return error instanceof Error && error.message.trim().length > 0; +} + +/** Content-shaped rows in the same slots as a loaded command row. */ +function SlashCommandsSkeleton() { + return ( + + {[0, 1, 2].map(index => ( + + ))} + + ); +} + +export function ProfileKiloCommandsScreen({ + profileId, + organizationId, +}: Readonly<{ profileId: string; organizationId?: string }>) { + const { t } = useTranslation(); + const profileQuery = useAgentProfile(profileId, organizationId); + const { + createKiloCommand, + updateKiloCommand, + deleteKiloCommand, + setKiloCommandEnabled, + reorderKiloCommands, + } = useAgentProfileSectionMutations(organizationId); + + const commands = profileQuery.data?.kiloCommands ?? NO_COMMANDS; + const rows = kiloCommandRows(commands); + // `null` = closed, `{ source: null }` = add, `{ source }` = edit that command. + const [form, setForm] = useState<{ source: KiloCommandSource | null } | null>(null); + + const startAdd = () => { + setForm({ source: null }); + }; + + const closeForm = () => { + setForm(null); + }; + + const runCreate = async (payload: KiloCommandSubmission) => { + try { + await createKiloCommand.mutateAsync({ profileId, ...payload }); + } catch (error) { + if (!hasUsableMessage(error)) { + toast.error(t('profiles.slashCommands.saveFailed')); + } + return; + } + closeForm(); + }; + + const runUpdate = async (commandId: string, payload: KiloCommandUpdatePayload) => { + try { + await updateKiloCommand.mutateAsync({ profileId, commandId, ...payload }); + } catch (error) { + if (!hasUsableMessage(error)) { + toast.error(t('profiles.slashCommands.saveFailed')); + } + return; + } + closeForm(); + }; + + const runDelete = async (commandId: string) => { + try { + await deleteKiloCommand.mutateAsync({ profileId, commandId }); + } catch (error) { + if (!hasUsableMessage(error)) { + toast.error(t('profiles.slashCommands.saveFailed')); + } + } + }; + + const confirmDelete = (source: KiloCommandSource) => { + Alert.alert(t('common.delete'), source.name, [ + { text: t('common.cancel'), style: 'cancel' }, + { + text: t('common.delete'), + style: 'destructive', + onPress: () => { + void runDelete(source.id); + }, + }, + ]); + }; + + const move = (index: number, delta: number) => { + const orderedIds = kiloCommandOrderAfterMove(commands, index, delta); + const unchanged = orderedIds.every((id, at) => commands[at]?.id === id); + if (unchanged) { + return; + } + reorderKiloCommands.mutate( + { profileId, orderedIds }, + { + onError: error => { + if (!hasUsableMessage(error)) { + toast.error(t('profiles.slashCommands.saveFailed')); + } + }, + } + ); + }; + + let content: ReactNode = null; + if (profileQuery.isError) { + content = ( + void profileQuery.refetch()} + isRetrying={profileQuery.isRefetching} + /> + ); + } else if (profileQuery.isPending) { + content = ; + } else if (rows.length === 0) { + content = ( + + {t('profiles.slashCommands.add')} + + } + /> + ); + } else { + content = ( + <> + {rows.map((row, index) => { + const source = commands[index]; + if (source === undefined) { + return null; + } + return ( + { + setKiloCommandEnabled.mutate( + { profileId, commandId: source.id, enabled }, + { + onError: error => { + if (!hasUsableMessage(error)) { + toast.error(t('profiles.slashCommands.saveFailed')); + } + }, + } + ); + }} + onMoveUp={() => { + move(index, -1); + }} + onMoveDown={() => { + move(index, 1); + }} + onEdit={() => { + setForm({ source }); + }} + onDelete={() => { + confirmDelete(source); + }} + /> + ); + })} + + + ); + } + + return ( + + + + {content} + + + {form !== null ? ( + { + void runCreate(payload); + }} + onUpdate={payload => { + if (form.source !== null) { + void runUpdate(form.source.id, payload); + } + }} + /> + ) : null} + + ); +} diff --git a/apps/mobile/src/components/profiles/profile-list-model.test.ts b/apps/mobile/src/components/profiles/profile-list-model.test.ts new file mode 100644 index 00000000000..95baaa06272 --- /dev/null +++ b/apps/mobile/src/components/profiles/profile-list-model.test.ts @@ -0,0 +1,135 @@ +import { describe, expect, it } from 'vitest'; + +import { + buildProfileSections, + isEffectiveDefault, + profileCounts, + profileListCountItems, +} from '@/components/profiles/profile-list-model'; + +type TestProfile = { + id: string; + name: string; + isDefault: boolean; + varCount: number; + commandCount: number; + mcpServerCount: number; + skillCount: number; +}; + +function profile(overrides: Partial & { id: string }): TestProfile { + return { + name: 'Backend debugging', + isDefault: false, + varCount: 0, + commandCount: 0, + mcpServerCount: 0, + skillCount: 0, + ...overrides, + }; +} + +const orgProfile = profile({ id: 'org-profile' }); +const personalProfile = profile({ id: 'personal-profile' }); + +describe('buildProfileSections', () => { + it('renders a personal context as one untitled section', () => { + const sections = buildProfileSections({ + orgProfiles: [], + personalProfiles: [personalProfile], + isOrgContext: false, + }); + + expect(sections).toEqual([{ key: 'personal', profiles: [personalProfile] }]); + expect(sections[0]?.titleKey).toBeUndefined(); + }); + + it('orders an organization context Organization then Personal', () => { + const sections = buildProfileSections({ + orgProfiles: [orgProfile], + personalProfiles: [personalProfile], + isOrgContext: true, + }); + + expect(sections.map(section => section.key)).toEqual(['organization', 'personal']); + expect(sections.map(section => section.titleKey)).toEqual([ + 'profiles.list.organizationHeading', + 'profiles.list.personalHeading', + ]); + expect(sections[0]?.profiles).toEqual([orgProfile]); + expect(sections[1]?.profiles).toEqual([personalProfile]); + }); + + it('drops an empty group so no section header dangles', () => { + const sections = buildProfileSections({ + orgProfiles: [], + personalProfiles: [personalProfile], + isOrgContext: true, + }); + + expect(sections).toEqual([ + { key: 'personal', titleKey: 'profiles.list.personalHeading', profiles: [personalProfile] }, + ]); + }); + + it('returns no sections when the context has no profiles', () => { + expect( + buildProfileSections({ orgProfiles: [], personalProfiles: [], isOrgContext: false }) + ).toEqual([]); + expect( + buildProfileSections({ orgProfiles: [], personalProfiles: [], isOrgContext: true }) + ).toEqual([]); + }); +}); + +describe('isEffectiveDefault', () => { + it('marks only the profile that matches the resolved effective default', () => { + expect(isEffectiveDefault(personalProfile, 'personal-profile')).toBe(true); + expect(isEffectiveDefault(orgProfile, 'personal-profile')).toBe(false); + }); + + it('lets a personal default win over an org default', () => { + // The server resolves `effectiveDefaultId` as personal default ?? org + // default; both profiles carry `isDefault` locally. + const orgDefault = profile({ id: 'org-default', isDefault: true }); + const personalDefault = profile({ id: 'personal-default', isDefault: true }); + const effectiveDefaultId = personalDefault.id; + + expect(isEffectiveDefault(personalDefault, effectiveDefaultId)).toBe(true); + expect(isEffectiveDefault(orgDefault, effectiveDefaultId)).toBe(false); + }); + + it('falls back to the profile flag when no default is resolved', () => { + expect(isEffectiveDefault(profile({ id: 'a', isDefault: true }), null)).toBe(true); + expect(isEffectiveDefault(profile({ id: 'b' }), null)).toBe(false); + }); +}); + +describe('profileCounts', () => { + it('returns the var / MCP / skill triple, not setup commands', () => { + expect( + profileCounts( + profile({ id: 'a', varCount: 3, commandCount: 9, mcpServerCount: 2, skillCount: 1 }) + ) + ).toEqual({ varCount: 3, mcpServerCount: 2, skillCount: 1 }); + }); +}); + +describe('profileListCountItems', () => { + it('resolves the non-zero counts in web order', () => { + expect( + profileListCountItems(profile({ id: 'a', varCount: 3, mcpServerCount: 2, skillCount: 1 })) + ).toEqual([ + { kind: 'vars', count: 3 }, + { kind: 'mcp', count: 2 }, + { kind: 'skills', count: 1 }, + ]); + expect(profileListCountItems(profile({ id: 'a', mcpServerCount: 2 }))).toEqual([ + { kind: 'mcp', count: 2 }, + ]); + }); + + it('resolves nothing when every count is zero', () => { + expect(profileListCountItems(profile({ id: 'a', commandCount: 2 }))).toEqual([]); + }); +}); diff --git a/apps/mobile/src/components/profiles/profile-list-model.ts b/apps/mobile/src/components/profiles/profile-list-model.ts new file mode 100644 index 00000000000..11c46402f46 --- /dev/null +++ b/apps/mobile/src/components/profiles/profile-list-model.ts @@ -0,0 +1,113 @@ +import { type ProfileCountItem } from '@/lib/profile-count-labels'; +import { type AgentProfileListItem } from '@/lib/hooks/agent-profile-types'; + +/** + * Pure view-model helpers for the mobile profile manager. + * + * No React and no React Native imports: every function here is unit-tested + * directly in `profile-list-model.test.ts`. The screens own rendering; this + * module owns the section ordering, the effective-default rule, and the + * compact counts summary the row subtitle shows. + */ + +type ProfileSectionKey = 'organization' | 'personal'; + +/** The three summary counts the list row shows, mirroring the server's `ProfileSummarySchema`. */ +export type ProfileCounts = Pick< + AgentProfileListItem, + 'varCount' | 'mcpServerCount' | 'skillCount' +>; + +export type ProfileDefaultSource = { + id: string; + isDefault: boolean; +}; + +export type ProfileSection = { + key: ProfileSectionKey; + /** + * Catalog key for the section header. Absent for the single untitled + * section a personal-context list renders; the screen renders it through + * `t()` with the reviewed English heading as the fallback. + */ + titleKey?: string; + profiles: TProfile[]; +}; + +/** + * Group profiles into the sections the list renders. A personal context is a + * single untitled section; an organization context shows Organization then + * Personal. Empty groups are dropped so the screen never renders a dangling + * header, and an empty result is the screen's empty state. + */ +export function buildProfileSections({ + orgProfiles, + personalProfiles, + isOrgContext, +}: Readonly<{ + orgProfiles: readonly TProfile[]; + personalProfiles: readonly TProfile[]; + isOrgContext: boolean; +}>): ProfileSection[] { + if (!isOrgContext) { + return personalProfiles.length > 0 + ? [{ key: 'personal', profiles: [...personalProfiles] }] + : []; + } + const sections: ProfileSection[] = []; + if (orgProfiles.length > 0) { + sections.push({ + key: 'organization', + titleKey: 'profiles.list.organizationHeading', + profiles: [...orgProfiles], + }); + } + if (personalProfiles.length > 0) { + sections.push({ + key: 'personal', + titleKey: 'profiles.list.personalHeading', + profiles: [...personalProfiles], + }); + } + return sections; +} + +/** + * Whether `profile` is the profile a session would auto-load. + * + * `effectiveDefaultId` is already resolved by the server + * (`agent-profiles-router.ts` `listCombined`: personal default wins over org + * default) or by `useAgentProfileList` for a personal context. When it is null + * no default is resolved, so fall back to the profile's own flag — the same + * rule the web list uses for a personal context. + */ +export function isEffectiveDefault( + profile: ProfileDefaultSource, + effectiveDefaultId: string | null +): boolean { + return effectiveDefaultId === null ? profile.isDefault : profile.id === effectiveDefaultId; +} + +/** Read the counts triple off a profile summary. */ +export function profileCounts(profile: ProfileCounts): ProfileCounts { + return { + varCount: profile.varCount, + mcpServerCount: profile.mcpServerCount, + skillCount: profile.skillCount, + }; +} + +/** + * The non-zero counts a row subtitle shows, in web order (vars, MCP, + * skills). The caller localizes them with the compact unit suffixes and joins + * them; empty when the profile has nothing configured, so the row renders + * without a subtitle. + */ +export function profileListCountItems(counts: ProfileCounts): ProfileCountItem[] { + const items: (ProfileCountItem | null)[] = [ + counts.varCount > 0 ? { kind: 'vars', count: counts.varCount } : null, + counts.mcpServerCount > 0 ? { kind: 'mcp', count: counts.mcpServerCount } : null, + counts.skillCount > 0 ? { kind: 'skills', count: counts.skillCount } : null, + ]; + return items.filter((item): item is ProfileCountItem => item !== null); +} diff --git a/apps/mobile/src/components/profiles/profile-mcp-model.test.ts b/apps/mobile/src/components/profiles/profile-mcp-model.test.ts new file mode 100644 index 00000000000..8ba385ef048 --- /dev/null +++ b/apps/mobile/src/components/profiles/profile-mcp-model.test.ts @@ -0,0 +1,294 @@ +import { describe, expect, it } from 'vitest'; + +import { MASKED_MCP_VALUE } from '@/components/profiles/mcp-json'; +import { + buildMcpServerPayload, + commandParts, + countSecretValues, + formatCommand, + initialMcpFormState, + type McpFormState, + mcpServerRows, + type McpServerSource, + mcpServerSummary, + validateMcpForm, +} from '@/components/profiles/profile-mcp-model'; + +function localSource(overrides: Partial = {}): McpServerSource { + return { + id: 'mcp-1', + name: 'docs', + type: 'local', + enabled: true, + timeout: null, + config: { command: ['npx', '@example/mcp'], environment: { API_KEY: MASKED_MCP_VALUE } }, + ...overrides, + }; +} + +function remoteSource(overrides: Partial = {}): McpServerSource { + return { + id: 'mcp-2', + name: 'remote', + type: 'remote', + enabled: false, + timeout: 5000, + config: { url: 'https://example.com/mcp', headers: { Authorization: MASKED_MCP_VALUE } }, + ...overrides, + }; +} + +describe('countSecretValues', () => { + it('counts environment keys on a local server', () => { + expect(countSecretValues(localSource())).toBe(1); + }); + + it('counts header keys on a remote server', () => { + expect(countSecretValues(remoteSource())).toBe(1); + }); + + it('is zero when there is no record', () => { + expect(countSecretValues(localSource({ config: { command: ['npx'] } }))).toBe(0); + expect(countSecretValues(remoteSource({ config: { url: 'https://x.test' } }))).toBe(0); + }); +}); + +describe('mcpServerSummary', () => { + it('joins the command line for a local server', () => { + expect(mcpServerSummary(localSource())).toBe('npx @example/mcp'); + }); + + it('uses the URL for a remote server', () => { + expect(mcpServerSummary(remoteSource())).toBe('https://example.com/mcp'); + }); +}); + +describe('mcpServerRows', () => { + it('projects name, type, enabled, summary and secret count', () => { + expect(mcpServerRows([localSource(), remoteSource()])).toEqual([ + { + id: 'mcp-1', + name: 'docs', + type: 'local', + enabled: true, + summary: 'npx @example/mcp', + secretCount: 1, + }, + { + id: 'mcp-2', + name: 'remote', + type: 'remote', + enabled: false, + summary: 'https://example.com/mcp', + secretCount: 1, + }, + ]); + }); +}); + +describe('initialMcpFormState', () => { + it('seeds blank defaults for an add', () => { + expect(initialMcpFormState()).toEqual({ + name: '', + type: 'local', + enabled: true, + command: '', + url: '', + configJson: '', + timeout: '', + }); + }); + + it('seeds a local server with its command line and masked environment', () => { + const state = initialMcpFormState(localSource()); + expect(state.type).toBe('local'); + expect(state.command).toBe('npx @example/mcp'); + expect(state.url).toBe(''); + expect(JSON.parse(state.configJson)).toEqual({ API_KEY: MASKED_MCP_VALUE }); + expect(state.timeout).toBe(''); + }); + + it('seeds a remote server with its url, masked headers and timeout', () => { + const state = initialMcpFormState(remoteSource()); + expect(state.type).toBe('remote'); + expect(state.url).toBe('https://example.com/mcp'); + expect(JSON.parse(state.configJson)).toEqual({ Authorization: MASKED_MCP_VALUE }); + expect(state.timeout).toBe('5000'); + }); + + it('round-trips an unedited command whose argument contains a space', () => { + const source = localSource({ + config: { command: ['tool', '--label=foo bar'] }, + }); + const state = initialMcpFormState(source); + + expect(state.command).toBe('tool "--label=foo bar"'); + expect(buildMcpServerPayload(state)).toEqual({ + type: 'local', + name: 'docs', + enabled: true, + config: { command: ['tool', '--label=foo bar'] }, + }); + }); +}); + +describe('commandParts', () => { + it('splits on whitespace and drops blanks', () => { + expect(commandParts(' npx @example/mcp --flag ')).toEqual([ + 'npx', + '@example/mcp', + '--flag', + ]); + expect(commandParts(' ')).toEqual([]); + }); + + it('keeps a quoted argument together and unescapes it', () => { + expect(commandParts(String.raw`tool "--label=foo bar" "say \"hi\"" ""`)).toEqual([ + 'tool', + '--label=foo bar', + 'say "hi"', + '', + ]); + }); + + it('re-parses every argument formatCommand wrote', () => { + const parts = ['tool', '--label=foo bar', 'say "hi"', '', String.raw`C:\path\to`]; + expect(commandParts(formatCommand(parts))).toEqual(parts); + }); +}); + +function formState(overrides: Partial = {}): McpFormState { + return { + name: 'docs', + type: 'local', + enabled: true, + command: 'npx @example/mcp', + url: '', + configJson: '', + timeout: '', + ...overrides, + }; +} + +/** An environment JSON fragment with `count` entries, for the record bound. */ +function envRecord(count: number): string { + return JSON.stringify( + Object.fromEntries(Array.from({ length: count }, (_, index) => [`K${index}`, 'v'])) + ); +} + +describe('validateMcpForm', () => { + it('accepts a valid local form', () => { + expect(validateMcpForm(formState())).toBeNull(); + }); + + it('requires a name and refuses a name outside the server pattern', () => { + expect(validateMcpForm(formState({ name: ' ' }))).toBe('name-required'); + expect(validateMcpForm(formState({ name: 'bad name' }))).toBe('name-invalid'); + }); + + it('requires a command for a local server', () => { + expect(validateMcpForm(formState({ command: ' ' }))).toBe('command-required'); + }); + + it('requires a parseable url for a remote server', () => { + expect(validateMcpForm(formState({ type: 'remote', url: '' }))).toBe('url-required'); + expect(validateMcpForm(formState({ type: 'remote', url: 'not a url' }))).toBe('url-invalid'); + expect( + validateMcpForm(formState({ type: 'remote', url: 'https://example.com/mcp' })) + ).toBeNull(); + }); + + it('refuses a timeout that is not a bounded integer', () => { + expect(validateMcpForm(formState({ timeout: 'abc' }))).toBe('timeout-invalid'); + expect(validateMcpForm(formState({ timeout: '0' }))).toBe('timeout-invalid'); + expect(validateMcpForm(formState({ timeout: '1.5' }))).toBe('timeout-invalid'); + expect(validateMcpForm(formState({ timeout: '5000' }))).toBeNull(); + }); + + it('refuses an env/header fragment that is not a string record', () => { + expect(validateMcpForm(formState({ configJson: '{' }))).toBe('json-invalid'); + expect(validateMcpForm(formState({ configJson: '{"PORT":8080}' }))).toBe('json-invalid'); + expect(validateMcpForm(formState({ configJson: '{"API_KEY":"x"}' }))).toBeNull(); + }); + + it('refuses a command beyond the server argument count and length bounds', () => { + expect( + validateMcpForm(formState({ command: Array.from({ length: 51 }, () => 'x').join(' ') })) + ).toBe('command-too-long'); + expect(validateMcpForm(formState({ command: 'a'.repeat(501) }))).toBe('command-too-long'); + expect( + validateMcpForm(formState({ command: Array.from({ length: 50 }, () => 'x').join(' ') })) + ).toBeNull(); + expect(validateMcpForm(formState({ command: 'a'.repeat(500) }))).toBeNull(); + }); + + it('refuses a URL beyond the server 2048-character bound', () => { + const url = `https://example.com/${'a'.repeat(2048)}`; + expect(validateMcpForm(formState({ type: 'remote', url }))).toBe('url-too-long'); + expect( + validateMcpForm(formState({ type: 'remote', url: `https://example.com/${'a'.repeat(2000)}` })) + ).toBeNull(); + }); + + it('refuses an env/header record beyond the server entry, key and value bounds', () => { + expect(validateMcpForm(formState({ configJson: envRecord(51) }))).toBe('record-too-large'); + expect(validateMcpForm(formState({ configJson: envRecord(50) }))).toBeNull(); + expect( + validateMcpForm(formState({ configJson: JSON.stringify({ ['k'.repeat(129)]: 'v' }) })) + ).toBe('record-too-large'); + expect( + validateMcpForm(formState({ configJson: JSON.stringify({ K: 'v'.repeat(4097) }) })) + ).toBe('record-too-large'); + expect( + validateMcpForm( + formState({ configJson: JSON.stringify({ ['k'.repeat(128)]: 'v'.repeat(4096) }) }) + ) + ).toBeNull(); + }); +}); + +describe('buildMcpServerPayload', () => { + it('builds a local payload with command, environment and timeout', () => { + expect( + buildMcpServerPayload( + formState({ configJson: '{"API_KEY":"sk-1"}', timeout: '5000', enabled: false }) + ) + ).toEqual({ + type: 'local', + name: 'docs', + enabled: false, + timeout: 5000, + config: { command: ['npx', '@example/mcp'], environment: { API_KEY: 'sk-1' } }, + }); + }); + + it('builds a remote payload with url and headers', () => { + expect( + buildMcpServerPayload( + formState({ + type: 'remote', + url: ' https://example.com/mcp ', + configJson: '{"Authorization":"Bearer x"}', + }) + ) + ).toEqual({ + type: 'remote', + name: 'docs', + enabled: true, + config: { + url: 'https://example.com/mcp', + headers: { Authorization: 'Bearer x' }, + }, + }); + }); + + it('omits environment, headers and timeout when empty', () => { + expect(buildMcpServerPayload(formState())).toEqual({ + type: 'local', + name: 'docs', + enabled: true, + config: { command: ['npx', '@example/mcp'] }, + }); + }); +}); diff --git a/apps/mobile/src/components/profiles/profile-mcp-model.ts b/apps/mobile/src/components/profiles/profile-mcp-model.ts new file mode 100644 index 00000000000..a850b57e548 --- /dev/null +++ b/apps/mobile/src/components/profiles/profile-mcp-model.ts @@ -0,0 +1,331 @@ +/** + * Pure view-model helpers for the profile MCP servers screen. + * + * No React and no React Native imports: every function here is unit-tested + * directly in `profile-mcp-model.test.ts`. The screen owns rendering and the + * network calls; this module owns the row projection, the add/edit form state, + * the client-side validation that runs before a `createMcp`/`updateMcp`, and + * the payload the mutations send. + * + * The source type is structural, so a test needs no tRPC type and the screen + * passes the `agentProfiles.get` result straight in. The payload mirrors the + * server's `mcpServerFullInputSchema`; the screen hands it to the mutation and + * TypeScript checks it against the tRPC input there. + */ + +import { formatRecord, parseRecord } from '@/components/profiles/mcp-json'; + +/** The fields of an MCP server the screen reads. Structural, so tests are easy. */ +export type McpServerSource = Readonly<{ + id: string; + name: string; + type: 'local' | 'remote'; + enabled: boolean; + timeout: number | null; + config: Readonly<{ + command?: readonly string[]; + url?: string; + environment?: Readonly>; + headers?: Readonly>; + }>; +}>; + +/** One MCP server row as the screen renders it. */ +export type McpServerRow = Readonly<{ + id: string; + name: string; + type: 'local' | 'remote'; + enabled: boolean; + /** The command line (local) or URL (remote); empty when the config is odd. */ + summary: string; + /** How many env (local) or header (remote) keys the server carries. */ + secretCount: number; +}>; + +/** + * The number of env/header keys on the server, matching the web editor's + * `countSecretValues`: local servers count `environment`, remote count + * `headers`. GET responses mask every value, so the count is all the row can + * show about them. + */ +export function countSecretValues(server: McpServerSource): number { + if (server.type === 'local') { + return Object.keys(server.config.environment ?? {}).length; + } + return Object.keys(server.config.headers ?? {}).length; +} + +/** The command line (local) or URL (remote) the row shows under the name. */ +export function mcpServerSummary(server: McpServerSource): string { + if (server.type === 'local') { + return formatCommand(server.config.command ?? []); + } + return server.config.url ?? ''; +} + +/** Project the profile's MCP servers into the rows the list renders. */ +export function mcpServerRows(servers: readonly McpServerSource[]): McpServerRow[] { + return servers.map(server => ({ + id: server.id, + name: server.name, + type: server.type, + enabled: server.enabled, + summary: mcpServerSummary(server), + secretCount: countSecretValues(server), + })); +} + +export type McpFormType = 'local' | 'remote'; + +/** The add/edit form fields. All strings; the sheet owns the refs. */ +export type McpFormState = Readonly<{ + name: string; + type: McpFormType; + enabled: boolean; + command: string; + url: string; + configJson: string; + timeout: string; +}>; + +/** + * Seed the form from the server being edited, or blank defaults for an add. + * The masked env/header values round-trip verbatim (`formatRecord`), so the + * user sees every key and rotates only the ones they retype. + */ +export function initialMcpFormState(server?: McpServerSource): McpFormState { + if (server === undefined) { + return { + name: '', + type: 'local', + enabled: true, + command: '', + url: '', + configJson: '', + timeout: '', + }; + } + return { + name: server.name, + type: server.type, + enabled: server.enabled, + command: server.type === 'local' ? formatCommand(server.config.command ?? []) : '', + url: server.type === 'remote' ? (server.config.url ?? '') : '', + configJson: formatRecord( + server.type === 'local' ? server.config.environment : server.config.headers + ), + timeout: server.timeout === null ? '' : String(server.timeout), + }; +} + +/** Server bounds: name pattern and max, command list, timeout range, url, record. */ +const MCP_NAME_PATTERN = /^[a-zA-Z0-9_-]+$/; +const MCP_NAME_MAX_LENGTH = 100; +const MCP_TIMEOUT_MAX = 3_600_000; +const URL_PATTERN = /^[a-z][a-z0-9+.-]*:\/\/\S+$/i; +/** Server bound: `mcpRemoteConfigInputSchema`'s `z.string().url().max(2048)`. */ +const MCP_URL_MAX_LENGTH = 2048; +/** Server bound: `mcpLocalConfigInputSchema`'s `z.array(...).min(1).max(50)`. */ +const MCP_COMMAND_MAX_ARGS = 50; +/** Server bound: each command argument's `z.string().max(500)`. */ +const MCP_COMMAND_ARG_MAX_LENGTH = 500; +/** Server bound: `MAX_MCP_ENV_OR_HEADERS` caps the env/headers record. */ +const MCP_RECORD_MAX_ENTRIES = 50; +/** Server bound: the env/header key's `z.string().max(128)`. */ +const MCP_RECORD_KEY_MAX_LENGTH = 128; +/** Server bound: the env/header value's `z.string().max(4096)`. */ +const MCP_RECORD_VALUE_MAX_LENGTH = 4096; + +export type McpFormError = + | 'name-required' + | 'name-invalid' + | 'command-required' + | 'command-too-long' + | 'url-required' + | 'url-invalid' + | 'url-too-long' + | 'timeout-invalid' + | 'json-invalid' + | 'record-too-large'; + +/** + * Split a command line back into the argument array the server stores. A + * double-quoted run keeps its spaces (and a backslash escapes the next + * character inside it), so `tool "--label=foo bar"` round-trips to two + * arguments instead of three. Anything unquoted splits on whitespace as before. + */ +export function commandParts(command: string): string[] { + const parts: string[] = []; + let current = ''; + let hasToken = false; + let quoted = false; + for (let index = 0; index < command.length; index += 1) { + const char = command[index] ?? ''; + if (quoted && char === '\\' && index + 1 < command.length) { + index += 1; + current += command[index]; + hasToken = true; + } else if (char === '"') { + quoted = !quoted; + // A quoted empty run is a real empty argument, unlike a bare blank run. + hasToken = true; + } else if (!quoted && /\s/.test(char)) { + if (hasToken) { + parts.push(current); + } + current = ''; + hasToken = false; + } else { + current += char; + hasToken = true; + } + } + if (hasToken) { + parts.push(current); + } + return parts; +} + +/** The escaped forms a quoted command argument carries, spelled without escaping. */ +const ESCAPED_BACKSLASH = String.raw`\\`; +const ESCAPED_QUOTE = String.raw`\"`; + +/** + * Render a command array as one editable line. Only an argument that would + * lose its boundary — one holding whitespace, a quote or a backslash, or an + * empty argument — is quoted and escaped, so the common command reads as + * plain text while `commandParts` recovers every argument exactly. + */ +export function formatCommand(parts: readonly string[]): string { + return parts + .map(part => + part.length === 0 || /[\s"\\]/.test(part) + ? `"${part.replaceAll('\\', ESCAPED_BACKSLASH).replaceAll('"', ESCAPED_QUOTE)}"` + : part + ) + .join(' '); +} + +function parseTimeout(raw: string): number | undefined | null { + const trimmed = raw.trim(); + if (trimmed.length === 0) { + return undefined; + } + const value = Number(trimmed); + if (!Number.isInteger(value) || value < 1 || value > MCP_TIMEOUT_MAX) { + return null; + } + return value; +} + +/** + * Validate the form before a save. Returns the field at fault, or `null` when + * valid. Mirrors the server's input schema so an invalid save never leaves the + * device: the name pattern and length, a non-empty command for a local server + * within the argument count/length bounds, a parseable URL within its length + * bound and a non-empty JSON fragment for a remote one, an integer timeout, and + * a `Record` JSON fragment within the entry/key/value bounds + * either way. + */ +export function validateMcpForm(state: McpFormState): McpFormError | null { + const name = state.name.trim(); + if (name.length === 0) { + return 'name-required'; + } + if (name.length > MCP_NAME_MAX_LENGTH || !MCP_NAME_PATTERN.test(name)) { + return 'name-invalid'; + } + if (state.type === 'local') { + const args = commandParts(state.command); + if (args.length === 0) { + return 'command-required'; + } + if ( + args.length > MCP_COMMAND_MAX_ARGS || + args.some(argument => argument.length > MCP_COMMAND_ARG_MAX_LENGTH) + ) { + return 'command-too-long'; + } + } else { + const url = state.url.trim(); + if (url.length === 0) { + return 'url-required'; + } + if (!URL_PATTERN.test(url)) { + return 'url-invalid'; + } + if (url.length > MCP_URL_MAX_LENGTH) { + return 'url-too-long'; + } + } + if (parseTimeout(state.timeout) === null) { + return 'timeout-invalid'; + } + const record = parseRecord(state.configJson); + if (!record.ok) { + return 'json-invalid'; + } + if (record.value !== undefined) { + const entries = Object.entries(record.value); + if ( + entries.length > MCP_RECORD_MAX_ENTRIES || + entries.some( + ([key, value]) => + key.length > MCP_RECORD_KEY_MAX_LENGTH || value.length > MCP_RECORD_VALUE_MAX_LENGTH + ) + ) { + return 'record-too-large'; + } + } + return null; +} + +/** The payload the MCP mutations send; mirrors the server's server input. */ +export type McpServerPayload = + | { + type: 'local'; + name: string; + enabled: boolean; + timeout?: number; + config: { command: string[]; environment?: Record }; + } + | { + type: 'remote'; + name: string; + enabled: boolean; + timeout?: number; + config: { url: string; headers?: Record }; + }; + +/** + * Build the mutation payload from a validated form. Only call after + * `validateMcpForm` returns `null`. + */ +export function buildMcpServerPayload(state: McpFormState): McpServerPayload { + const name = state.name.trim(); + const timeout = parseTimeout(state.timeout); + const record = parseRecord(state.configJson); + const values = record.ok ? record.value : undefined; + if (state.type === 'local') { + return { + type: 'local', + name, + enabled: state.enabled, + ...(timeout == null ? {} : { timeout }), + config: { + command: commandParts(state.command), + ...(values === undefined ? {} : { environment: values }), + }, + }; + } + return { + type: 'remote', + name, + enabled: state.enabled, + ...(timeout == null ? {} : { timeout }), + config: { + url: state.url.trim(), + ...(values === undefined ? {} : { headers: values }), + }, + }; +} diff --git a/apps/mobile/src/components/profiles/profile-mcp-screen.mounted.test.tsx b/apps/mobile/src/components/profiles/profile-mcp-screen.mounted.test.tsx new file mode 100644 index 00000000000..d36608a8a53 --- /dev/null +++ b/apps/mobile/src/components/profiles/profile-mcp-screen.mounted.test.tsx @@ -0,0 +1,317 @@ +import { beforeEach, describe, expect, it, vi } from 'vitest'; + +import '@/i18n'; +import { + changeText, + confirmAlert, + findAll, + findField, + findOne, + mountScreen, + pressEmptyAction, + pressPressable, + pressSheetDone, + rerenderScreen, + testProfile, + testServer, +} from '@/components/profiles/profile-mcp-screen.test-helpers'; +import { act } from '@/test/renderer'; +import { waitFor } from '@/test/render-with-providers'; + +const h = vi.hoisted(() => ({ + alert: vi.fn(), + error: vi.fn(), + query: { + data: undefined as unknown, + isError: false, + isPending: true, + isRefetching: false, + refetch: vi.fn(), + }, + mutations: { + createMcp: { mutateAsync: vi.fn(), isPending: false }, + updateMcp: { mutateAsync: vi.fn(), isPending: false }, + deleteMcp: { mutateAsync: vi.fn(), isPending: false }, + setMcpEnabled: { mutate: vi.fn(), isPending: false }, + }, +})); + +vi.mock('@/lib/hooks/use-agent-profiles', () => ({ + useAgentProfile: () => h.query, + useAgentProfileSectionMutations: () => h.mutations, +})); +vi.mock('sonner-native', () => ({ toast: { error: h.error } })); +vi.mock('react-native', () => ({ + View: 'View', + ScrollView: 'ScrollView', + Pressable: 'Pressable', + Switch: 'Switch', + Alert: { alert: h.alert }, +})); +vi.mock('react-native-safe-area-context', () => ({ + useSafeAreaInsets: () => ({ top: 0, bottom: 0, left: 0, right: 0 }), +})); +vi.mock('@/lib/hooks/use-theme-colors', () => ({ + useThemeColors: () => ({ mutedForeground: '#000000', destructive: '#FF0000' }), +})); +vi.mock('@/components/agents/session-page-sheet', () => ({ + SessionPageSheet: 'SessionPageSheet', +})); +vi.mock('@/components/sheet-header', () => ({ SheetHeader: 'SheetHeader' })); +vi.mock('@/components/screen-header', () => ({ ScreenHeader: 'ScreenHeader' })); +vi.mock('@/components/query-error', () => ({ QueryError: 'QueryError' })); +vi.mock('@/components/empty-state', () => ({ EmptyState: 'EmptyState' })); +vi.mock('@/components/ui/button', () => ({ Button: 'Button' })); +vi.mock('@/components/ui/form-field', () => ({ FormField: 'FormField' })); +vi.mock('@/components/ui/segmented-control', () => ({ SegmentedControl: 'SegmentedControl' })); +vi.mock('@/components/ui/skeleton', () => ({ Skeleton: 'Skeleton' })); +vi.mock('@/components/ui/text', () => ({ Text: 'Text' })); +vi.mock('@/components/ui/icons', () => ({ + Lock: 'Lock', + Pencil: 'Pencil', + Server: 'Server', + Trash2: 'Trash2', +})); + +describe('ProfileMcpScreen', () => { + beforeEach(() => { + vi.clearAllMocks(); + Object.assign(h.query, { + data: undefined, + isError: false, + isPending: true, + isRefetching: false, + }); + for (const mutation of Object.values(h.mutations)) { + Object.assign(mutation, { isPending: false }); + } + }); + + it('loading: renders skeletons and no rows', async () => { + const { renderer, unmount } = await mountScreen(); + + expect(findAll(renderer.root, 'Skeleton').length).toBeGreaterThan(0); + expect(findAll(renderer.root, 'Switch')).toHaveLength(0); + + unmount(); + }); + + it('error: renders QueryError and Retry refetches', async () => { + Object.assign(h.query, { isError: true, isPending: false }); + + const { renderer, unmount } = await mountScreen(); + + act(() => { + (findOne(renderer.root, 'QueryError').props as { onRetry: () => void }).onRetry(); + }); + expect(h.query.refetch).toHaveBeenCalledTimes(1); + + unmount(); + }); + + it('empty: shows the empty copy and the add CTA opens the sheet', async () => { + h.query.data = testProfile(); + h.query.isPending = false; + + const { renderer, unmount } = await mountScreen(); + + expect(findOne(renderer.root, 'EmptyState').props.title).toBe('No MCP servers yet'); + pressEmptyAction(renderer); + expect(findOne(renderer.root, 'SheetHeader')).toBeTruthy(); + + unmount(); + }); + + it('happy: a row shows name, type and summary; the switch toggles the server', async () => { + h.query.data = testProfile([testServer()]); + h.query.isPending = false; + + const { renderer, unmount } = await mountScreen(); + + const texts = findAll(renderer.root, 'Text').map(node => node.props.children); + expect(texts).toContain('docs'); + expect(texts).toContain('Local'); + expect(texts).toContain('npx @example/mcp'); + + const toggle = findOne(renderer.root, 'Switch'); + expect(toggle.props.accessibilityLabel).toBe('docs'); + act(() => { + (toggle.props as { onValueChange: (v: boolean) => void }).onValueChange(false); + }); + expect(h.mutations.setMcpEnabled.mutate.mock.calls[0]?.[0]).toEqual({ + profileId: 'profile-1', + mcpServerId: 'mcp-1', + enabled: false, + }); + + unmount(); + }); + + it('happy: the edit control opens the sheet seeded with the server', async () => { + h.query.data = testProfile([testServer()]); + h.query.isPending = false; + h.mutations.updateMcp.mutateAsync.mockResolvedValue({ success: true }); + + const { renderer, unmount } = await mountScreen(); + pressPressable(renderer.root, 'Edit MCP server'); + + expect(findField(renderer.root, 'Server name').props.defaultValue).toBe('docs'); + expect(findField(renderer.root, 'Command line').props.defaultValue).toBe('npx @example/mcp'); + expect(findAll(renderer.root, 'SegmentedControl')).toHaveLength(0); + + pressSheetDone(renderer.root); + await waitFor(() => h.mutations.updateMcp.mutateAsync.mock.calls.length > 0); + expect(h.mutations.updateMcp.mutateAsync.mock.calls[0]?.[0]).toEqual({ + profileId: 'profile-1', + mcpServerId: 'mcp-1', + server: { + type: 'local', + name: 'docs', + enabled: true, + config: { + command: ['npx', '@example/mcp'], + environment: { API_KEY: '\u2022\u2022\u2022\u2022' }, + }, + }, + }); + + unmount(); + }); + + it('keeps remote type and masked headers unchanged when editing', async () => { + const config = { + url: 'https://example.com/mcp', + headers: { Authorization: '\u2022\u2022\u2022\u2022' }, + }; + h.query.data = testProfile([testServer({ type: 'remote', config })]); + h.query.isPending = false; + h.mutations.updateMcp.mutateAsync.mockResolvedValue({ success: true }); + const { renderer, unmount } = await mountScreen(); + pressPressable(renderer.root, 'Edit MCP server'); + expect(findAll(renderer.root, 'SegmentedControl')).toHaveLength(0); + pressSheetDone(renderer.root); + await waitFor(() => h.mutations.updateMcp.mutateAsync.mock.calls.length > 0); + expect(h.mutations.updateMcp.mutateAsync.mock.calls[0]?.[0]).toMatchObject({ + server: { type: 'remote', config }, + }); + unmount(); + }); + + it('happy: a valid add saves through createMcp and closes the sheet', async () => { + h.query.data = testProfile(); + h.query.isPending = false; + h.mutations.createMcp.mutateAsync.mockResolvedValue({ id: 'mcp-2' }); + + const { renderer, unmount } = await mountScreen(); + pressEmptyAction(renderer); + changeText(renderer.root, 'Server name', 'new-server'); + changeText(renderer.root, 'Command line', 'npx some-mcp'); + pressSheetDone(renderer.root); + + await waitFor(() => h.mutations.createMcp.mutateAsync.mock.calls.length > 0); + expect(h.mutations.createMcp.mutateAsync.mock.calls[0]?.[0]).toEqual({ + profileId: 'profile-1', + server: { + type: 'local', + name: 'new-server', + enabled: true, + config: { command: ['npx', 'some-mcp'] }, + }, + }); + await waitFor(() => findAll(renderer.root, 'SheetHeader').length === 0); + + unmount(); + }); + + it('non-retryable: an empty name is refused with an inline message and nothing persists', async () => { + h.query.data = testProfile(); + h.query.isPending = false; + + const { renderer, unmount } = await mountScreen(); + pressEmptyAction(renderer); + pressSheetDone(renderer.root); + + expect(h.mutations.createMcp.mutateAsync).not.toHaveBeenCalled(); + expect(findField(renderer.root, 'Server name').props.error).toBe('Enter a server name'); + expect(findOne(renderer.root, 'SheetHeader')).toBeTruthy(); + + unmount(); + }); + + it('non-retryable: an invalid URL is refused for a remote server', async () => { + h.query.data = testProfile(); + h.query.isPending = false; + + const { renderer, unmount } = await mountScreen(); + pressEmptyAction(renderer); + act(() => { + const segmented = findOne(renderer.root, 'SegmentedControl'); + (segmented.props as { onChange: (v: string) => void }).onChange('remote'); + }); + changeText(renderer.root, 'Server name', 'new-server'); + changeText(renderer.root, 'URL', 'not a url'); + pressSheetDone(renderer.root); + + expect(h.mutations.createMcp.mutateAsync).not.toHaveBeenCalled(); + expect(findField(renderer.root, 'URL').props.error).toBe('Enter a valid URL'); + + unmount(); + }); + + it('retryable: a failure with no message toasts the fallback and keeps the sheet', async () => { + h.query.data = testProfile(); + h.query.isPending = false; + const error = new Error('placeholder'); + error.message = ''; + h.mutations.createMcp.mutateAsync.mockRejectedValue(error); + + const { renderer, unmount } = await mountScreen(); + pressEmptyAction(renderer); + changeText(renderer.root, 'Server name', 'new-server'); + changeText(renderer.root, 'Command line', 'npx some-mcp'); + pressSheetDone(renderer.root); + + await waitFor(() => h.error.mock.calls.length > 0); + expect(h.error).toHaveBeenCalledWith("Couldn't save MCP server"); + expect(findOne(renderer.root, 'SheetHeader')).toBeTruthy(); + + unmount(); + }); + + it('happy: confirming delete calls deleteMcp', async () => { + h.query.data = testProfile([testServer()]); + h.query.isPending = false; + h.mutations.deleteMcp.mutateAsync.mockResolvedValue({ success: true }); + + const { renderer, unmount } = await mountScreen(); + pressPressable(renderer.root, 'Delete'); + await act(async () => { + confirmAlert(h.alert); + await Promise.resolve(); + }); + + await waitFor(() => h.mutations.deleteMcp.mutateAsync.mock.calls.length > 0); + expect(h.mutations.deleteMcp.mutateAsync.mock.calls[0]?.[0]).toEqual({ + profileId: 'profile-1', + mcpServerId: 'mcp-1', + }); + + unmount(); + }); + + it('keeps the rows while a refetch is in flight', async () => { + h.query.data = testProfile([testServer()]); + h.query.isPending = false; + + const { renderer, queryClient, unmount } = await mountScreen(); + + h.query.isRefetching = true; + await act(async () => { + rerenderScreen(renderer, queryClient); + await Promise.resolve(); + }); + expect(findOne(renderer.root, 'Switch').props.accessibilityLabel).toBe('docs'); + + unmount(); + }); +}); diff --git a/apps/mobile/src/components/profiles/profile-mcp-screen.test-helpers.ts b/apps/mobile/src/components/profiles/profile-mcp-screen.test-helpers.ts new file mode 100644 index 00000000000..c3db1efa052 --- /dev/null +++ b/apps/mobile/src/components/profiles/profile-mcp-screen.test-helpers.ts @@ -0,0 +1,132 @@ +import { type QueryClient, QueryClientProvider } from '@tanstack/react-query'; +import { createElement } from 'react'; + +import { ProfileMcpScreen } from '@/components/profiles/profile-mcp-screen'; +import { act, type ReactTestInstance, type ReactTestRenderer } from '@/test/renderer'; +import { renderWithProviders } from '@/test/render-with-providers'; + +/** + * Shared harness for the Profile MCP mounted tests. The `vi.mock` calls stay in + * the test file (they must be hoisted there); everything that does not depend + * on the hoisted mock state lives here. + */ + +export type TestMcpServer = { + id: string; + name: string; + type: 'local' | 'remote'; + enabled: boolean; + timeout: number | null; + config: { + command?: string[]; + url?: string; + environment?: Record; + headers?: Record; + }; +}; + +export function testProfile(mcpServers: TestMcpServer[] = []) { + return { id: 'profile-1', mcpServers }; +} + +export function testServer(overrides: Partial = {}): TestMcpServer { + return { + id: 'mcp-1', + name: 'docs', + type: 'local', + enabled: true, + timeout: null, + config: { + command: ['npx', '@example/mcp'], + environment: { API_KEY: '\u2022\u2022\u2022\u2022' }, + }, + ...overrides, + }; +} + +export function findAll(root: ReactTestInstance, type: string): ReactTestInstance[] { + return root.findAll(node => typeof node.type === 'string' && node.type === type); +} + +export function findOne(root: ReactTestInstance, type: string): ReactTestInstance { + const node = findAll(root, type)[0]; + if (!node) { + throw new Error(`${type} was not rendered`); + } + return node; +} + +export function findField(root: ReactTestInstance, label: string): ReactTestInstance { + const field = findAll(root, 'FormField').find(node => node.props.label === label); + if (!field) { + throw new Error(`field ${label} was not rendered`); + } + return field; +} + +export function pressPressable(root: ReactTestInstance, label: string): void { + const pressable = findAll(root, 'Pressable').find( + node => node.props.accessibilityLabel === label + ); + if (!pressable) { + throw new Error(`pressable ${label} was not rendered`); + } + act(() => { + (pressable.props as { onPress: () => void }).onPress(); + }); +} + +export function pressEmptyAction(renderer: ReactTestRenderer): void { + const action = findOne(renderer.root, 'EmptyState').props.action as { + props: { onPress: () => void }; + }; + act(() => { + action.props.onPress(); + }); +} + +export function changeText(root: ReactTestInstance, label: string, value: string): void { + act(() => { + (findField(root, label).props as { onChangeText: (v: string) => void }).onChangeText(value); + }); +} + +export function pressSheetDone(root: ReactTestInstance): void { + act(() => { + (findOne(root, 'SheetHeader').props as { onDone: () => void }).onDone(); + }); +} + +type TestAlertMock = { mock: { calls: unknown[][] } }; + +/** Confirm the destructive button of the first Alert the screen raised. */ +export function confirmAlert(alert: TestAlertMock): void { + const buttons = alert.mock.calls[0]?.[2] as { style: string; onPress?: () => void }[] | undefined; + const destructive = buttons?.find(button => button.style === 'destructive'); + if (!destructive?.onPress) { + throw new Error('destructive alert button was not registered'); + } + destructive.onPress(); +} + +// eslint-disable-next-line typescript-eslint/promise-function-async -- returning the harness promise unchanged +export function mountScreen(organizationId?: string) { + return renderWithProviders( + createElement(ProfileMcpScreen, { profileId: 'profile-1', organizationId }) + ); +} + +/** Re-render the mounted screen against fresh mock data, keeping the providers. */ +export function rerenderScreen( + renderer: ReactTestRenderer, + queryClient: QueryClient, + organizationId?: string +): void { + renderer.update( + createElement( + QueryClientProvider, + { client: queryClient }, + createElement(ProfileMcpScreen, { profileId: 'profile-1', organizationId }) + ) + ); +} diff --git a/apps/mobile/src/components/profiles/profile-mcp-screen.tsx b/apps/mobile/src/components/profiles/profile-mcp-screen.tsx new file mode 100644 index 00000000000..f9c3151cd32 --- /dev/null +++ b/apps/mobile/src/components/profiles/profile-mcp-screen.tsx @@ -0,0 +1,273 @@ +import { type ReactNode, useState } from 'react'; +import { useTranslation } from 'react-i18next'; +import { Alert, Pressable, ScrollView, Switch, View } from 'react-native'; +import { toast } from 'sonner-native'; + +import { EmptyState } from '@/components/empty-state'; +import { McpFormSheet } from '@/components/profiles/mcp-form-sheet'; +import { + type McpServerPayload, + mcpServerRows, + type McpServerSource, +} from '@/components/profiles/profile-mcp-model'; +import { QueryError } from '@/components/query-error'; +import { ScreenHeader } from '@/components/screen-header'; +import { Button } from '@/components/ui/button'; +import { Lock, Pencil, Server, Trash2 } from '@/components/ui/icons'; +import { Skeleton } from '@/components/ui/skeleton'; +import { Text } from '@/components/ui/text'; +import { + type AgentProfileDetail, + useAgentProfile, + useAgentProfileSectionMutations, +} from '@/lib/hooks/use-agent-profiles'; +import { useThemeColors } from '@/lib/hooks/use-theme-colors'; + +/** Stable empty fallback so the derived list is not a new array each render. */ +const NO_MCP_SERVERS: AgentProfileDetail['mcpServers'] = []; + +/** The mutation hook toasts `error.message`; the screen supplies a fallback otherwise. */ +function hasUsableMessage(error: unknown): boolean { + return error instanceof Error && error.message.trim().length > 0; +} + +type McpServerRowProps = Readonly<{ + server: McpServerSource; + summary: string; + secretCount: number; + onToggle: (enabled: boolean) => void; + onEdit: () => void; + onDelete: () => void; +}>; + +/** + * One MCP server row: the name in mono, its type, the command/URL summary, the + * enabled switch and a lock badge with the masked env/header key count. The row + * carries no container `accessibilityLabel`, so the switch's label stays the + * only element a screen reader matches by the server's name. + */ +function McpServerRowView({ + server, + summary, + secretCount, + onToggle, + onEdit, + onDelete, +}: McpServerRowProps) { + const { t } = useTranslation(); + const colors = useThemeColors(); + return ( + + + + + {server.name} + + + {server.type === 'local' ? t('profiles.mcp.localType') : t('profiles.mcp.remoteType')} + + {secretCount > 0 ? ( + + + {secretCount} + + ) : null} + + {summary.length > 0 ? ( + + {summary} + + ) : null} + + + {server.enabled ? t('common.enabled') : t('common.disabled')} + + + + + + + + + + ); +} + +/** Content-shaped rows in the same slot and height as a loaded server row. */ +function McpSkeleton() { + return ( + + {[0, 1, 2].map(index => ( + + ))} + + ); +} + +export function ProfileMcpScreen({ + profileId, + organizationId, +}: Readonly<{ profileId: string; organizationId?: string }>) { + const { t } = useTranslation(); + const profileQuery = useAgentProfile(profileId, organizationId); + const { createMcp, updateMcp, deleteMcp, setMcpEnabled } = + useAgentProfileSectionMutations(organizationId); + + const servers = profileQuery.data?.mcpServers ?? NO_MCP_SERVERS; + const rows = mcpServerRows(servers); + // `null` = closed, `{ server: null }` = add, `{ server }` = edit that server. + const [form, setForm] = useState<{ server: McpServerSource | null } | null>(null); + + const startAdd = () => { + setForm({ server: null }); + }; + + const closeForm = () => { + setForm(null); + }; + + const saveServer = async (target: McpServerSource | null, payload: McpServerPayload) => { + try { + await (target === null + ? createMcp.mutateAsync({ profileId, server: payload }) + : updateMcp.mutateAsync({ profileId, mcpServerId: target.id, server: payload })); + } catch (error) { + if (!hasUsableMessage(error)) { + toast.error(t('profiles.mcp.saveFailed')); + } + return; + } + closeForm(); + }; + + const runDelete = async (mcpServerId: string) => { + try { + await deleteMcp.mutateAsync({ profileId, mcpServerId }); + } catch (error) { + if (!hasUsableMessage(error)) { + toast.error(t('profiles.mcp.saveFailed')); + } + } + }; + + const confirmDelete = (target: McpServerSource) => { + Alert.alert(t('common.delete'), target.name, [ + { text: t('common.cancel'), style: 'cancel' }, + { + text: t('common.delete'), + style: 'destructive', + onPress: () => { + void runDelete(target.id); + }, + }, + ]); + }; + + let content: ReactNode = null; + if (profileQuery.isError) { + content = ( + void profileQuery.refetch()} + isRetrying={profileQuery.isRefetching} + /> + ); + } else if (profileQuery.isPending) { + content = ; + } else if (rows.length === 0) { + content = ( + + {t('profiles.mcp.add')} + + } + /> + ); + } else { + content = ( + <> + {rows.map((row, index) => { + const server = servers[index]; + if (server === undefined) { + return null; + } + return ( + { + setMcpEnabled.mutate( + { profileId, mcpServerId: server.id, enabled }, + { + onError: error => { + if (!hasUsableMessage(error)) { + toast.error(t('profiles.mcp.saveFailed')); + } + }, + } + ); + }} + onEdit={() => { + setForm({ server }); + }} + onDelete={() => { + confirmDelete(server); + }} + /> + ); + })} + + + ); + } + + return ( + + + + {content} + + + {form !== null ? ( + { + void saveServer(form.server, payload); + }} + /> + ) : null} + + ); +} diff --git a/apps/mobile/src/components/profiles/profile-overview-model.test.ts b/apps/mobile/src/components/profiles/profile-overview-model.test.ts new file mode 100644 index 00000000000..be8c12b2ff5 --- /dev/null +++ b/apps/mobile/src/components/profiles/profile-overview-model.test.ts @@ -0,0 +1,104 @@ +import { describe, expect, it } from 'vitest'; + +import { + defaultControlLabel, + defaultDescriptionKey, + deleteErrorMessage, + metadataFormKey, + overviewSectionRows, +} from '@/components/profiles/profile-overview-model'; + +describe('defaultControlLabel', () => { + it('offers to set the profile as default when it is not', () => { + expect(defaultControlLabel(false)).toBe('profiles.setAsDefault'); + }); + + it('offers to remove the default when it is', () => { + expect(defaultControlLabel(true)).toBe('profiles.removeDefault'); + }); +}); + +describe('defaultDescriptionKey', () => { + it('explains the personal rule for a personal profile', () => { + expect(defaultDescriptionKey(false)).toBe('profiles.personalDefaultDescription'); + }); + + it('explains the organization rule for an org profile', () => { + expect(defaultDescriptionKey(true)).toBe('profiles.organizationDefaultDescription'); + }); +}); + +describe('deleteErrorMessage', () => { + it('classifies a PRECONDITION_FAILED response as blocked', () => { + expect(deleteErrorMessage({ data: { code: 'PRECONDITION_FAILED' } })).toBe('blocked'); + }); + + it('classifies a generic failure as failed', () => { + expect(deleteErrorMessage(new Error('network down'))).toBe('failed'); + expect(deleteErrorMessage({ data: { code: 'INTERNAL_SERVER_ERROR' } })).toBe('failed'); + expect(deleteErrorMessage(null)).toBe('failed'); + expect(deleteErrorMessage(undefined)).toBe('failed'); + }); +}); + +describe('metadataFormKey', () => { + it('is stable when only the profile timestamp changes', () => { + expect(metadataFormKey({ name: 'Backend', description: 'Old' })).toBe( + metadataFormKey({ name: 'Backend', description: 'Old' }) + ); + }); + + it('changes when the name or the description changes', () => { + const base = metadataFormKey({ name: 'Backend', description: 'Old' }); + expect(metadataFormKey({ name: 'Renamed', description: 'Old' })).not.toBe(base); + expect(metadataFormKey({ name: 'Backend', description: 'New' })).not.toBe(base); + }); + + it('treats an absent description as an empty one', () => { + expect(metadataFormKey({ name: 'Backend', description: null })).toBe( + metadataFormKey({ name: 'Backend', description: '' }) + ); + }); +}); + +describe('overviewSectionRows', () => { + it('returns the six sections in a fixed order with their counts', () => { + const rows = overviewSectionRows({ + vars: [{ key: 'A' }, { key: 'B' }, { key: 'C' }], + commands: ['pnpm install', 'pnpm build'], + kiloCommands: [{ id: 'k1' }, { id: 'k2' }, { id: 'k3' }, { id: 'k4' }], + mcpServers: [{ id: 'm1' }], + skills: [{ id: 'skill-1' }], + agents: [{ id: 'a1' }, { id: 'a2' }], + }); + + expect(rows).toEqual([ + { key: 'variables', titleKey: 'profiles.variablesTitle', count: 3 }, + { key: 'commands', titleKey: 'profiles.commandsTitle', count: 2 }, + { key: 'slashCommands', titleKey: 'profiles.slashCommands.title', count: 4 }, + { key: 'mcp', titleKey: 'profiles.mcp.title', count: 1 }, + { key: 'skills', titleKey: 'profiles.skillsTitle', count: 1 }, + { key: 'agents', titleKey: 'profiles.agents.title', count: 2 }, + ]); + }); + + it('keeps every row with zero counts for an empty profile', () => { + expect( + overviewSectionRows({ + vars: [], + commands: [], + kiloCommands: [], + mcpServers: [], + skills: [], + agents: [], + }) + ).toEqual([ + { key: 'variables', titleKey: 'profiles.variablesTitle', count: 0 }, + { key: 'commands', titleKey: 'profiles.commandsTitle', count: 0 }, + { key: 'slashCommands', titleKey: 'profiles.slashCommands.title', count: 0 }, + { key: 'mcp', titleKey: 'profiles.mcp.title', count: 0 }, + { key: 'skills', titleKey: 'profiles.skillsTitle', count: 0 }, + { key: 'agents', titleKey: 'profiles.agents.title', count: 0 }, + ]); + }); +}); diff --git a/apps/mobile/src/components/profiles/profile-overview-model.ts b/apps/mobile/src/components/profiles/profile-overview-model.ts new file mode 100644 index 00000000000..e4c9d87f7ba --- /dev/null +++ b/apps/mobile/src/components/profiles/profile-overview-model.ts @@ -0,0 +1,97 @@ +/** + * Pure view-model helpers for the profile Overview screen. + * + * No React and no React Native imports: every function here is unit-tested + * directly in `profile-overview-model.test.ts`. The screen owns rendering; this + * module owns the default-row copy selection, the delete failure + * classification, and the three section rows the Overview lists. + */ + +/** The `profiles.*` keys the default row's switch reads its label from. */ +const SET_DEFAULT_LABEL_KEY = 'profiles.setAsDefault'; +const REMOVE_DEFAULT_LABEL_KEY = 'profiles.removeDefault'; + +/** + * Label for the default toggle. The label names the action a press performs, + * so it flips with the profile's current default state. + */ +export function defaultControlLabel(isDefault: boolean): string { + return isDefault ? REMOVE_DEFAULT_LABEL_KEY : SET_DEFAULT_LABEL_KEY; +} + +/** + * Copy for the default row's subtitle. An org-owned profile explains the + * organization rule; a personal profile explains the personal rule. + */ +export function defaultDescriptionKey(isOrgOwned: boolean): string { + return isOrgOwned + ? 'profiles.organizationDefaultDescription' + : 'profiles.personalDefaultDescription'; +} + +export type DeleteErrorKind = 'blocked' | 'failed'; + +/** + * Classify a failed profile delete. The server refuses a delete with + * `PRECONDITION_FAILED` when a webhook trigger still references the profile + * (`agent-profiles-router.ts`); anything else is a plain failure. + */ +export function deleteErrorMessage(error: unknown): DeleteErrorKind { + const code = (error as { data?: { code?: unknown } } | null | undefined)?.data?.code; + return code === 'PRECONDITION_FAILED' ? 'blocked' : 'failed'; +} + +export type OverviewSectionKey = + | 'variables' + | 'commands' + | 'slashCommands' + | 'mcp' + | 'skills' + | 'agents'; + +/** One navigable section row: its route key, catalog title, and item count. */ +export type OverviewSectionRow = Readonly<{ + key: OverviewSectionKey; + titleKey: string; + count: number; +}>; + +/** + * Remount key for the uncontrolled metadata form. It changes only when the + * metadata the form seeds changes, so a refetch triggered by something else — + * the default toggle invalidates the detail query — keeps the mounted form and + * the user's unsaved name/description edits. `updatedAt` is unusable here: the + * server bumps it on every write, including the default toggle. + */ +export function metadataFormKey(profile: { name: string; description: string | null }): string { + return JSON.stringify([profile.name, profile.description ?? '']); +} + +/** The arrays the Overview counts. Structural, so a test needs no tRPC type. */ +export type ProfileOverviewSource = { + vars: readonly unknown[]; + commands: readonly unknown[]; + kiloCommands: readonly unknown[]; + mcpServers: readonly unknown[]; + skills: readonly unknown[]; + agents: readonly unknown[]; +}; + +/** + * The Overview section rows, always in the same order so the screen's layout + * never depends on the counts. The order mirrors the web editor's tabs. + */ +export function overviewSectionRows(profile: ProfileOverviewSource): OverviewSectionRow[] { + return [ + { key: 'variables', titleKey: 'profiles.variablesTitle', count: profile.vars.length }, + { key: 'commands', titleKey: 'profiles.commandsTitle', count: profile.commands.length }, + { + key: 'slashCommands', + titleKey: 'profiles.slashCommands.title', + count: profile.kiloCommands.length, + }, + { key: 'mcp', titleKey: 'profiles.mcp.title', count: profile.mcpServers.length }, + { key: 'skills', titleKey: 'profiles.skillsTitle', count: profile.skills.length }, + { key: 'agents', titleKey: 'profiles.agents.title', count: profile.agents.length }, + ]; +} diff --git a/apps/mobile/src/components/profiles/profile-overview-screen.mounted.test.tsx b/apps/mobile/src/components/profiles/profile-overview-screen.mounted.test.tsx new file mode 100644 index 00000000000..43150958d8c --- /dev/null +++ b/apps/mobile/src/components/profiles/profile-overview-screen.mounted.test.tsx @@ -0,0 +1,315 @@ +import { beforeEach, describe, expect, it, vi } from 'vitest'; + +import '@/i18n'; +import { + changeText, + findAll, + findField, + findOne, + findRow, + mountScreen, + pressDelete, + pressSave, + rerenderScreen, + testProfile, + type TestProfileDetail, +} from '@/components/profiles/profile-overview-screen.test-helpers'; +import { act } from '@/test/renderer'; +import { waitFor } from '@/test/render-with-providers'; + +const h = vi.hoisted(() => ({ + back: vi.fn(), + push: vi.fn(), + success: vi.fn(), + error: vi.fn(), + alert: vi.fn(), + query: { + data: undefined as TestProfileDetail | undefined, + isError: false, + isPending: true, + isRefetching: false, + refetch: vi.fn(), + }, + mutations: { + update: { mutateAsync: vi.fn(), isPending: false }, + deleteProfile: { mutateAsync: vi.fn(), isPending: false }, + setAsDefault: { mutate: vi.fn(), isPending: false }, + clearDefault: { mutate: vi.fn(), isPending: false }, + }, +})); + +vi.mock('@/lib/hooks/use-agent-profiles', () => ({ + useAgentProfile: () => h.query, + useAgentProfileMutations: () => h.mutations, +})); +vi.mock('sonner-native', () => ({ toast: { success: h.success, error: h.error } })); +vi.mock('expo-router', () => ({ useRouter: () => ({ back: h.back, push: h.push }) })); +vi.mock('@/lib/profile-agent-navigation', () => ({ + getProfileVariablesPath: (id: string, org?: string) => + `/profiles/${id}/variables${org ? `?org=${org}` : ''}`, + getProfileCommandsPath: (id: string, org?: string) => + `/profiles/${id}/commands${org ? `?org=${org}` : ''}`, + getProfileSlashCommandsPath: (id: string, org?: string) => + `/profiles/${id}/slash-commands${org ? `?org=${org}` : ''}`, + getProfileMcpPath: (id: string, org?: string) => `/profiles/${id}/mcp${org ? `?org=${org}` : ''}`, + getProfileSkillsPath: (id: string, org?: string) => + `/profiles/${id}/skills${org ? `?org=${org}` : ''}`, + getProfileAgentsPath: (id: string, org?: string) => + `/profiles/${id}/agents${org ? `?org=${org}` : ''}`, +})); +vi.mock('react-native', () => ({ + View: 'View', + ScrollView: 'ScrollView', + Alert: { alert: h.alert }, +})); +vi.mock('@/lib/hooks/use-theme-colors', () => ({ + useThemeColors: () => ({ destructiveForeground: '#FFFFFF' }), +})); +vi.mock('@/components/screen-header', () => ({ ScreenHeader: 'ScreenHeader' })); +vi.mock('@/components/query-error', () => ({ QueryError: 'QueryError' })); +vi.mock('@/components/profiles/profile-repo-pins-section', () => ({ + ProfileRepoPinsSection: 'ProfileRepoPinsSection', +})); +vi.mock('@/components/ui/button', () => ({ Button: 'Button' })); +vi.mock('@/components/ui/configure-row', () => ({ ConfigureRow: 'ConfigureRow' })); +vi.mock('@/components/ui/form-field', () => ({ FormField: 'FormField' })); +vi.mock('@/components/ui/preference-row', () => ({ PreferenceRow: 'PreferenceRow' })); +vi.mock('@/components/ui/skeleton', () => ({ Skeleton: 'Skeleton' })); +vi.mock('@/components/ui/text', () => ({ Text: 'Text' })); +vi.mock('@/components/ui/icons', () => ({ + Bot: 'Bot', + CornerDownLeft: 'CornerDownLeft', + KeyRound: 'KeyRound', + Server: 'Server', + Sparkles: 'Sparkles', + Star: 'Star', + Terminal: 'Terminal', + Trash2: 'Trash2', +})); + +describe('ProfileOverviewScreen', () => { + beforeEach(() => { + vi.clearAllMocks(); + Object.assign(h.query, { + data: undefined, + isError: false, + isPending: true, + isRefetching: false, + }); + Object.assign(h.mutations.update, { isPending: false }); + Object.assign(h.mutations.deleteProfile, { isPending: false }); + }); + + it('loading: renders skeleton rows and no content rows', async () => { + const { renderer, unmount } = await mountScreen(); + + expect(findAll(renderer.root, 'Skeleton').length).toBeGreaterThan(0); + expect(findAll(renderer.root, 'ConfigureRow')).toHaveLength(0); + + unmount(); + }); + + it('error: renders QueryError and Retry refetches', async () => { + Object.assign(h.query, { isError: true, isPending: false }); + + const { renderer, unmount } = await mountScreen(); + + const queryError = findOne(renderer.root, 'QueryError'); + expect(queryError.props.title).toBe("Couldn't load profiles"); + act(() => { + (queryError.props as { onRetry: () => void }).onRetry(); + }); + expect(h.query.refetch).toHaveBeenCalledTimes(1); + + unmount(); + }); + + it('happy: seeds the fields, lists the sections, and saves the metadata', async () => { + h.query.data = testProfile({ + vars: [{ key: 'A' }], + commands: ['pnpm install', 'pnpm build'], + skills: [{ id: 's1' }], + }); + h.query.isPending = false; + h.mutations.update.mutateAsync.mockResolvedValue({ success: true }); + + const { renderer, unmount } = await mountScreen(); + + expect(findField(renderer.root, 'Profile name').props.defaultValue).toBe('Backend debugging'); + expect(findField(renderer.root, 'Profile description').props.defaultValue).toBe( + 'Old description' + ); + expect(findAll(renderer.root, 'ConfigureRow').map(row => row.props.subtitle)).toEqual([ + '1', + '2', + '0', + '0', + '1', + '0', + ]); + + act(() => { + (findRow(renderer.root, 'Skills').props as { onPress: () => void }).onPress(); + }); + expect(h.push).toHaveBeenCalledWith('/profiles/profile-1/skills'); + + changeText(renderer.root, 'Profile name', ' Renamed '); + await pressSave(renderer.root); + + await waitFor(() => h.mutations.update.mutateAsync.mock.calls.length > 0); + expect(h.mutations.update.mutateAsync.mock.calls[0]?.[0]).toEqual({ + profileId: 'profile-1', + name: 'Renamed', + description: 'Old description', + }); + await waitFor(() => h.success.mock.calls.length > 0); + expect(h.success).toHaveBeenCalledWith('Updated'); + + unmount(); + }); + + it('empty: an empty profile still renders all six rows with zero counts', async () => { + h.query.data = testProfile(); + h.query.isPending = false; + + const { renderer, unmount } = await mountScreen(); + + expect(findAll(renderer.root, 'ConfigureRow').map(row => row.props.subtitle)).toEqual([ + '0', + '0', + '0', + '0', + '0', + '0', + ]); + + unmount(); + }); + + it('retryable: a save failure with no usable message adds saveFailed', async () => { + h.query.data = testProfile(); + h.query.isPending = false; + const error = new Error('placeholder'); + error.message = ''; + h.mutations.update.mutateAsync.mockRejectedValue(error); + + const { renderer, unmount } = await mountScreen(); + + await pressSave(renderer.root); + + await waitFor(() => h.error.mock.calls.length > 0); + expect(h.error).toHaveBeenCalledWith("Couldn't save profile"); + + unmount(); + }); + + it('happy: the default toggle calls setAsDefault and clearDefault', async () => { + h.query.data = testProfile({ isDefault: false }); + h.query.isPending = false; + + const { renderer, unmount } = await mountScreen(); + + const defaultRow = findOne(renderer.root, 'PreferenceRow'); + expect(defaultRow.props.subtitle).toBe('Auto-loaded when no repository has a pinned profile.'); + expect(defaultRow.props.switchAccessibilityLabel).toBe('Set as default'); + + act(() => { + (defaultRow.props as { onValueChange: (next: boolean) => void }).onValueChange(true); + }); + expect(h.mutations.setAsDefault.mutate).toHaveBeenCalledWith({ profileId: 'profile-1' }); + + act(() => { + (defaultRow.props as { onValueChange: (next: boolean) => void }).onValueChange(false); + }); + expect(h.mutations.clearDefault.mutate).toHaveBeenCalledWith({ profileId: 'profile-1' }); + + unmount(); + }); + + it('keeps unsaved metadata edits when a refetch changes only updatedAt', async () => { + h.query.data = testProfile(); + h.query.isPending = false; + + const { renderer, queryClient, unmount } = await mountScreen(); + + changeText(renderer.root, 'Profile name', 'Renamed'); + + // Toggling the default invalidates the detail query; the refetched profile + // carries a new updatedAt but the same name and description. + h.query.data = testProfile({ isDefault: true, updatedAt: '2026-02-02T00:00:00.000Z' }); + await act(async () => { + rerenderScreen(renderer, queryClient); + await Promise.resolve(); + }); + + await pressSave(renderer.root); + + await waitFor(() => h.mutations.update.mutateAsync.mock.calls.length > 0); + expect(h.mutations.update.mutateAsync.mock.calls[0]?.[0]).toEqual({ + profileId: 'profile-1', + name: 'Renamed', + description: 'Old description', + }); + + unmount(); + }); + + it('happy: confirming delete toasts and navigates back', async () => { + h.query.data = testProfile(); + h.query.isPending = false; + h.mutations.deleteProfile.mutateAsync.mockResolvedValue({ success: true }); + + const { renderer, unmount } = await mountScreen(); + + await pressDelete(renderer.root, h.alert); + + await waitFor(() => h.mutations.deleteProfile.mutateAsync.mock.calls.length > 0); + expect(h.mutations.deleteProfile.mutateAsync.mock.calls[0]?.[0]).toEqual({ + profileId: 'profile-1', + }); + await waitFor(() => h.success.mock.calls.length > 0); + expect(h.success).toHaveBeenCalledWith('Profile "Backend debugging" deleted'); + await waitFor(() => h.back.mock.calls.length > 0); + + unmount(); + }); + + it('non-retryable: a PRECONDITION_FAILED delete shows deleteBlocked and stays', async () => { + h.query.data = testProfile(); + h.query.isPending = false; + h.mutations.deleteProfile.mutateAsync.mockRejectedValue({ + data: { code: 'PRECONDITION_FAILED' }, + }); + + const { renderer, unmount } = await mountScreen(); + + await pressDelete(renderer.root, h.alert); + + await waitFor(() => h.error.mock.calls.length > 0); + expect(h.error).toHaveBeenCalledWith( + 'This profile is used by a webhook trigger. Remove it from those triggers first.' + ); + expect(h.back).not.toHaveBeenCalled(); + expect(findField(renderer.root, 'Profile name')).toBeTruthy(); + + unmount(); + }); + + it('retryable: a generic delete failure with no usable message adds deleteFailed', async () => { + h.query.data = testProfile(); + h.query.isPending = false; + const error = new Error('placeholder'); + error.message = ''; + h.mutations.deleteProfile.mutateAsync.mockRejectedValue(error); + + const { renderer, unmount } = await mountScreen(); + + await pressDelete(renderer.root, h.alert); + + await waitFor(() => h.error.mock.calls.length > 0); + expect(h.error).toHaveBeenCalledWith("Couldn't delete profile"); + expect(h.back).not.toHaveBeenCalled(); + + unmount(); + }); +}); diff --git a/apps/mobile/src/components/profiles/profile-overview-screen.test-helpers.ts b/apps/mobile/src/components/profiles/profile-overview-screen.test-helpers.ts new file mode 100644 index 00000000000..d2f7c6403b8 --- /dev/null +++ b/apps/mobile/src/components/profiles/profile-overview-screen.test-helpers.ts @@ -0,0 +1,141 @@ +import { createElement } from 'react'; +import { type QueryClient, QueryClientProvider } from '@tanstack/react-query'; + +import { ProfileOverviewScreen } from '@/components/profiles/profile-overview-screen'; +import { act, type ReactTestInstance, type ReactTestRenderer } from '@/test/renderer'; +import { renderWithProviders } from '@/test/render-with-providers'; + +/** + * Shared harness for the Profile Overview mounted tests. The `vi.mock` calls + * stay in each test file (they must be hoisted per file); everything that does + * not depend on the hoisted mock state lives here so no mounted test file + * grows past the lint line budget. + */ + +/** The profile detail the mocked `useAgentProfile` returns. */ +export type TestProfileDetail = { + id: string; + name: string; + description: string | null; + isDefault: boolean; + updatedAt: string; + vars: unknown[]; + commands: unknown[]; + kiloCommands: unknown[]; + mcpServers: unknown[]; + skills: unknown[]; + agents: unknown[]; +}; + +type TestAlertButton = { style: string; onPress?: () => void }; + +/** The mock surface `Alert.alert` exposes to the harness. */ +export type TestAlertMock = { mock: { calls: unknown[][] } }; + +export function testProfile(overrides: Partial = {}): TestProfileDetail { + return { + id: 'profile-1', + name: 'Backend debugging', + description: 'Old description', + isDefault: false, + updatedAt: '2026-01-01T00:00:00.000Z', + vars: [], + commands: [], + kiloCommands: [], + mcpServers: [], + skills: [], + agents: [], + ...overrides, + }; +} + +export function findAll(root: ReactTestInstance, type: string): ReactTestInstance[] { + return root.findAll(node => typeof node.type === 'string' && node.type === type); +} + +export function findOne(root: ReactTestInstance, type: string): ReactTestInstance { + const node = findAll(root, type)[0]; + if (!node) { + throw new Error(`${type} was not rendered`); + } + return node; +} + +export function findField(root: ReactTestInstance, label: string): ReactTestInstance { + const field = findAll(root, 'FormField').find(node => node.props.label === label); + if (!field) { + throw new Error(`field ${label} was not rendered`); + } + return field; +} + +export function findRow(root: ReactTestInstance, title: string): ReactTestInstance { + const row = findAll(root, 'ConfigureRow').find(node => node.props.title === title); + if (!row) { + throw new Error(`row ${title} was not rendered`); + } + return row; +} + +function pressButton(root: ReactTestInstance, index: number): void { + const button = findAll(root, 'Button')[index]; + if (!button) { + throw new Error(`button ${index} was not rendered`); + } + act(() => { + (button.props as { onPress: () => void }).onPress(); + }); +} + +export function changeText(root: ReactTestInstance, label: string, value: string): void { + act(() => { + (findField(root, label).props as { onChangeText: (v: string) => void }).onChangeText(value); + }); +} + +/** Confirm the destructive Alert button the screen raised. */ +function confirmAlert(alert: TestAlertMock): void { + const buttons = alert.mock.calls[0]?.[2] as TestAlertButton[] | undefined; + const destructive = buttons?.find(button => button.style === 'destructive'); + if (!destructive?.onPress) { + throw new Error('destructive alert button was not registered'); + } + destructive.onPress(); +} + +// eslint-disable-next-line typescript-eslint/promise-function-async -- returning the harness promise unchanged +export function mountScreen(organizationId?: string) { + return renderWithProviders( + createElement(ProfileOverviewScreen, { profileId: 'profile-1', organizationId }) + ); +} + +/** Re-render the mounted screen against fresh mock data, keeping the providers. */ +export function rerenderScreen( + renderer: ReactTestRenderer, + queryClient: QueryClient, + organizationId?: string +): void { + renderer.update( + createElement( + QueryClientProvider, + { client: queryClient }, + createElement(ProfileOverviewScreen, { profileId: 'profile-1', organizationId }) + ) + ); +} + +export async function pressSave(root: ReactTestInstance): Promise { + await act(async () => { + pressButton(root, 0); + await Promise.resolve(); + }); +} + +export async function pressDelete(root: ReactTestInstance, alert: TestAlertMock): Promise { + await act(async () => { + pressButton(root, 1); + confirmAlert(alert); + await Promise.resolve(); + }); +} diff --git a/apps/mobile/src/components/profiles/profile-overview-screen.tsx b/apps/mobile/src/components/profiles/profile-overview-screen.tsx new file mode 100644 index 00000000000..29cc24de61b --- /dev/null +++ b/apps/mobile/src/components/profiles/profile-overview-screen.tsx @@ -0,0 +1,327 @@ +import { type Href, useRouter } from 'expo-router'; +import { useRef, useState } from 'react'; +import { useTranslation } from 'react-i18next'; +import { Alert, ScrollView, View } from 'react-native'; +import { toast } from 'sonner-native'; + +import { + defaultControlLabel, + defaultDescriptionKey, + deleteErrorMessage, + metadataFormKey, + type OverviewSectionKey, + overviewSectionRows, +} from '@/components/profiles/profile-overview-model'; +import { ProfileRepoPinsSection } from '@/components/profiles/profile-repo-pins-section'; +import { QueryError } from '@/components/query-error'; +import { ScreenHeader } from '@/components/screen-header'; +import { Button } from '@/components/ui/button'; +import { ConfigureRow } from '@/components/ui/configure-row'; +import { FormField } from '@/components/ui/form-field'; +import { + Bot, + CornerDownLeft, + KeyRound, + type LucideIcon, + Server, + Sparkles, + Star, + Terminal, + Trash2, +} from '@/components/ui/icons'; +import { PreferenceRow } from '@/components/ui/preference-row'; +import { Skeleton } from '@/components/ui/skeleton'; +import { Text } from '@/components/ui/text'; +import { validateProfileName } from '@/lib/agent-profile-forms'; +import { + type AgentProfileDetail, + useAgentProfile, + useAgentProfileMutations, +} from '@/lib/hooks/use-agent-profiles'; +import { useThemeColors } from '@/lib/hooks/use-theme-colors'; +import { + getProfileAgentsPath, + getProfileCommandsPath, + getProfileMcpPath, + getProfileSkillsPath, + getProfileSlashCommandsPath, + getProfileVariablesPath, +} from '@/lib/profile-agent-navigation'; + +/** Icon per Overview section, matching the section's purpose. */ +const SECTION_ICONS = { + variables: KeyRound, + commands: Terminal, + slashCommands: CornerDownLeft, + mcp: Server, + skills: Sparkles, + agents: Bot, +} satisfies Record; + +/** Route builder per Overview section, carrying the profile's context. */ +const SECTION_PATHS = { + variables: getProfileVariablesPath, + commands: getProfileCommandsPath, + slashCommands: getProfileSlashCommandsPath, + mcp: getProfileMcpPath, + skills: getProfileSkillsPath, + agents: getProfileAgentsPath, +} satisfies Record Href>; + +/** + * The mutation hook toasts `error.message`; the screen only supplies a + * fallback when the server sent nothing readable. + */ +function hasUsableMessage(error: unknown): boolean { + return error instanceof Error && error.message.trim().length > 0; +} + +type MetadataFormProps = Readonly<{ + profile: AgentProfileDetail; + isSaving: boolean; + onSave: (values: { name: string; description: string }) => void; +}>; + +/** + * Uncontrolled metadata form. The screen remounts this component — keyed on the + * metadata it seeds via `metadataFormKey` — so a refetch re-seeds both fields + * only when that metadata changed; the refs live here, so the remount resets + * them alongside the inputs. + */ +function ProfileMetadataForm({ profile, isSaving, onSave }: MetadataFormProps) { + const { t } = useTranslation(); + const nameRef = useRef(profile.name); + const descriptionRef = useRef(profile.description ?? ''); + const [nameError, setNameError] = useState(null); + + const submit = () => { + const name = nameRef.current.trim(); + if (validateProfileName(name) !== null) { + setNameError(t('profiles.nameRequired')); + return; + } + setNameError(null); + onSave({ name, description: descriptionRef.current.trim() }); + }; + + return ( + + { + nameRef.current = value; + if (nameError !== null && validateProfileName(value) === null) { + setNameError(null); + } + }} + /> + { + descriptionRef.current = value; + }} + /> + + + ); +} + +/** + * Content-shaped loading rows in the same slots as the loaded screen (two + * fields, the default row, the six section rows, the delete button) so the + * swap to real content does not move anything. + */ +function ProfileOverviewSkeleton() { + return ( + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ); +} + +export function ProfileOverviewScreen({ + profileId, + organizationId, +}: Readonly<{ profileId: string; organizationId?: string }>) { + const router = useRouter(); + const { t } = useTranslation(); + const colors = useThemeColors(); + const profileQuery = useAgentProfile(profileId, organizationId); + const { update, deleteProfile, setAsDefault, clearDefault } = + useAgentProfileMutations(organizationId); + + const profile = profileQuery.data; + const isOrgOwned = organizationId !== undefined; + + const saveMetadata = async ({ name, description }: { name: string; description: string }) => { + try { + await update.mutateAsync({ profileId, name, description }); + toast.success(t('common.updated')); + } catch (error) { + if (!hasUsableMessage(error)) { + toast.error(t('profiles.saveFailed')); + } + } + }; + + const runDelete = async (profileName: string) => { + try { + await deleteProfile.mutateAsync({ profileId }); + toast.success(t('profiles.deletedToast', { name: profileName })); + router.back(); + } catch (error) { + if (deleteErrorMessage(error) === 'blocked') { + toast.error(t('profiles.deleteBlocked')); + } else if (!hasUsableMessage(error)) { + toast.error(t('profiles.deleteFailed')); + } + } + }; + + const confirmDelete = (profileName: string) => { + Alert.alert(t('profiles.deleteTitle'), t('profiles.deleteMessage'), [ + { text: t('common.cancel'), style: 'cancel' }, + { + text: t('common.delete'), + style: 'destructive', + onPress: () => { + void runDelete(profileName); + }, + }, + ]); + }; + + const defaultPending = setAsDefault.isPending || clearDefault.isPending; + + return ( + + + + {profileQuery.isError ? ( + void profileQuery.refetch()} + isRetrying={profileQuery.isRefetching} + /> + ) : null} + + {!profileQuery.isError && profile === undefined ? : null} + + {!profileQuery.isError && profile !== undefined ? ( + <> + { + void saveMetadata(values); + }} + /> + + { + if (next) { + setAsDefault.mutate({ profileId }); + } else { + clearDefault.mutate({ profileId }); + } + }} + /> + + + + + + {t('profiles.settingsSectionTitle')} + + + {overviewSectionRows(profile).map((row, index, rows) => ( + { + router.push(SECTION_PATHS[row.key](profileId, organizationId)); + }} + /> + ))} + + + + + + ) : null} + + + ); +} diff --git a/apps/mobile/src/components/profiles/profile-owner-model.test.ts b/apps/mobile/src/components/profiles/profile-owner-model.test.ts new file mode 100644 index 00000000000..83d211bab74 --- /dev/null +++ b/apps/mobile/src/components/profiles/profile-owner-model.test.ts @@ -0,0 +1,118 @@ +import { describe, expect, it } from 'vitest'; + +import { + createOrganizationId, + hasOrganizationContext, + ownerChoices, + profileOrganizationId, + profileOwnerType, + splitProfilesByOwner, +} from '@/components/profiles/profile-owner-model'; + +type TestProfile = { + id: string; + ownerType?: 'organization' | 'user'; +}; + +function profile(overrides: Partial & { id: string }): TestProfile { + return { ...overrides }; +} + +const orgProfile = profile({ id: 'org-profile', ownerType: 'organization' }); +const personalProfile = profile({ id: 'personal-profile', ownerType: 'user' }); +const untaggedProfile = profile({ id: 'untagged-profile' }); + +describe('ownerChoices', () => { + it('offers personal only without an organization context', () => { + expect(ownerChoices(null)).toEqual(['personal']); + expect(ownerChoices(undefined)).toEqual(['personal']); + }); + + it('offers personal and organization inside an organization context', () => { + expect(ownerChoices('org-1')).toEqual(['personal', 'organization']); + }); +}); + +describe('hasOrganizationContext', () => { + it('is true only for a non-null organization id', () => { + expect(hasOrganizationContext('org-1')).toBe(true); + expect(hasOrganizationContext(null)).toBe(false); + expect(hasOrganizationContext(undefined)).toBe(false); + }); +}); + +describe('profileOwnerType', () => { + it('reads the server owner tag, defaulting an untagged row to personal', () => { + expect(profileOwnerType(orgProfile)).toBe('organization'); + expect(profileOwnerType(personalProfile)).toBe('personal'); + // `agentProfiles.list` does not tag personal rows. + expect(profileOwnerType(untaggedProfile)).toBe('personal'); + }); +}); + +describe('createOrganizationId', () => { + it('sends the organization only for an organization-owned create', () => { + expect(createOrganizationId('org-1', 'organization')).toBe('org-1'); + }); + + it('never sends the organization for a personal create', () => { + expect(createOrganizationId('org-1', 'personal')).toBeUndefined(); + expect(createOrganizationId(null, 'personal')).toBeUndefined(); + }); + + it('cannot send an organization when no context is active', () => { + expect(createOrganizationId(null, 'organization')).toBeUndefined(); + expect(createOrganizationId(undefined, 'organization')).toBeUndefined(); + }); +}); + +describe('profileOrganizationId', () => { + it('sends the context organization for an organization-owned profile', () => { + expect(profileOrganizationId('org-1', orgProfile)).toBe('org-1'); + }); + + it('sends no organization for a personal profile even inside an organization context', () => { + expect(profileOrganizationId('org-1', personalProfile)).toBeUndefined(); + expect(profileOrganizationId('org-1', untaggedProfile)).toBeUndefined(); + }); + + it('sends no organization when no context is active', () => { + expect(profileOrganizationId(null, orgProfile)).toBeUndefined(); + expect(profileOrganizationId(undefined, orgProfile)).toBeUndefined(); + }); +}); + +describe('splitProfilesByOwner', () => { + it('reads both buckets from the combined list in an organization context', () => { + const buckets = splitProfilesByOwner({ + isOrgContext: true, + combined: { orgProfiles: [orgProfile], personalProfiles: [personalProfile] }, + personal: [untaggedProfile], + }); + + expect(buckets.orgProfiles).toEqual([orgProfile]); + expect(buckets.personalProfiles).toEqual([personalProfile]); + }); + + it('ignores stale combined data in a personal context', () => { + const buckets = splitProfilesByOwner({ + isOrgContext: false, + combined: { orgProfiles: [orgProfile], personalProfiles: [personalProfile] }, + personal: [untaggedProfile], + }); + + expect(buckets.orgProfiles).toEqual([]); + expect(buckets.personalProfiles).toEqual([untaggedProfile]); + }); + + it('returns empty buckets before any list data has loaded', () => { + expect(splitProfilesByOwner({ isOrgContext: true })).toEqual({ + orgProfiles: [], + personalProfiles: [], + }); + expect(splitProfilesByOwner({ isOrgContext: false })).toEqual({ + orgProfiles: [], + personalProfiles: [], + }); + }); +}); diff --git a/apps/mobile/src/components/profiles/profile-owner-model.ts b/apps/mobile/src/components/profiles/profile-owner-model.ts new file mode 100644 index 00000000000..de6d4077025 --- /dev/null +++ b/apps/mobile/src/components/profiles/profile-owner-model.ts @@ -0,0 +1,104 @@ +/** + * Pure ownership rules for the mobile profile manager. + * + * No React and no React Native imports: every function here is unit-tested + * directly in `profile-owner-model.test.ts`. The rules mirror the web Manage + * Profiles dialog (`apps/web/src/components/cloud-agent/ProfilesListDialog.tsx`), + * which is the source of truth for personal versus organization ownership: + * which owner a create may carry, which `organizationId` an existing profile's + * read/write must carry, and how the combined list splits into its buckets. + */ + +/** The owner a user can pick when creating a profile. */ +export type ProfileOwnerChoice = 'personal' | 'organization'; + +/** + * The owner tag a profile list item carries. The server tags organization rows + * `organization` and personal rows `user` (`ProfilesListDialog.tsx:95-98`). + */ +type ProfileOwnerType = 'user' | 'organization'; + +/** The part of a profile summary ownership depends on. */ +type OwnerBearing = { ownerType?: ProfileOwnerType }; + +/** + * The owner choices a create screen offers. An organization context offers + * Personal and Organization; a personal context offers Personal only + * (`ProfilesListDialog.tsx:431-459`). + */ +export function ownerChoices( + organizationId: string | null | undefined +): readonly ProfileOwnerChoice[] { + return hasOrganizationContext(organizationId) ? ['personal', 'organization'] : ['personal']; +} + +/** Whether an organization context is active, i.e. ownership is a choice. */ +export function hasOrganizationContext( + organizationId: string | null | undefined +): organizationId is string { + return organizationId != null; +} + +/** The owner choice an existing profile is. An untagged row is personal. */ +export function profileOwnerType(profile: OwnerBearing): ProfileOwnerChoice { + return profile.ownerType === 'organization' ? 'organization' : 'personal'; +} + +/** + * The `organizationId` a create must send for the chosen owner. Only an + * organization-owned profile sends it (`ProfilesListDialog.tsx:173-179`): a + * personal profile must never inherit the selected organization, or it would + * be created under the wrong owner. + */ +export function createOrganizationId( + organizationId: string | null | undefined, + owner: ProfileOwnerChoice +): string | undefined { + return hasOrganizationContext(organizationId) && owner === 'organization' + ? organizationId + : undefined; +} + +/** + * The `organizationId` an update, delete or detail read must send for an + * existing profile: the active context id only when the profile is + * organization-owned, and `undefined` for a personal profile even inside an + * organization context (`ProfilesListDialog.tsx:142-156,308-319`). + */ +export function profileOrganizationId( + organizationId: string | null | undefined, + profile: OwnerBearing +): string | undefined { + return profileOwnerType(profile) === 'organization' ? (organizationId ?? undefined) : undefined; +} + +/** The two buckets a list context renders, split by owner. */ +export type ProfileOwnerBuckets = { + orgProfiles: TProfile[]; + personalProfiles: TProfile[]; +}; + +/** + * Split the query data a list context reads into the two buckets the list + * renders, mirroring the web dialog (`ProfilesListDialog.tsx:95-100`). An + * organization context reads `agentProfiles.listCombined`, which carries both + * buckets; a personal context reads `agentProfiles.list` and has no + * organization bucket, whatever stale combined data is still cached. + */ +export function splitProfilesByOwner({ + isOrgContext, + combined, + personal, +}: Readonly<{ + isOrgContext: boolean; + combined?: { orgProfiles: readonly TProfile[]; personalProfiles: readonly TProfile[] }; + personal?: readonly TProfile[]; +}>): ProfileOwnerBuckets { + if (isOrgContext) { + return { + orgProfiles: [...(combined?.orgProfiles ?? [])], + personalProfiles: [...(combined?.personalProfiles ?? [])], + }; + } + return { orgProfiles: [], personalProfiles: [...(personal ?? [])] }; +} diff --git a/apps/mobile/src/components/profiles/profile-repo-pins-section.mounted.test.tsx b/apps/mobile/src/components/profiles/profile-repo-pins-section.mounted.test.tsx new file mode 100644 index 00000000000..89a7d931ecb --- /dev/null +++ b/apps/mobile/src/components/profiles/profile-repo-pins-section.mounted.test.tsx @@ -0,0 +1,224 @@ +import { createElement } from 'react'; +import { beforeEach, describe, expect, it, vi } from 'vitest'; + +import '@/i18n'; +import { ProfileRepoPinsSection } from '@/components/profiles/profile-repo-pins-section'; +import { act, type ReactTestInstance } from '@/test/renderer'; +import { renderWithProviders, waitFor } from '@/test/render-with-providers'; + +type TestBinding = { + repoFullName: string; + platform: string; + profileId: string; + profileName: string; +}; +type TestRepoOption = { platform: 'github' | 'gitlab'; fullName: string; private: boolean }; + +const h = vi.hoisted(() => ({ + success: vi.fn(), + error: vi.fn(), + bindingsQuery: { + bindings: [] as TestBinding[], + isLoading: true, + isError: false, + isRefetching: false, + refetch: vi.fn(), + }, + mutations: { + bind: { mutateAsync: vi.fn(), isPending: false }, + unbind: { mutateAsync: vi.fn(), isPending: false }, + }, + repoOptions: { + repositories: [] as TestRepoOption[], + isLoading: false, + isError: false, + isRefetching: false, + refetch: vi.fn(), + }, +})); + +vi.mock('@/lib/hooks/use-repo-bindings', () => ({ + useRepoBindings: () => h.bindingsQuery, + useRepoBindingMutations: () => h.mutations, + useRepoOptions: () => h.repoOptions, +})); +vi.mock('sonner-native', () => ({ toast: { success: h.success, error: h.error } })); +vi.mock('react-native', () => ({ + View: 'View', + ScrollView: 'ScrollView', + Pressable: 'Pressable', + TextInput: 'TextInput', + I18nManager: { isRTL: false }, +})); +vi.mock('react-native-safe-area-context', () => ({ + useSafeAreaInsets: () => ({ top: 0, bottom: 0, left: 0, right: 0 }), +})); +vi.mock('@/lib/hooks/use-theme-colors', () => ({ + useThemeColors: () => ({ + mutedForeground: '#000000', + destructive: '#FF0000', + primary: '#0000FF', + foreground: '#111111', + }), +})); +vi.mock('@/components/agents/session-page-sheet', () => ({ + SessionPageSheet: 'SessionPageSheet', +})); +vi.mock('@/components/sheet-header', () => ({ SheetHeader: 'SheetHeader' })); +vi.mock('@/components/query-error', () => ({ QueryError: 'QueryError' })); +vi.mock('@/components/empty-state', () => ({ EmptyState: 'EmptyState' })); +vi.mock('@/components/ui/button', () => ({ Button: 'Button' })); +vi.mock('@/components/ui/skeleton', () => ({ Skeleton: 'Skeleton' })); +vi.mock('@/components/ui/text', () => ({ Text: 'Text' })); +vi.mock('@/components/ui/icons', () => ({ + Check: 'Check', + GitBranch: 'GitBranch', + Link2: 'Link2', + Lock: 'Lock', + Plus: 'Plus', + Search: 'Search', + SearchX: 'SearchX', + Trash2: 'Trash2', + Unlock: 'Unlock', +})); + +function findAll(root: ReactTestInstance, type: string): ReactTestInstance[] { + return root.findAll(node => typeof node.type === 'string' && node.type === type); +} + +function pressPressable(root: ReactTestInstance, label: string): void { + const pressable = findAll(root, 'Pressable').find( + node => node.props.accessibilityLabel === label + ); + if (!pressable) { + throw new Error(`pressable ${label} was not rendered`); + } + act(() => { + (pressable.props as { onPress: () => void }).onPress(); + }); +} + +function pressButton(root: ReactTestInstance, label: string): void { + const button = findAll(root, 'Button').find(node => node.props.accessibilityLabel === label); + if (!button) { + throw new Error(`button ${label} was not rendered`); + } + act(() => { + (button.props as { onPress: () => void }).onPress(); + }); +} + +// eslint-disable-next-line typescript-eslint/promise-function-async -- returning the harness promise unchanged +function mountSection(organizationId?: string) { + return renderWithProviders( + createElement(ProfileRepoPinsSection, { profileId: 'profile-1', organizationId }) + ); +} + +describe('ProfileRepoPinsSection', () => { + beforeEach(() => { + vi.clearAllMocks(); + Object.assign(h.bindingsQuery, { + bindings: [], + isLoading: true, + isError: false, + isRefetching: false, + }); + Object.assign(h.mutations.bind, { isPending: false }); + Object.assign(h.mutations.unbind, { isPending: false }); + Object.assign(h.repoOptions, { + repositories: [ + { platform: 'github', fullName: 'acme/api', private: true }, + { platform: 'gitlab', fullName: 'acme/infra', private: false }, + ], + isLoading: false, + isError: false, + isRefetching: false, + }); + }); + + it('loading: renders the card skeleton', async () => { + const { renderer, unmount } = await mountSection(); + expect(findAll(renderer.root, 'Skeleton').length).toBeGreaterThan(0); + unmount(); + }); + + it('retryable: a bindings failure shows QueryError and Retry refetches', async () => { + Object.assign(h.bindingsQuery, { isError: true, isLoading: false }); + const { renderer, unmount } = await mountSection(); + const queryError = findAll(renderer.root, 'QueryError')[0]; + if (!queryError) { + throw new Error('QueryError was not rendered'); + } + act(() => { + (queryError.props as { onRetry: () => void }).onRetry(); + }); + expect(h.bindingsQuery.refetch).toHaveBeenCalledTimes(1); + unmount(); + }); + + it('empty: shows Not pinned to any repositories.', async () => { + Object.assign(h.bindingsQuery, { isLoading: false }); + const { renderer, unmount } = await mountSection(); + expect(findAll(renderer.root, 'Text').map(node => node.props.children)).toContain( + 'Not pinned to any repositories.' + ); + unmount(); + }); + + it('happy: lists only this profile’s bindings and unbinds one', async () => { + Object.assign(h.bindingsQuery, { + isLoading: false, + bindings: [ + { + repoFullName: 'acme/api', + platform: 'github', + profileId: 'profile-1', + profileName: 'Mine', + }, + { + repoFullName: 'acme/other', + platform: 'github', + profileId: 'profile-2', + profileName: 'Theirs', + }, + ], + }); + h.mutations.unbind.mutateAsync.mockResolvedValue({ success: true }); + + const { renderer, unmount } = await mountSection(); + + const texts = findAll(renderer.root, 'Text').map(node => node.props.children); + expect(texts).toContain('acme/api'); + expect(texts).not.toContain('acme/other'); + + pressPressable(renderer.root, 'Unbind'); + await waitFor(() => h.mutations.unbind.mutateAsync.mock.calls.length > 0); + expect(h.mutations.unbind.mutateAsync.mock.calls[0]?.[0]).toEqual({ + repoFullName: 'acme/api', + platform: 'github', + }); + + unmount(); + }); + + it('happy: Pin a repo binds the picked repo to this profile', async () => { + Object.assign(h.bindingsQuery, { isLoading: false }); + h.mutations.bind.mutateAsync.mockResolvedValue({ success: true }); + + const { renderer, unmount } = await mountSection(); + expect(findAll(renderer.root, 'SessionPageSheet')).toHaveLength(0); + pressButton(renderer.root, 'Pin a repo'); + expect(findAll(renderer.root, 'SessionPageSheet')).toHaveLength(1); + pressPressable(renderer.root, 'acme/infra'); + + await waitFor(() => h.mutations.bind.mutateAsync.mock.calls.length > 0); + expect(h.mutations.bind.mutateAsync.mock.calls[0]?.[0]).toEqual({ + profileId: 'profile-1', + repoFullName: 'acme/infra', + platform: 'gitlab', + }); + + unmount(); + }); +}); diff --git a/apps/mobile/src/components/profiles/profile-repo-pins-section.tsx b/apps/mobile/src/components/profiles/profile-repo-pins-section.tsx new file mode 100644 index 00000000000..89fc6db3877 --- /dev/null +++ b/apps/mobile/src/components/profiles/profile-repo-pins-section.tsx @@ -0,0 +1,189 @@ +import { useState } from 'react'; +import { useTranslation } from 'react-i18next'; +import { Pressable, View } from 'react-native'; +import { toast } from 'sonner-native'; + +import { RepoPickerSheet } from '@/components/profiles/repo-picker-sheet'; +import { + bindingsForProfile, + type RepoBindingPlatform, + type RepoOption, + repoPlatformBadge, +} from '@/components/profiles/repo-bindings-model'; +import { QueryError } from '@/components/query-error'; +import { Button } from '@/components/ui/button'; +import { GitBranch, Link2, Plus, Trash2 } from '@/components/ui/icons'; +import { Skeleton } from '@/components/ui/skeleton'; +import { Text } from '@/components/ui/text'; +import { + useRepoBindingMutations, + useRepoBindings, + useRepoOptions, +} from '@/lib/hooks/use-repo-bindings'; +import { useThemeColors } from '@/lib/hooks/use-theme-colors'; + +/** The mutation hook toasts `error.message`; the section supplies a fallback otherwise. */ +function hasUsableMessage(error: unknown): boolean { + return error instanceof Error && error.message.trim().length > 0; +} + +/** A card-shaped skeleton in the same slot as the loaded pins card. */ +function RepoPinsSkeleton() { + return ( + + + + + + + + + ); +} + +/** + * The `Pinned to repositories` block on the profile Overview: the repos this + * profile is bound to, with a `Pin a repo` picker and a per-row unbind. Ports + * the web `RepoPinsSection` (`ProfilesListDialog.tsx:895-1144`) to the phone: + * tap-to-pick, no drag. The loading skeleton reserves the card's space so the + * Overview does not jump when the bindings land. + */ +export function ProfileRepoPinsSection({ + profileId, + organizationId, +}: Readonly<{ profileId: string; organizationId?: string }>) { + const { t } = useTranslation(); + const colors = useThemeColors(); + const bindingsQuery = useRepoBindings(organizationId); + const { bind, unbind } = useRepoBindingMutations(organizationId); + const [pickerOpen, setPickerOpen] = useState(false); + const repoOptions = useRepoOptions(organizationId, pickerOpen); + + const pinned = bindingsForProfile(bindingsQuery.bindings, profileId); + + const handlePin = async (repo: RepoOption) => { + setPickerOpen(false); + try { + await bind.mutateAsync({ + profileId, + repoFullName: repo.fullName, + platform: repo.platform, + }); + } catch (error) { + if (!hasUsableMessage(error)) { + toast.error(t('profiles.repoBindings.pinFailed')); + } + return; + } + toast.success(t('profiles.repoBindings.pinnedToast', { repo: repo.fullName })); + }; + + const handleUnbind = async (repoFullName: string, platform: RepoBindingPlatform) => { + try { + await unbind.mutateAsync({ repoFullName, platform }); + } catch (error) { + if (!hasUsableMessage(error)) { + toast.error(t('profiles.repoBindings.unbindFailed')); + } + return; + } + toast.success(t('profiles.repoBindings.unboundToast')); + }; + + if (bindingsQuery.isLoading && !bindingsQuery.isError) { + return ; + } + + return ( + + + + + + {t('profiles.repoBindings.pinnedTitle')} + + + + + + + {t('profiles.repoBindings.pinnedDescription')} + + + {bindingsQuery.isError ? ( + void bindingsQuery.refetch()} + isRetrying={bindingsQuery.isRefetching} + /> + ) : null} + + {!bindingsQuery.isError && pinned.length === 0 ? ( + + {t('profiles.repoBindings.pinnedEmpty')} + + ) : null} + + {!bindingsQuery.isError && pinned.length > 0 ? ( + + {pinned.map(binding => ( + + + + {binding.repoFullName} + + + {repoPlatformBadge(binding.platform)} + + { + void handleUnbind(binding.repoFullName, binding.platform as RepoBindingPlatform); + }} + > + + + + ))} + + ) : null} + + {pickerOpen ? ( + { + repoOptions.refetch(); + }} + selectedKey={null} + onSelect={repo => { + void handlePin(repo); + }} + onClose={() => { + setPickerOpen(false); + }} + /> + ) : null} + + ); +} diff --git a/apps/mobile/src/components/profiles/profile-skills-screen.mounted.test.tsx b/apps/mobile/src/components/profiles/profile-skills-screen.mounted.test.tsx new file mode 100644 index 00000000000..5573ed739cf --- /dev/null +++ b/apps/mobile/src/components/profiles/profile-skills-screen.mounted.test.tsx @@ -0,0 +1,297 @@ +import { beforeEach, describe, expect, it, vi } from 'vitest'; + +import '@/i18n'; +import { + changeText, + confirmAlert, + findAll, + findField, + findOne, + mountScreen, + pressEmptyAction, + pressPressable, + pressSheetDone, + rerenderScreen, + type TestAlertMock, + testProfile, + testSkill, +} from '@/components/profiles/profile-skills-screen.test-helpers'; +import { act } from '@/test/renderer'; +import { waitFor } from '@/test/render-with-providers'; + +const h = vi.hoisted(() => ({ + alert: vi.fn(), + error: vi.fn(), + query: { + data: undefined as unknown, + isError: false, + isPending: true, + isRefetching: false, + refetch: vi.fn(), + }, + mutations: { + createCustomSkill: { mutateAsync: vi.fn(), isPending: false }, + updateSkill: { mutateAsync: vi.fn(), isPending: false }, + deleteSkill: { mutateAsync: vi.fn(), isPending: false }, + setSkillEnabled: { mutate: vi.fn(), isPending: false }, + }, +})); + +vi.mock('@/lib/hooks/use-agent-profiles', () => ({ + useAgentProfile: () => h.query, + useAgentProfileMutations: () => h.mutations, +})); +vi.mock('sonner-native', () => ({ toast: { error: h.error } })); +vi.mock('react-native', () => ({ + View: 'View', + ScrollView: 'ScrollView', + Pressable: 'Pressable', + Switch: 'Switch', + Alert: { alert: h.alert }, +})); +vi.mock('react-native-safe-area-context', () => ({ + useSafeAreaInsets: () => ({ top: 0, bottom: 0, left: 0, right: 0 }), +})); +vi.mock('@/lib/hooks/use-theme-colors', () => ({ + useThemeColors: () => ({ + mutedForeground: '#000000', + destructive: '#FF0000', + primaryForeground: '#000000', + }), +})); +vi.mock('@/components/agents/session-page-sheet', () => ({ + SessionPageSheet: 'SessionPageSheet', +})); +vi.mock('@/components/sheet-header', () => ({ SheetHeader: 'SheetHeader' })); +vi.mock('@/components/screen-header', () => ({ ScreenHeader: 'ScreenHeader' })); +vi.mock('@/components/query-error', () => ({ QueryError: 'QueryError' })); +vi.mock('@/components/empty-state', () => ({ EmptyState: 'EmptyState' })); +vi.mock('@/components/ui/button', () => ({ Button: 'Button' })); +vi.mock('@/components/ui/form-field', () => ({ FormField: 'FormField' })); +vi.mock('@/components/ui/skeleton', () => ({ Skeleton: 'Skeleton' })); +vi.mock('@/components/ui/text', () => ({ Text: 'Text' })); +vi.mock('@/components/ui/icons', () => ({ + Pencil: 'Pencil', + Sparkles: 'Sparkles', + Trash2: 'Trash2', +})); + +describe('ProfileSkillsScreen', () => { + beforeEach(() => { + vi.clearAllMocks(); + Object.assign(h.query, { + data: undefined, + isError: false, + isPending: true, + isRefetching: false, + }); + for (const mutation of Object.values(h.mutations)) { + Object.assign(mutation, { isPending: false }); + } + }); + + it('loading: renders skeletons and no rows', async () => { + const { renderer, unmount } = await mountScreen(); + + expect(findAll(renderer.root, 'Skeleton').length).toBeGreaterThan(0); + expect(findAll(renderer.root, 'Switch')).toHaveLength(0); + + unmount(); + }); + + it('error: renders QueryError and Retry refetches', async () => { + Object.assign(h.query, { isError: true, isPending: false }); + + const { renderer, unmount } = await mountScreen(); + + act(() => { + (findOne(renderer.root, 'QueryError').props as { onRetry: () => void }).onRetry(); + }); + expect(h.query.refetch).toHaveBeenCalledTimes(1); + + unmount(); + }); + + it('empty: shows skillsEmpty and the add CTA opens the sheet', async () => { + h.query.data = testProfile(); + h.query.isPending = false; + + const { renderer, unmount } = await mountScreen(); + + expect(findOne(renderer.root, 'EmptyState').props.title).toBe('No skills yet'); + pressEmptyAction(renderer); + expect(findOne(renderer.root, 'SheetHeader')).toBeTruthy(); + + unmount(); + }); + + it('happy: a row shows the name, source type, and status; the switch toggles', async () => { + h.query.data = testProfile([testSkill()]); + h.query.isPending = false; + + const { renderer, unmount } = await mountScreen(); + + const texts = findAll(renderer.root, 'Text').map(node => node.props.children); + expect(texts).toContain('code-review'); + expect(texts).toContain('custom'); + expect(texts).toContain('Enabled'); + + const toggle = findOne(renderer.root, 'Switch'); + expect(toggle.props.accessibilityLabel).toBe('code-review'); + act(() => { + (toggle.props as { onValueChange: (v: boolean) => void }).onValueChange(false); + }); + expect(h.mutations.setSkillEnabled.mutate.mock.calls[0]?.[0]).toEqual({ + profileId: 'profile-1', + skillId: 'skill-1', + enabled: false, + }); + + unmount(); + }); + + it('happy: the edit control opens the sheet seeded with the skill', async () => { + h.query.data = testProfile([testSkill()]); + h.query.isPending = false; + + const { renderer, unmount } = await mountScreen(); + pressPressable(renderer.root, 'Edit skill'); + + expect(findField(renderer.root, 'Skill name').props.defaultValue).toBe('code-review'); + expect(findField(renderer.root, 'Skill content').props.defaultValue).toBe('Body'); + + unmount(); + }); + + it('clears the stored description when frontmatter description is removed', async () => { + h.query.data = testProfile([ + testSkill({ + rawMarkdown: '---\nname: code-review\ndescription: Old description\n---\nBody', + }), + ]); + h.query.isPending = false; + h.mutations.updateSkill.mutateAsync.mockResolvedValue({ success: true }); + const { renderer, unmount } = await mountScreen(); + pressPressable(renderer.root, 'Edit skill'); + changeText(renderer.root, 'Skill content', '---\nname: code-review\n---\nBody'); + pressSheetDone(renderer.root); + await waitFor(() => h.mutations.updateSkill.mutateAsync.mock.calls.length > 0); + expect(h.mutations.updateSkill.mutateAsync.mock.calls[0]?.[0]).toMatchObject({ + skillId: 'skill-1', + description: null, + }); + unmount(); + }); + + it('happy: a valid add saves through createCustomSkill and closes the sheet', async () => { + h.query.data = testProfile(); + h.query.isPending = false; + h.mutations.createCustomSkill.mutateAsync.mockResolvedValue({ id: 'skill-2' }); + + const { renderer, unmount } = await mountScreen(); + pressEmptyAction(renderer); + changeText(renderer.root, 'Skill name', 'my-skill'); + changeText(renderer.root, 'Skill content', 'Body'); + pressSheetDone(renderer.root); + + await waitFor(() => h.mutations.createCustomSkill.mutateAsync.mock.calls.length > 0); + expect(h.mutations.createCustomSkill.mutateAsync.mock.calls[0]?.[0]).toEqual({ + profileId: 'profile-1', + name: 'my-skill', + rawMarkdown: 'Body', + }); + await waitFor(() => findAll(renderer.root, 'SheetHeader').length === 0); + + unmount(); + }); + + it('happy: frontmatter in the content prefills the name field', async () => { + h.query.data = testProfile(); + h.query.isPending = false; + + const { renderer, unmount } = await mountScreen(); + pressEmptyAction(renderer); + changeText( + renderer.root, + 'Skill content', + ['---', 'name: pasted-skill', '---', 'Body'].join('\n') + ); + + expect(findField(renderer.root, 'Skill name').props.defaultValue).toBe('pasted-skill'); + + unmount(); + }); + + it('non-retryable: an empty name is refused and nothing persists', async () => { + h.query.data = testProfile(); + h.query.isPending = false; + + const { renderer, unmount } = await mountScreen(); + pressEmptyAction(renderer); + pressSheetDone(renderer.root); + + expect(h.mutations.createCustomSkill.mutateAsync).not.toHaveBeenCalled(); + expect(findField(renderer.root, 'Skill name').props.error).toBe('Enter a skill name'); + expect(findOne(renderer.root, 'SheetHeader')).toBeTruthy(); + + unmount(); + }); + + it('retryable: a failure with no message toasts the fallback and keeps the sheet', async () => { + h.query.data = testProfile(); + h.query.isPending = false; + const error = new Error('placeholder'); + error.message = ''; + h.mutations.createCustomSkill.mutateAsync.mockRejectedValue(error); + + const { renderer, unmount } = await mountScreen(); + pressEmptyAction(renderer); + changeText(renderer.root, 'Skill name', 'my-skill'); + changeText(renderer.root, 'Skill content', 'Body'); + pressSheetDone(renderer.root); + + await waitFor(() => h.error.mock.calls.length > 0); + expect(h.error).toHaveBeenCalledWith("Couldn't save skill"); + expect(findOne(renderer.root, 'SheetHeader')).toBeTruthy(); + + unmount(); + }); + + it('happy: confirming delete calls deleteSkill', async () => { + h.query.data = testProfile([testSkill()]); + h.query.isPending = false; + h.mutations.deleteSkill.mutateAsync.mockResolvedValue({ success: true }); + + const { renderer, unmount } = await mountScreen(); + pressPressable(renderer.root, 'Delete'); + await act(async () => { + confirmAlert(h.alert as TestAlertMock); + await Promise.resolve(); + }); + + await waitFor(() => h.mutations.deleteSkill.mutateAsync.mock.calls.length > 0); + expect(h.mutations.deleteSkill.mutateAsync.mock.calls[0]?.[0]).toEqual({ + profileId: 'profile-1', + skillId: 'skill-1', + }); + + unmount(); + }); + + it('keeps the rows while a refetch is in flight', async () => { + h.query.data = testProfile([testSkill()]); + h.query.isPending = false; + + const { renderer, queryClient, unmount } = await mountScreen(); + + h.query.isRefetching = true; + await act(async () => { + rerenderScreen(renderer, queryClient); + await Promise.resolve(); + }); + + expect(findOne(renderer.root, 'Switch').props.accessibilityLabel).toBe('code-review'); + + unmount(); + }); +}); diff --git a/apps/mobile/src/components/profiles/profile-skills-screen.test-helpers.ts b/apps/mobile/src/components/profiles/profile-skills-screen.test-helpers.ts new file mode 100644 index 00000000000..55d54fe56c2 --- /dev/null +++ b/apps/mobile/src/components/profiles/profile-skills-screen.test-helpers.ts @@ -0,0 +1,131 @@ +import { createElement } from 'react'; +import { type QueryClient, QueryClientProvider } from '@tanstack/react-query'; + +import { ProfileSkillsScreen } from '@/components/profiles/profile-skills-screen'; +import { act, type ReactTestInstance, type ReactTestRenderer } from '@/test/renderer'; +import { renderWithProviders } from '@/test/render-with-providers'; + +/** + * Shared harness for the Profile Skills mounted tests. The `vi.mock` calls + * stay in the test file (they must be hoisted there); everything that does not + * depend on the hoisted mock state lives here. + */ + +export type TestSkill = { + id: string; + name: string; + sourceType: string; + enabled: boolean; + rawMarkdown: string; +}; + +/** The profile detail the mocked `useAgentProfile` returns. */ +export type TestProfileDetail = { id: string; skills: TestSkill[] }; + +export function testProfile(skills: TestSkill[] = []): TestProfileDetail { + return { id: 'profile-1', skills }; +} + +export function testSkill(overrides: Partial = {}): TestSkill { + return { + id: 'skill-1', + name: 'code-review', + sourceType: 'custom', + enabled: true, + rawMarkdown: 'Body', + ...overrides, + }; +} + +export function findAll(root: ReactTestInstance, type: string): ReactTestInstance[] { + return root.findAll(node => typeof node.type === 'string' && node.type === type); +} + +export function findOne(root: ReactTestInstance, type: string): ReactTestInstance { + const node = findAll(root, type)[0]; + if (!node) { + throw new Error(`${type} was not rendered`); + } + return node; +} + +export function findField(root: ReactTestInstance, label: string): ReactTestInstance { + const field = findAll(root, 'FormField').find(node => node.props.label === label); + if (!field) { + throw new Error(`field ${label} was not rendered`); + } + return field; +} + +function findPressable(root: ReactTestInstance, label: string): ReactTestInstance { + const pressable = findAll(root, 'Pressable').find( + node => node.props.accessibilityLabel === label + ); + if (!pressable) { + throw new Error(`pressable ${label} was not rendered`); + } + return pressable; +} + +export function pressPressable(root: ReactTestInstance, label: string): void { + const pressable = findPressable(root, label); + act(() => { + (pressable.props as { onPress: () => void }).onPress(); + }); +} + +/** Press the add CTA the empty state renders through its `action` prop. */ +export function pressEmptyAction(renderer: ReactTestRenderer): void { + const action = findOne(renderer.root, 'EmptyState').props.action as { + props: { onPress: () => void }; + }; + act(() => { + action.props.onPress(); + }); +} + +export function changeText(root: ReactTestInstance, label: string, value: string): void { + act(() => { + (findField(root, label).props as { onChangeText: (v: string) => void }).onChangeText(value); + }); +} + +/** Press the sheet's Save, which the mocked `SheetHeader` exposes as `onDone`. */ +export function pressSheetDone(root: ReactTestInstance): void { + act(() => { + (findOne(root, 'SheetHeader').props as { onDone: () => void }).onDone(); + }); +} + +/** The mock surface `Alert.alert` exposes to the harness. */ +export type TestAlertMock = { mock: { calls: unknown[][] } }; + +export function confirmAlert(alert: TestAlertMock): void { + const buttons = alert.mock.calls[0]?.[2] as { style: string; onPress?: () => void }[] | undefined; + const destructive = buttons?.find(button => button.style === 'destructive'); + if (!destructive?.onPress) { + throw new Error('destructive alert button was not registered'); + } + destructive.onPress(); +} + +// eslint-disable-next-line typescript-eslint/promise-function-async -- returning the harness promise unchanged +export function mountScreen(organizationId?: string) { + return renderWithProviders( + createElement(ProfileSkillsScreen, { profileId: 'profile-1', organizationId }) + ); +} + +export function rerenderScreen( + renderer: ReactTestRenderer, + queryClient: QueryClient, + organizationId?: string +): void { + renderer.update( + createElement( + QueryClientProvider, + { client: queryClient }, + createElement(ProfileSkillsScreen, { profileId: 'profile-1', organizationId }) + ) + ); +} diff --git a/apps/mobile/src/components/profiles/profile-skills-screen.tsx b/apps/mobile/src/components/profiles/profile-skills-screen.tsx new file mode 100644 index 00000000000..59b93546d73 --- /dev/null +++ b/apps/mobile/src/components/profiles/profile-skills-screen.tsx @@ -0,0 +1,252 @@ +import { type ReactNode, useState } from 'react'; +import { useTranslation } from 'react-i18next'; +import { Alert, Pressable, ScrollView, Switch, View } from 'react-native'; +import { toast } from 'sonner-native'; + +import { EmptyState } from '@/components/empty-state'; +import { skillRowSubtitle } from '@/components/profiles/skill-markdown'; +import { SkillFormSheet, type SkillFormSubmission } from '@/components/profiles/skill-form-sheet'; +import { QueryError } from '@/components/query-error'; +import { ScreenHeader } from '@/components/screen-header'; +import { Button } from '@/components/ui/button'; +import { Pencil, Sparkles, Trash2 } from '@/components/ui/icons'; +import { Skeleton } from '@/components/ui/skeleton'; +import { Text } from '@/components/ui/text'; +import { + type AgentProfileDetail, + useAgentProfile, + useAgentProfileMutations, +} from '@/lib/hooks/use-agent-profiles'; +import { useThemeColors } from '@/lib/hooks/use-theme-colors'; + +/** Stable empty fallback so the derived list is not a new array each render. */ +const NO_SKILLS: AgentProfileDetail['skills'] = []; + +/** The skill the sheet edits; `null` opens the sheet in add mode. */ +type SkillTarget = { id: string; name: string; rawMarkdown: string }; + +type SkillRowProps = Readonly<{ + skill: AgentProfileDetail['skills'][number]; + onToggle: (enabled: boolean) => void; + onEdit: () => void; + onDelete: () => void; +}>; + +/** + * The mutation hook toasts `error.message`; the screen only supplies a + * fallback when the server sent nothing readable. + */ +function hasUsableMessage(error: unknown): boolean { + return error instanceof Error && error.message.trim().length > 0; +} + +/** + * One skill row: the name in mono, its source type, the enabled status, the + * toggle, and edit/delete controls. The row carries no container + * `accessibilityLabel`, so the switch's label stays the only element a screen + * reader matches by the skill's name. + */ +function SkillRow({ skill, onToggle, onEdit, onDelete }: Readonly) { + const { t } = useTranslation(); + const colors = useThemeColors(); + return ( + + + + {skill.name} + + + {skillRowSubtitle(skill)} + + + + {skill.enabled ? t('common.enabled') : t('common.disabled')} + + + + + + + + + + ); +} + +/** Content-shaped rows in the same slot and height as a loaded skill row. */ +function SkillsSkeleton() { + return ( + + {[0, 1, 2].map(index => ( + + ))} + + ); +} + +export function ProfileSkillsScreen({ + profileId, + organizationId, +}: Readonly<{ profileId: string; organizationId?: string }>) { + const { t } = useTranslation(); + const profileQuery = useAgentProfile(profileId, organizationId); + const { createCustomSkill, updateSkill, deleteSkill, setSkillEnabled } = + useAgentProfileMutations(organizationId); + + const skills = profileQuery.data?.skills ?? NO_SKILLS; + // `null` = closed, `{ skill: null }` = add, `{ skill }` = edit that skill. + const [form, setForm] = useState<{ skill: SkillTarget | null } | null>(null); + + const startAdd = () => { + setForm({ skill: null }); + }; + + const closeForm = () => { + setForm(null); + }; + + const saveSkill = async (target: SkillTarget | null, submission: SkillFormSubmission) => { + try { + await (target === null + ? createCustomSkill.mutateAsync({ profileId, ...submission }) + : updateSkill.mutateAsync({ + profileId, + skillId: target.id, + ...submission, + description: submission.description ?? null, + })); + } catch (error) { + if (!hasUsableMessage(error)) { + toast.error(t('profiles.skillSaveFailed')); + } + return; + } + closeForm(); + }; + + const runDelete = async (skillId: string) => { + try { + await deleteSkill.mutateAsync({ profileId, skillId }); + } catch (error) { + if (!hasUsableMessage(error)) { + toast.error(t('profiles.skillSaveFailed')); + } + } + }; + + const confirmDelete = (skill: SkillTarget) => { + Alert.alert(t('common.delete'), skill.name, [ + { text: t('common.cancel'), style: 'cancel' }, + { + text: t('common.delete'), + style: 'destructive', + onPress: () => { + void runDelete(skill.id); + }, + }, + ]); + }; + + let content: ReactNode = null; + if (profileQuery.isError) { + content = ( + void profileQuery.refetch()} + isRetrying={profileQuery.isRefetching} + /> + ); + } else if (profileQuery.isPending) { + content = ; + } else if (skills.length === 0) { + content = ( + + {t('profiles.addSkill')} + + } + /> + ); + } else { + content = ( + <> + {skills.map(skill => ( + { + setSkillEnabled.mutate( + { profileId, skillId: skill.id, enabled }, + { + onError: error => { + if (!hasUsableMessage(error)) { + toast.error(t('profiles.skillSaveFailed')); + } + }, + } + ); + }} + onEdit={() => { + setForm({ + skill: { id: skill.id, name: skill.name, rawMarkdown: skill.rawMarkdown }, + }); + }} + onDelete={() => { + confirmDelete({ + id: skill.id, + name: skill.name, + rawMarkdown: skill.rawMarkdown, + }); + }} + /> + ))} + + + ); + } + + return ( + + + + {content} + + + {form !== null ? ( + { + void saveSkill(form.skill, submission); + }} + /> + ) : null} + + ); +} diff --git a/apps/mobile/src/components/profiles/profile-variables-model.test.ts b/apps/mobile/src/components/profiles/profile-variables-model.test.ts new file mode 100644 index 00000000000..f1e86c00e61 --- /dev/null +++ b/apps/mobile/src/components/profiles/profile-variables-model.test.ts @@ -0,0 +1,106 @@ +import { describe, expect, it } from 'vitest'; + +import { + applyVariableEdit, + SECRET_MASK, + validateVariableInput, + variableRows, +} from '@/components/profiles/profile-variables-model'; + +describe('variableRows', () => { + it('returns no rows for an empty list', () => { + expect(variableRows([])).toEqual([]); + }); + + it('masks a secret and shows a plain value as-is', () => { + const rows = variableRows([ + { key: 'API_KEY', value: '***', isSecret: true }, + { key: 'NODE_ENV', value: 'production', isSecret: false }, + ]); + + expect(rows).toEqual([ + { key: 'API_KEY', value: '***', isSecret: true, maskedValue: SECRET_MASK }, + { + key: 'NODE_ENV', + value: 'production', + isSecret: false, + maskedValue: 'production', + }, + ]); + }); +}); + +describe('validateVariableInput', () => { + it('accepts a non-empty key within the 1..256 bound', () => { + expect(validateVariableInput({ key: 'API_KEY', value: '' })).toBeNull(); + expect(validateVariableInput({ key: 'a'.repeat(256), value: 'x' })).toBeNull(); + }); + + it('reports an empty key after trimming', () => { + expect(validateVariableInput({ key: '', value: 'x' })).toBe('empty'); + expect(validateVariableInput({ key: ' ', value: 'x' })).toBe('empty'); + }); + + it('reports a key longer than 256 characters', () => { + expect(validateVariableInput({ key: 'a'.repeat(257), value: 'x' })).toBe('too-long'); + }); + + it('reports a key that cleans onto an existing stored key', () => { + expect(validateVariableInput({ key: 'foo-bar', value: 'x' }, ['FOO_BAR'])).toBe('duplicate'); + expect(validateVariableInput({ key: 'FOO_BAR', value: 'x' }, ['FOO_BAR'])).toBe('duplicate'); + // A different key, or no stored keys, still passes. + expect(validateVariableInput({ key: 'foo-bar', value: 'x' }, ['OTHER_KEY'])).toBeNull(); + expect(validateVariableInput({ key: 'foo-bar', value: 'x' })).toBeNull(); + }); +}); + +describe('applyVariableEdit', () => { + const vars = [ + { key: 'A', value: '1', isSecret: false }, + { key: 'C', value: '3', isSecret: false }, + ]; + + it('replaces the value of the row with the same key', () => { + expect(applyVariableEdit(vars, { key: 'A', value: '2', isSecret: true })).toEqual([ + { key: 'A', value: '2', isSecret: true }, + { key: 'C', value: '3', isSecret: false }, + ]); + }); + + it('appends a new key and keeps the key order', () => { + expect(applyVariableEdit(vars, { key: 'B', value: '2', isSecret: false })).toEqual([ + { key: 'A', value: '1', isSecret: false }, + { key: 'B', value: '2', isSecret: false }, + { key: 'C', value: '3', isSecret: false }, + ]); + }); + + it('leaves the source list untouched', () => { + applyVariableEdit(vars, { key: 'B', value: '2', isSecret: false }); + expect(vars).toEqual([ + { key: 'A', value: '1', isSecret: false }, + { key: 'C', value: '3', isSecret: false }, + ]); + }); + + it('sorts without Array.prototype.toSorted, which Hermes lacks', () => { + // The device runtime is Hermes: Array.prototype.toSorted does not exist + // there, so the sort must go through the mutating API on a copy. Deleting + // it in Node reproduces the device crash (`undefined is not a function`). + const descriptor = Object.getOwnPropertyDescriptor(Array.prototype, 'toSorted'); + // oxlint-disable-next-line typescript-eslint/no-dynamic-delete -- the test deletes the ES2023 built-in to reproduce the Hermes runtime, then restores it below + delete (Array.prototype as unknown as Record).toSorted; + try { + expect(applyVariableEdit(vars, { key: 'B', value: '2', isSecret: false })).toEqual([ + { key: 'A', value: '1', isSecret: false }, + { key: 'B', value: '2', isSecret: false }, + { key: 'C', value: '3', isSecret: false }, + ]); + } finally { + if (descriptor) { + // oxlint-disable-next-line no-extend-native -- restores exactly the built-in deleted above, so later tests keep the real Array prototype + Object.defineProperty(Array.prototype, 'toSorted', descriptor); + } + } + }); +}); diff --git a/apps/mobile/src/components/profiles/profile-variables-model.ts b/apps/mobile/src/components/profiles/profile-variables-model.ts new file mode 100644 index 00000000000..1dd4a759c31 --- /dev/null +++ b/apps/mobile/src/components/profiles/profile-variables-model.ts @@ -0,0 +1,104 @@ +/** + * Pure view-model helpers for the profile Environment variables screen. + * + * No React and no React Native imports: every function here is unit-tested + * directly in `profile-variables-model.test.ts`. The screen owns rendering and + * network calls; this module owns the masked/visible row shape, the client-side + * validation that runs before a `setVar`, and the local list update a saved + * edit applies. + */ + +import { cleanVariableKey, VARIABLE_KEY_MAX_LENGTH } from '@/lib/agent-profile-forms'; + +/** The fields of a variable the screen reads. Structural, so a test needs no tRPC type. */ +export type ProfileVarSource = Readonly<{ + key: string; + value: string; + isSecret: boolean; +}>; + +/** One environment-variable row as the screen renders it. */ +export type VariableRow = Readonly<{ + key: string; + /** The value the server returned. A secret arrives already masked as `***`. */ + value: string; + isSecret: boolean; + /** What the row shows while hidden: a dot run for a secret, the value otherwise. */ + maskedValue: string; +}>; + +/** The dot run a hidden secret renders instead of its value. */ +export const SECRET_MASK = '••••••••'; + +/** + * Render rows from the profile's variables. A secret is masked by default: the + * server only ever sends `***` for one, so a row the user just saved — whose + * value is still local — is the only place a secret value can be revealed. + */ +export function variableRows(vars: readonly ProfileVarSource[]): VariableRow[] { + return vars.map(v => ({ + key: v.key, + value: v.value, + isSecret: v.isSecret, + maskedValue: v.isSecret ? SECRET_MASK : v.value, + })); +} + +export type VariableInputError = 'empty' | 'too-long' | 'duplicate'; + +/** + * Validate the edited variable before a save. Matches the server's + * `VarSchema` key bound (`z.string().min(1).max(256)`); `value` is part of the + * form's input but unconstrained, matching the server. `takenKeys` are the + * profile's stored keys: the server upserts on `(profile_id, key)` with the + * cleaned key the form sends, so a new key that normalizes onto an existing one + * would silently replace that variable. Returns an error code, or `null` when + * valid. + */ +export function validateVariableInput( + input: Readonly<{ key: string; value: string }>, + takenKeys: readonly string[] = [] +): VariableInputError | null { + const key = input.key.trim(); + if (key.length === 0) { + return 'empty'; + } + if (key.length > VARIABLE_KEY_MAX_LENGTH) { + return 'too-long'; + } + if (takenKeys.includes(cleanVariableKey(key))) { + return 'duplicate'; + } + return null; +} + +/** A saved environment variable, as the screen sends it to `setVar`. */ +export type VariableEdit = Readonly<{ key: string; value: string; isSecret: boolean }>; + +/** + * Apply a saved edit to the local list: replace the row with the same key, or + * append a new one, and keep the key order the `get` query returns. The screen + * calls this after `setVar` resolves so the row shows the saved value while the + * invalidated detail query refetches. + */ +export function applyVariableEdit( + vars: readonly ProfileVarSource[], + edit: VariableEdit +): ProfileVarSource[] { + const exists = vars.some(v => v.key === edit.key); + const next = exists + ? vars.map(v => (v.key === edit.key ? { ...v, value: edit.value, isSecret: edit.isSecret } : v)) + : [...vars, { key: edit.key, value: edit.value, isSecret: edit.isSecret }]; + // eslint-disable-next-line unicorn/no-array-sort -- Hermes does not implement Array.prototype.toSorted; map/spread already copies so nothing shared is mutated + return next.sort(compareVariableKeys); +} + +function compareVariableKeys(a: ProfileVarSource, b: ProfileVarSource): number { + if (a.key < b.key) { + return -1; + } + if (a.key > b.key) { + return 1; + } + return 0; +} diff --git a/apps/mobile/src/components/profiles/profile-variables-rows.tsx b/apps/mobile/src/components/profiles/profile-variables-rows.tsx new file mode 100644 index 00000000000..ba354fdf01f --- /dev/null +++ b/apps/mobile/src/components/profiles/profile-variables-rows.tsx @@ -0,0 +1,287 @@ +import { useRef, useState } from 'react'; +import { useTranslation } from 'react-i18next'; +import { Pressable, Switch, View } from 'react-native'; + +import { + validateVariableInput, + type VariableEdit, + type VariableInputError, + type VariableRow, +} from '@/components/profiles/profile-variables-model'; +import { Button } from '@/components/ui/button'; +import { FormField } from '@/components/ui/form-field'; +import { Eye, EyeOff, Lock, Trash2 } from '@/components/ui/icons'; +import { Skeleton } from '@/components/ui/skeleton'; +import { Text } from '@/components/ui/text'; +import { cleanVariableKey } from '@/lib/agent-profile-forms'; +import { useThemeColors } from '@/lib/hooks/use-theme-colors'; + +/** Content-shaped rows in the same slot and height as a loaded variable row. */ +export function VariablesSkeleton() { + return ( + + {[0, 1, 2].map(index => ( + + ))} + + ); +} + +/** + * Inline edit copy for a refused input. Every case is refused before a save is + * attempted, so none uses the screen's save-failed fallback: an empty key is + * required, an over-long key states the server's 256-character bound, and a key + * that cleans onto an existing one says so instead of silently overwriting it. + */ +function variableInputErrorMessage(t: (key: string) => string, error: VariableInputError): string { + if (error === 'empty') { + return t('common.required'); + } + return error === 'duplicate' ? t('profiles.keyDuplicate') : t('profiles.keyTooLong'); +} + +/** + * The empty value field's copy, mirroring the web editor: editing a stored + * secret asks for a new value to rotate it, a new variable marked secret says + * the value will be encrypted, and every non-secret value field reads "Value". + */ +function valueFieldPlaceholder( + t: (key: string) => string, + isSecret: boolean, + isNew: boolean +): string { + if (!isSecret) { + return t('profiles.valueLabel'); + } + return isNew ? t('profiles.secrets.valuePlaceholder') : t('profiles.secrets.newValuePlaceholder'); +} + +type VariableRowViewProps = Readonly<{ + row: VariableRow; + /** + * Optional row-level reveal. The profile Variables screen omits it so a + * secret is only ever revealed from the edit form; the draft editors pass it + * so a value the user is still composing can be checked. + */ + revealed?: boolean; + isDeleting?: boolean; + onToggleReveal?: () => void; + onEdit: () => void; + onDelete: () => void; +}>; + +/** + * One variable row: the key in mono and the value. A secret never renders its + * stored value — the server sends `***`, and the row shows the dot run instead. + * When the caller passes `onToggleReveal`, the row offers a reveal control; + * otherwise a secret stays masked. + */ +export function VariableRowView({ + row, + revealed = false, + isDeleting = false, + onToggleReveal, + onEdit, + onDelete, +}: VariableRowViewProps) { + const { t } = useTranslation(); + const colors = useThemeColors(); + return ( + + + + {row.key} + + {row.isSecret && onToggleReveal === undefined ? ( + + + + {row.maskedValue} + + + ) : ( + + {revealed ? row.value : row.maskedValue} + + )} + + {row.isSecret && onToggleReveal !== undefined ? ( + + {revealed ? ( + + ) : ( + + )} + + ) : null} + + + + + ); +} + +type VariableEditFormProps = Readonly<{ + isNew: boolean; + initial: VariableEdit; + /** + * The profile's stored keys, so a new key that cleans onto an existing one is + * refused rather than silently overwriting it (the server upserts on + * `(profile_id, key)`). Ignored for an edit, whose key is fixed. + */ + existingKeys?: readonly string[]; + isSaving: boolean; + /** + * The bound the value must satisfy, when the caller's target is stricter + * than the profile's own 10000-character server cap. The manual session + * editor passes the prepare-session limit, so an over-long value is not + * typeable in the first place. + */ + maxValueLength?: number; + onCancel: () => void; + onSave: (edit: VariableEdit) => Promise; +}>; + +/** + * The inline add/edit form. Uncontrolled fields read through refs; `hasValue` + * is derived UI only, so the Save button disables until a secret edit has a new + * value to store (the server returns a masked `***`, which must not be saved + * back). A failed save leaves the form mounted with the typed values. + */ +export function VariableEditForm({ + isNew, + initial, + existingKeys = [], + isSaving, + maxValueLength, + onCancel, + onSave, +}: VariableEditFormProps) { + const { t } = useTranslation(); + const colors = useThemeColors(); + const keyRef = useRef(initial.key); + const valueRef = useRef(initial.value); + const [isSecret, setIsSecret] = useState(initial.isSecret); + const [revealed, setRevealed] = useState(false); + const [hasValue, setHasValue] = useState(initial.value.trim().length > 0); + const [error, setError] = useState(null); + + const canSave = !isSecret || hasValue; + const valuePlaceholder = valueFieldPlaceholder(t, isSecret, isNew); + + const submit = async () => { + const key = keyRef.current; + if (isNew) { + const invalid = validateVariableInput({ key, value: valueRef.current }, existingKeys); + if (invalid !== null) { + setError(invalid); + return; + } + } + setError(null); + await onSave({ + key: isNew ? cleanVariableKey(key.trim()) : initial.key, + value: valueRef.current, + isSecret, + }); + }; + + return ( + + { + keyRef.current = value; + if (error !== null) { + setError(validateVariableInput({ key: value, value: valueRef.current }, existingKeys)); + } + }} + /> + { + valueRef.current = value; + setHasValue(value.trim().length > 0); + }} + /> + {isSecret ? ( + { + setRevealed(current => !current); + }} + > + {revealed ? ( + + ) : ( + + )} + {t('profiles.secrets.reveal')} + + ) : null} + + + + {t('profiles.secrets.markSecret')} + + + + + + + + + ); +} diff --git a/apps/mobile/src/components/profiles/profile-variables-screen.mounted.test.tsx b/apps/mobile/src/components/profiles/profile-variables-screen.mounted.test.tsx new file mode 100644 index 00000000000..e6d8049a8b2 --- /dev/null +++ b/apps/mobile/src/components/profiles/profile-variables-screen.mounted.test.tsx @@ -0,0 +1,372 @@ +import { beforeEach, describe, expect, it, vi } from 'vitest'; + +import '@/i18n'; +import { + changeText, + confirmAlert, + findAll, + findField, + findOne, + findPressable, + mountScreen, + pressButton, + pressPressable, + rerenderScreen, + type TestAlertMock, + testProfile, + type TestProfileDetail, +} from '@/components/profiles/profile-variables-screen.test-helpers'; +import { act } from '@/test/renderer'; +import { waitFor } from '@/test/render-with-providers'; + +const h = vi.hoisted(() => ({ + alert: vi.fn(), + error: vi.fn(), + query: { + data: undefined as TestProfileDetail | undefined, + isError: false, + isPending: true, + isRefetching: false, + refetch: vi.fn(), + }, + mutations: { + setVar: { mutateAsync: vi.fn(), isPending: false }, + deleteVar: { mutateAsync: vi.fn(), isPending: false }, + }, +})); + +vi.mock('@/lib/hooks/use-agent-profiles', () => ({ + useAgentProfile: () => h.query, + useAgentProfileMutations: () => h.mutations, +})); +vi.mock('sonner-native', () => ({ toast: { error: h.error } })); +vi.mock('react-native', () => ({ + View: 'View', + ScrollView: 'ScrollView', + Pressable: 'Pressable', + Switch: 'Switch', + Alert: { alert: h.alert }, +})); +vi.mock('@/lib/hooks/use-theme-colors', () => ({ + useThemeColors: () => ({ + mutedForeground: '#000000', + secondaryForeground: '#000000', + destructive: '#FF0000', + }), +})); +vi.mock('@/components/screen-header', () => ({ ScreenHeader: 'ScreenHeader' })); +vi.mock('@/components/query-error', () => ({ QueryError: 'QueryError' })); +vi.mock('@/components/empty-state', () => ({ EmptyState: 'EmptyState' })); +vi.mock('@/components/ui/button', () => ({ Button: 'Button' })); +vi.mock('@/components/ui/form-field', () => ({ FormField: 'FormField' })); +vi.mock('@/components/ui/skeleton', () => ({ Skeleton: 'Skeleton' })); +vi.mock('@/components/ui/text', () => ({ Text: 'Text' })); +vi.mock('@/components/ui/icons', () => ({ + Eye: 'Eye', + EyeOff: 'EyeOff', + KeyRound: 'KeyRound', + Lock: 'Lock', + Trash2: 'Trash2', +})); + +/** Press the add CTA the empty state renders through its `action` prop. */ +function pressEmptyAction(renderer: Awaited>['renderer']): void { + const action = findOne(renderer.root, 'EmptyState').props.action as { + props: { onPress: () => void }; + }; + act(() => { + action.props.onPress(); + }); +} + +describe('ProfileVariablesScreen', () => { + beforeEach(() => { + vi.clearAllMocks(); + Object.assign(h.query, { + data: undefined, + isError: false, + isPending: true, + isRefetching: false, + }); + Object.assign(h.mutations.setVar, { isPending: false }); + Object.assign(h.mutations.deleteVar, { isPending: false }); + }); + + it('loading: renders skeletons and no rows', async () => { + const { renderer, unmount } = await mountScreen(); + + expect(findAll(renderer.root, 'Skeleton').length).toBeGreaterThan(0); + expect(findAll(renderer.root, 'FormField')).toHaveLength(0); + + unmount(); + }); + + it('error: renders QueryError and Retry refetches', async () => { + Object.assign(h.query, { isError: true, isPending: false }); + + const { renderer, unmount } = await mountScreen(); + + const queryError = findOne(renderer.root, 'QueryError'); + act(() => { + (queryError.props as { onRetry: () => void }).onRetry(); + }); + expect(h.query.refetch).toHaveBeenCalledTimes(1); + + unmount(); + }); + + it('empty: shows variablesEmpty and the add CTA opens the form', async () => { + h.query.data = testProfile(); + h.query.isPending = false; + + const { renderer, unmount } = await mountScreen(); + + expect(findOne(renderer.root, 'EmptyState').props.title).toBe('No variables yet'); + pressEmptyAction(renderer); + expect(findField(renderer.root, 'Key')).toBeTruthy(); + + unmount(); + }); + + it('happy: an added variable is saved with a cleaned key and shown', async () => { + h.query.data = testProfile(); + h.query.isPending = false; + h.mutations.setVar.mutateAsync.mockResolvedValue({ success: true }); + + const { renderer, unmount } = await mountScreen(); + pressEmptyAction(renderer); + changeText(renderer.root, 'Key', 'api key'); + changeText(renderer.root, 'Value', 'shh'); + pressButton(renderer.root, 'Save'); + + await waitFor(() => h.mutations.setVar.mutateAsync.mock.calls.length > 0); + expect(h.mutations.setVar.mutateAsync.mock.calls[0]?.[0]).toEqual({ + profileId: 'profile-1', + key: 'API_KEY', + value: 'shh', + isSecret: false, + }); + await waitFor(() => + findAll(renderer.root, 'Pressable').some(node => node.props.accessibilityLabel === 'API_KEY') + ); + + unmount(); + }); + + it('non-retryable: an empty key is refused before the call', async () => { + h.query.data = testProfile(); + h.query.isPending = false; + + const { renderer, unmount } = await mountScreen(); + pressEmptyAction(renderer); + pressButton(renderer.root, 'Save'); + + expect(h.mutations.setVar.mutateAsync).not.toHaveBeenCalled(); + expect(findField(renderer.root, 'Key').props.error).toBe('required'); + + unmount(); + }); + + it('non-retryable: an over-long key names the key length before the call', async () => { + h.query.data = testProfile(); + h.query.isPending = false; + + const { renderer, unmount } = await mountScreen(); + pressEmptyAction(renderer); + changeText(renderer.root, 'Key', 'a'.repeat(257)); + pressButton(renderer.root, 'Save'); + + expect(h.mutations.setVar.mutateAsync).not.toHaveBeenCalled(); + expect(findField(renderer.root, 'Key').props.error).toBe('Key must be 256 characters or fewer'); + + unmount(); + }); + + it('non-retryable: a new key that cleans onto an existing one is refused', async () => { + h.query.data = testProfile([{ key: 'FOO_BAR', value: '1', isSecret: false }]); + h.query.isPending = false; + + const { renderer, unmount } = await mountScreen(); + pressButton(renderer.root, 'Add variable'); + changeText(renderer.root, 'Key', 'foo-bar'); + changeText(renderer.root, 'Value', '2'); + pressButton(renderer.root, 'Save'); + + // The server upserts on (profile_id, key), so `foo-bar` would silently + // replace `FOO_BAR`; the form refuses it instead of calling setVar. + expect(h.mutations.setVar.mutateAsync).not.toHaveBeenCalled(); + expect(findField(renderer.root, 'Key').props.error).toBe( + 'This key already exists in the profile' + ); + + unmount(); + }); + + it('retryable: a failure with no message toasts the fallback and keeps the form', async () => { + h.query.data = testProfile(); + h.query.isPending = false; + const error = new Error('placeholder'); + error.message = ''; + h.mutations.setVar.mutateAsync.mockRejectedValue(error); + + const { renderer, unmount } = await mountScreen(); + pressEmptyAction(renderer); + changeText(renderer.root, 'Key', 'API_KEY'); + changeText(renderer.root, 'Value', 'shh'); + pressButton(renderer.root, 'Save'); + + await waitFor(() => h.error.mock.calls.length > 0); + expect(h.error).toHaveBeenCalledWith("Couldn't save variable"); + expect(findField(renderer.root, 'Key')).toBeTruthy(); + + unmount(); + }); + + it('happy: tapping a row edits its value under the same key', async () => { + h.query.data = testProfile([{ key: 'API_KEY', value: '1', isSecret: false }]); + h.query.isPending = false; + h.mutations.setVar.mutateAsync.mockResolvedValue({ success: true }); + + const { renderer, unmount } = await mountScreen(); + pressPressable(renderer.root, 'API_KEY'); + + const keyField = findField(renderer.root, 'Key'); + expect(keyField.props.disabled).toBe(true); + expect(keyField.props.defaultValue).toBe('API_KEY'); + // A non-secret edit keeps the plain Value placeholder. + expect(findField(renderer.root, 'Value').props.placeholder).toBe('Value'); + + changeText(renderer.root, 'Value', '2'); + pressButton(renderer.root, 'Save'); + + await waitFor(() => h.mutations.setVar.mutateAsync.mock.calls.length > 0); + expect(h.mutations.setVar.mutateAsync.mock.calls[0]?.[0]).toEqual({ + profileId: 'profile-1', + key: 'API_KEY', + value: '2', + isSecret: false, + }); + + unmount(); + }); + + it('happy: a secret row masks its value and never echoes the stored value', async () => { + h.query.data = testProfile([{ key: 'TOKEN', value: '***', isSecret: true }]); + h.query.isPending = false; + + const { renderer, unmount } = await mountScreen(); + + expect(findAll(renderer.root, 'Text').some(node => node.props.children === '••••••••')).toBe( + true + ); + expect(findAll(renderer.root, 'Text').some(node => node.props.children === '***')).toBe(false); + + unmount(); + }); + + it('happy: a secret value can be revealed only while editing', async () => { + h.query.data = testProfile([{ key: 'TOKEN', value: '***', isSecret: true }]); + h.query.isPending = false; + + const { renderer, unmount } = await mountScreen(); + + // The row offers no reveal control. + expect( + findAll(renderer.root, 'Pressable').some( + node => node.props.accessibilityLabel === 'Reveal value' + ) + ).toBe(false); + + pressPressable(renderer.root, 'TOKEN'); + const valueField = findField(renderer.root, 'Value'); + expect(valueField.props.secureTextEntry).toBe(true); + // A stored secret is never seeded into the edit field. + expect(valueField.props.defaultValue).toBe(''); + // The empty field says what the user must do: type a new value to rotate. + expect(valueField.props.placeholder).toBe('Enter new secret value'); + + pressPressable(renderer.root, 'Reveal value'); + expect(findField(renderer.root, 'Value').props.secureTextEntry).toBe(false); + + unmount(); + }); + + it('happy: a new variable marked secret says its value is encrypted', async () => { + h.query.data = testProfile(); + h.query.isPending = false; + + const { renderer, unmount } = await mountScreen(); + pressEmptyAction(renderer); + // Until it is marked secret, the value field keeps the plain placeholder. + expect(findField(renderer.root, 'Value').props.placeholder).toBe('Value'); + + act(() => { + ( + findOne(renderer.root, 'Switch').props as { onValueChange: (value: boolean) => void } + ).onValueChange(true); + }); + expect(findField(renderer.root, 'Value').props.placeholder).toBe('Secret value (encrypted)'); + + unmount(); + }); + + it('happy: confirming delete calls deleteVar and drops the row', async () => { + h.query.data = testProfile([{ key: 'API_KEY', value: '1', isSecret: false }]); + h.query.isPending = false; + h.mutations.deleteVar.mutateAsync.mockResolvedValue({ success: true }); + + const { renderer, unmount } = await mountScreen(); + pressPressable(renderer.root, 'Delete'); + await act(async () => { + confirmAlert(h.alert as TestAlertMock); + await Promise.resolve(); + }); + + await waitFor(() => h.mutations.deleteVar.mutateAsync.mock.calls.length > 0); + expect(h.mutations.deleteVar.mutateAsync.mock.calls[0]?.[0]).toEqual({ + profileId: 'profile-1', + key: 'API_KEY', + }); + await waitFor(() => findAll(renderer.root, 'Pressable').length === 0); + + unmount(); + }); + + it('disables delete and refuses another confirmation while deletion is pending', async () => { + h.query.data = testProfile([{ key: 'API_KEY', value: '1', isSecret: false }]); + h.query.isPending = false; + const { renderer, queryClient, unmount } = await mountScreen(); + h.mutations.deleteVar.isPending = true; + act(() => { + rerenderScreen(renderer, queryClient); + }); + expect(findPressable(renderer.root, 'Delete').props.disabled).toBe(true); + pressPressable(renderer.root, 'Delete'); + expect(h.alert).not.toHaveBeenCalled(); + h.mutations.deleteVar.isPending = false; + act(() => { + rerenderScreen(renderer, queryClient); + }); + expect(findPressable(renderer.root, 'Delete').props.disabled).toBe(false); + pressPressable(renderer.root, 'Delete'); + expect(h.alert).toHaveBeenCalledTimes(1); + unmount(); + }); + + it('keeps the rows while a refetch is in flight', async () => { + h.query.data = testProfile([{ key: 'API_KEY', value: '1', isSecret: false }]); + h.query.isPending = false; + + const { renderer, queryClient, unmount } = await mountScreen(); + + h.query.isRefetching = true; + await act(async () => { + rerenderScreen(renderer, queryClient); + await Promise.resolve(); + }); + + expect(findPressable(renderer.root, 'API_KEY')).toBeTruthy(); + + unmount(); + }); +}); diff --git a/apps/mobile/src/components/profiles/profile-variables-screen.test-helpers.ts b/apps/mobile/src/components/profiles/profile-variables-screen.test-helpers.ts new file mode 100644 index 00000000000..38157ff62d7 --- /dev/null +++ b/apps/mobile/src/components/profiles/profile-variables-screen.test-helpers.ts @@ -0,0 +1,120 @@ +import { createElement } from 'react'; +import { type QueryClient, QueryClientProvider } from '@tanstack/react-query'; + +import { ProfileVariablesScreen } from '@/components/profiles/profile-variables-screen'; +import { act, type ReactTestInstance, type ReactTestRenderer } from '@/test/renderer'; +import { renderWithProviders } from '@/test/render-with-providers'; + +/** + * Shared harness for the Profile Variables mounted tests. The `vi.mock` calls + * stay in the test file (they must be hoisted there); everything that does not + * depend on the hoisted mock state lives here. + */ + +type TestVar = { key: string; value: string; isSecret: boolean }; + +/** The profile detail the mocked `useAgentProfile` returns. */ +export type TestProfileDetail = { id: string; vars: TestVar[] }; + +export function testProfile(vars: TestVar[] = []): TestProfileDetail { + return { id: 'profile-1', vars }; +} + +export function findAll(root: ReactTestInstance, type: string): ReactTestInstance[] { + return root.findAll(node => typeof node.type === 'string' && node.type === type); +} + +export function findOne(root: ReactTestInstance, type: string): ReactTestInstance { + const node = findAll(root, type)[0]; + if (!node) { + throw new Error(`${type} was not rendered`); + } + return node; +} + +export function findField(root: ReactTestInstance, label: string): ReactTestInstance { + const field = findAll(root, 'FormField').find(node => node.props.label === label); + if (!field) { + throw new Error(`field ${label} was not rendered`); + } + return field; +} + +export function findPressable(root: ReactTestInstance, label: string): ReactTestInstance { + const pressable = findAll(root, 'Pressable').find( + node => node.props.accessibilityLabel === label + ); + if (!pressable) { + throw new Error(`pressable ${label} was not rendered`); + } + return pressable; +} + +export function pressPressable(root: ReactTestInstance, label: string): void { + const pressable = findPressable(root, label); + act(() => { + (pressable.props as { onPress: () => void }).onPress(); + }); +} + +function findButton(root: ReactTestInstance, label: string): ReactTestInstance { + const button = findAll(root, 'Button').find( + node => node.findAll(child => child.props.children === label).length > 0 + ); + if (!button) { + throw new Error(`button ${label} was not rendered`); + } + return button; +} + +export function pressButton(root: ReactTestInstance, label: string): void { + const button = findButton(root, label); + act(() => { + (button.props as { onPress: () => void }).onPress(); + }); +} + +function changeField(field: ReactTestInstance, value: string): void { + act(() => { + (field.props as { onChangeText: (v: string) => void }).onChangeText(value); + }); +} + +export function changeText(root: ReactTestInstance, label: string, value: string): void { + changeField(findField(root, label), value); +} + +type TestAlertButton = { style: string; onPress?: () => void }; + +/** The mock surface `Alert.alert` exposes to the harness. */ +export type TestAlertMock = { mock: { calls: unknown[][] } }; + +export function confirmAlert(alert: TestAlertMock): void { + const buttons = alert.mock.calls[0]?.[2] as TestAlertButton[] | undefined; + const destructive = buttons?.find(button => button.style === 'destructive'); + if (!destructive?.onPress) { + throw new Error('destructive alert button was not registered'); + } + destructive.onPress(); +} + +// eslint-disable-next-line typescript-eslint/promise-function-async -- returning the harness promise unchanged +export function mountScreen(organizationId?: string) { + return renderWithProviders( + createElement(ProfileVariablesScreen, { profileId: 'profile-1', organizationId }) + ); +} + +export function rerenderScreen( + renderer: ReactTestRenderer, + queryClient: QueryClient, + organizationId?: string +): void { + renderer.update( + createElement( + QueryClientProvider, + { client: queryClient }, + createElement(ProfileVariablesScreen, { profileId: 'profile-1', organizationId }) + ) + ); +} diff --git a/apps/mobile/src/components/profiles/profile-variables-screen.tsx b/apps/mobile/src/components/profiles/profile-variables-screen.tsx new file mode 100644 index 00000000000..db9b4148865 --- /dev/null +++ b/apps/mobile/src/components/profiles/profile-variables-screen.tsx @@ -0,0 +1,221 @@ +import { type ReactNode, useState } from 'react'; +import { useTranslation } from 'react-i18next'; +import { Alert, ScrollView, View } from 'react-native'; +import { toast } from 'sonner-native'; + +import { EmptyState } from '@/components/empty-state'; +import { + applyVariableEdit, + type ProfileVarSource, + type VariableEdit, + type VariableRow, + variableRows, +} from '@/components/profiles/profile-variables-model'; +import { + VariableEditForm, + VariableRowView, + VariablesSkeleton, +} from '@/components/profiles/profile-variables-rows'; +import { QueryError } from '@/components/query-error'; +import { ScreenHeader } from '@/components/screen-header'; +import { Button } from '@/components/ui/button'; +import { KeyRound } from '@/components/ui/icons'; +import { Text } from '@/components/ui/text'; +import { useAgentProfile, useAgentProfileMutations } from '@/lib/hooks/use-agent-profiles'; + +/** Stable empty fallback so the derived list is not a new array each render. */ +const NO_VARS: readonly ProfileVarSource[] = []; + +/** + * The mutation hook toasts `error.message`; the screen only supplies a + * fallback when the server sent nothing readable. + */ +function hasUsableMessage(error: unknown): boolean { + return error instanceof Error && error.message.trim().length > 0; +} + +/** + * The edit the inline form seeds from. A secret's value is never seeded: the + * server returns a masked `***`, and saving that back would overwrite the + * secret, so the user must enter a new value. + */ +function initialVariableEdit(row: VariableRow | undefined): VariableEdit { + if (row === undefined) { + return { key: '', value: '', isSecret: false }; + } + if (row.isSecret) { + return { key: row.key, value: '', isSecret: true }; + } + return { key: row.key, value: row.value, isSecret: false }; +} + +export function ProfileVariablesScreen({ + profileId, + organizationId, +}: Readonly<{ profileId: string; organizationId?: string }>) { + const { t } = useTranslation(); + const profileQuery = useAgentProfile(profileId, organizationId); + const { setVar, deleteVar } = useAgentProfileMutations(organizationId); + + const profile = profileQuery.data; + const serverVars: readonly ProfileVarSource[] = profile?.vars ?? NO_VARS; + // The screen owns the list so a saved edit shows immediately; the sync below + // swaps in the server's copy whenever a refetch lands, so the list never + // blanks and never drifts from server truth. + const [vars, setVars] = useState(serverVars); + const [syncedVars, setSyncedVars] = useState(serverVars); + if (serverVars !== syncedVars) { + setSyncedVars(serverVars); + setVars(serverVars); + } + + // `null` = closed, `''` = the add form, any other value = the key being edited. + const [editingKey, setEditingKey] = useState(null); + + const rows = variableRows(vars); + const isAdding = editingKey === ''; + + const startAdd = () => { + setEditingKey(''); + }; + + const closeForm = () => { + setEditingKey(null); + }; + + const saveEdit = async (edit: VariableEdit): Promise => { + try { + await setVar.mutateAsync({ + profileId, + key: edit.key, + value: edit.value, + isSecret: edit.isSecret, + }); + } catch (error) { + if (!hasUsableMessage(error)) { + toast.error(t('profiles.variablesSaveFailed')); + } + return false; + } + setVars(current => applyVariableEdit(current, edit)); + setEditingKey(null); + return true; + }; + + const runDelete = async (key: string) => { + if (deleteVar.isPending) { + return; + } + try { + await deleteVar.mutateAsync({ profileId, key }); + } catch (error) { + if (!hasUsableMessage(error)) { + toast.error(t('profiles.variablesSaveFailed')); + } + return; + } + setVars(current => current.filter(v => v.key !== key)); + }; + + const confirmDelete = (key: string) => { + if (deleteVar.isPending) { + return; + } + Alert.alert(t('common.delete'), key, [ + { text: t('common.cancel'), style: 'cancel' }, + { + text: t('common.delete'), + style: 'destructive', + onPress: () => { + void runDelete(key); + }, + }, + ]); + }; + + const renderEditForm = (isNew: boolean, initial: VariableEdit) => ( + variable.key)} + isSaving={setVar.isPending} + onCancel={closeForm} + onSave={saveEdit} + /> + ); + + let content: ReactNode = null; + if (profileQuery.isError) { + content = ( + void profileQuery.refetch()} + isRetrying={profileQuery.isRefetching} + /> + ); + } else if (profileQuery.isPending) { + content = ; + } else if (rows.length === 0 && !isAdding) { + content = ( + + {t('profiles.addVariable')} + + } + /> + ); + } else { + content = ( + <> + {rows.map(row => + row.key === editingKey ? ( + renderEditForm(false, initialVariableEdit(row)) + ) : ( + { + setEditingKey(row.key); + }} + onDelete={() => { + confirmDelete(row.key); + }} + /> + ) + )} + + {isAdding ? renderEditForm(true, initialVariableEdit(undefined)) : null} + + {editingKey === null ? ( + + ) : null} + + ); + } + + return ( + + + + {content} + + + ); +} diff --git a/apps/mobile/src/components/profiles/profiles-list-screen.mounted.test.tsx b/apps/mobile/src/components/profiles/profiles-list-screen.mounted.test.tsx new file mode 100644 index 00000000000..ee3eccdd008 --- /dev/null +++ b/apps/mobile/src/components/profiles/profiles-list-screen.mounted.test.tsx @@ -0,0 +1,282 @@ +import { createElement } from 'react'; +import { act, type ReactTestInstance } from '@/test/renderer'; +import { beforeEach, describe, expect, it, vi } from 'vitest'; + +import '@/i18n'; +import { ProfilesListScreen } from '@/components/profiles/profiles-list-screen'; +import { renderWithProviders } from '@/test/render-with-providers'; + +// ── Hoisted mocks ────────────────────────────────────────────────────────── + +type ListResult = { + orgProfiles: ProfileSummary[]; + personalProfiles: ProfileSummary[]; + effectiveDefaultId: string | null; + isLoading: boolean; + isError: boolean; + isRefetching: boolean; + refetch: () => void; +}; + +type ProfileSummary = { + id: string; + name: string; + isDefault: boolean; + varCount: number; + commandCount: number; + mcpServerCount: number; + skillCount: number; + ownerType?: 'organization' | 'user'; +}; + +const refetchFn = vi.hoisted(() => vi.fn()); +const routerPush = vi.hoisted(() => vi.fn()); +const organizationState = vi.hoisted(() => ({ organizationId: 'org-1' as string | null })); +const listState = vi.hoisted(() => ({ + orgProfiles: [], + personalProfiles: [], + effectiveDefaultId: null, + isLoading: true, + isError: false, + isRefetching: false, + refetch: () => { + refetchFn(); + }, +})); + +vi.mock('@/lib/hooks/use-agent-profiles', () => ({ + useAgentProfileList: () => listState, +})); + +vi.mock('@/lib/organization-context', () => ({ + useOrganization: () => ({ + organizationId: organizationState.organizationId, + isLoaded: true, + }), +})); + +vi.mock('expo-router', () => ({ + useRouter: () => ({ push: routerPush }), +})); + +vi.mock('@/lib/profile-agent-navigation', () => ({ + getProfileOverviewPath: (profileId: string, organizationId?: string) => + organizationId ? `/profiles/${profileId}?org=${organizationId}` : `/profiles/${profileId}`, +})); + +vi.mock('react-native', () => ({ + View: 'View', +})); + +vi.mock('@/lib/hooks/use-theme-colors', () => ({ + useThemeColors: () => ({ + primary: '#4F5A10', + primaryForeground: '#FFFFFF', + mutedForeground: '#6F6A61', + }), +})); + +vi.mock('@/components/screen-header', () => ({ ScreenHeader: () => null })); +vi.mock('@/components/tab-screen', () => ({ TabScreenScrollView: 'ScrollView' })); +vi.mock('@/components/empty-state', () => ({ EmptyState: 'EmptyState' })); +vi.mock('@/components/query-error', () => ({ QueryError: 'QueryError' })); +vi.mock('@/components/ui/button', () => ({ Button: 'Button' })); +vi.mock('@/components/ui/configure-row', () => ({ ConfigureRow: 'ConfigureRow' })); +vi.mock('@/components/ui/skeleton', () => ({ Skeleton: 'Skeleton' })); +vi.mock('@/components/ui/text', () => ({ Text: 'Text' })); +vi.mock('@/components/ui/refresh-control', () => ({ RefreshControl: 'RefreshControl' })); +vi.mock('@/components/ui/icons', () => ({ + Building2: 'Building2', + Plus: 'Plus', + SlidersHorizontal: 'SlidersHorizontal', + Star: 'Star', + User: 'User', +})); + +// ── Helpers ──────────────────────────────────────────────────────────────── + +function profile(overrides: Partial & { id: string }): ProfileSummary { + return { + name: 'Backend debugging', + isDefault: false, + varCount: 0, + commandCount: 0, + mcpServerCount: 0, + skillCount: 0, + ...overrides, + }; +} + +function findAll(root: ReactTestInstance, type: string): ReactTestInstance[] { + return root.findAll(node => typeof node.type === 'string' && node.type === type); +} + +function findOne(root: ReactTestInstance, type: string): ReactTestInstance { + const node = findAll(root, type)[0]; + if (!node) { + throw new Error(`${type} was not rendered`); + } + return node; +} + +function rowTitles(root: ReactTestInstance): string[] { + return findAll(root, 'ConfigureRow').map(row => String(row.props.title)); +} + +function textChildren(root: ReactTestInstance): string[] { + return findAll(root, 'Text').map(node => String(node.props.children)); +} + +async function mount() { + const result = await renderWithProviders(createElement(ProfilesListScreen)); + return result; +} + +// ── Tests ────────────────────────────────────────────────────────────────── + +describe('ProfilesListScreen', () => { + beforeEach(() => { + refetchFn.mockReset(); + routerPush.mockReset(); + organizationState.organizationId = 'org-1'; + Object.assign(listState, { + orgProfiles: [], + personalProfiles: [], + effectiveDefaultId: null, + isLoading: false, + isError: false, + isRefetching: false, + }); + }); + + it('loading: renders content-shaped skeleton rows and no profile rows', async () => { + listState.isLoading = true; + + const { renderer, unmount } = await mount(); + + // 3 placeholder rows x (icon tile + title bar + subtitle bar). + expect(findAll(renderer.root, 'Skeleton')).toHaveLength(9); + expect(findAll(renderer.root, 'ConfigureRow')).toHaveLength(0); + + unmount(); + }); + + it('error: renders QueryError and Retry refetches', async () => { + listState.isError = true; + + const { renderer, unmount } = await mount(); + + const queryError = findOne(renderer.root, 'QueryError'); + expect(queryError.props.title).toBe("Couldn't load profiles"); + act(() => { + (queryError.props as { onRetry: () => void }).onRetry(); + }); + expect(refetchFn).toHaveBeenCalledTimes(1); + + unmount(); + }); + + it('empty: renders EmptyState with the create CTA', async () => { + organizationState.organizationId = null; + + const { renderer, unmount } = await mount(); + + const emptyState = findOne(renderer.root, 'EmptyState'); + expect(emptyState.props.title).toBe('No profiles yet'); + + const action = emptyState.props.action as { props: { onPress: () => void } }; + act(() => { + action.props.onPress(); + }); + expect(routerPush).toHaveBeenCalledWith('/(app)/(tabs)/(3_profile)/profiles/new'); + + unmount(); + }); + + it('happy: renders org then personal rows, marks the effective default, and opens a profile', async () => { + listState.orgProfiles = [ + profile({ id: 'org-1-profile', name: 'Org profile', varCount: 1, ownerType: 'organization' }), + ]; + listState.personalProfiles = [ + profile({ + id: 'personal-1', + name: 'Personal profile', + commandCount: 9, + mcpServerCount: 2, + isDefault: true, + }), + ]; + listState.effectiveDefaultId = 'personal-1'; + + const { renderer, unmount } = await mount(); + + expect(textChildren(renderer.root)).toContain('Organization'); + expect(textChildren(renderer.root)).toContain('Personal'); + expect(rowTitles(renderer.root)).toEqual(['Org profile', 'Personal profile']); + + const personalRow = findAll(renderer.root, 'ConfigureRow').find( + row => row.props.title === 'Personal profile' + ); + if (!personalRow) { + throw new Error('personal row missing'); + } + expect(personalRow.props.subtitle).toBe('2 MCP'); + const star = personalRow.props.trailing as { + type: string; + props: { accessibilityLabel: string }; + }; + expect(star.type).toBe('Star'); + expect(star.props.accessibilityLabel).toBe('Personal profile, Default profile'); + + const orgRow = findAll(renderer.root, 'ConfigureRow').find( + row => row.props.title === 'Org profile' + ); + if (!orgRow) { + throw new Error('org row missing'); + } + expect(orgRow.props.subtitle).toBe('1v'); + // The row's leading icon names its owner. + expect(orgRow.props.icon).toBe('Building2'); + expect(personalRow.props.icon).toBe('User'); + // Only the resolved effective default gets the filled star. + expect(orgRow.props.trailing).toBeUndefined(); + act(() => { + (orgRow.props as { onPress: () => void }).onPress(); + }); + // An org-owned profile carries its organization id into the editor. + expect(routerPush).toHaveBeenCalledWith('/profiles/org-1-profile?org=org-1'); + + act(() => { + (personalRow.props as { onPress: () => void }).onPress(); + }); + // A personal profile keeps no owner context, even from an org list. + expect(routerPush).toHaveBeenCalledWith('/profiles/personal-1'); + + // The persistent create CTA is present once profiles exist. + const buttons = findAll(renderer.root, 'Button'); + const createButton = buttons.at(-1); + if (!createButton) { + throw new Error('create button was not rendered'); + } + act(() => { + (createButton.props as { onPress: () => void }).onPress(); + }); + expect(routerPush).toHaveBeenCalledWith('/(app)/(tabs)/(3_profile)/profiles/new'); + + unmount(); + }); + + it('personal context: renders one untitled section', async () => { + organizationState.organizationId = null; + listState.personalProfiles = [profile({ id: 'personal-1', name: 'Personal profile' })]; + + const { renderer, unmount } = await mount(); + + expect(rowTitles(renderer.root)).toEqual(['Personal profile']); + expect(textChildren(renderer.root)).not.toContain('Organization'); + // `agentProfiles.list` does not tag owner, so every row is personal. + expect(findOne(renderer.root, 'ConfigureRow').props.icon).toBe('User'); + + unmount(); + }); +}); diff --git a/apps/mobile/src/components/profiles/profiles-list-screen.tsx b/apps/mobile/src/components/profiles/profiles-list-screen.tsx new file mode 100644 index 00000000000..544524feae8 --- /dev/null +++ b/apps/mobile/src/components/profiles/profiles-list-screen.tsx @@ -0,0 +1,212 @@ +import { type Href, useRouter } from 'expo-router'; +import { useTranslation } from 'react-i18next'; +import { View } from 'react-native'; + +import { EmptyState } from '@/components/empty-state'; +import { + buildProfileSections, + isEffectiveDefault, + profileCounts, + profileListCountItems, +} from '@/components/profiles/profile-list-model'; +import { profileOrganizationId, profileOwnerType } from '@/components/profiles/profile-owner-model'; +import { QueryError } from '@/components/query-error'; +import { ScreenHeader } from '@/components/screen-header'; +import { TabScreenScrollView } from '@/components/tab-screen'; +import { Button } from '@/components/ui/button'; +import { ConfigureRow } from '@/components/ui/configure-row'; +import { + Building2, + type LucideIcon, + Plus, + SlidersHorizontal, + Star, + User, +} from '@/components/ui/icons'; +import { RefreshControl } from '@/components/ui/refresh-control'; +import { Skeleton } from '@/components/ui/skeleton'; +import { Text } from '@/components/ui/text'; +import { useThemeColors } from '@/lib/hooks/use-theme-colors'; +import { formatProfileCountItemsShort } from '@/lib/profile-count-labels'; +import { type AgentProfileListItem, useAgentProfileList } from '@/lib/hooks/use-agent-profiles'; +import { useOrganization } from '@/lib/organization-context'; +import { getProfileOverviewPath } from '@/lib/profile-agent-navigation'; + +const NEW_PROFILE_PATH = '/(app)/(tabs)/(3_profile)/profiles/new' as Href; + +/** + * The row's leading icon names its owner, the same split the web list row + * makes (`ProfilesListDialog.tsx:372-376`): a building for an organization + * profile, a person for a personal one. + */ +function ownerIcon(profile: AgentProfileListItem): LucideIcon { + return profileOwnerType(profile) === 'organization' ? Building2 : User; +} + +/** Profile name plus the default marker, for the star's accessibility label. */ +function defaultStarLabel(name: string, defaultLabel: string): string { + return [name, defaultLabel].join(', '); +} + +/** + * The effective-default marker. Returns `undefined` when the profile is not + * the default, so `ConfigureRow` falls back to its chevron. + */ +function renderDefaultStar({ + isDefault, + name, + label, + color, +}: Readonly<{ isDefault: boolean; name: string; label: string; color: string }>) { + if (!isDefault) { + return undefined; + } + return ( + + ); +} + +/** + * Content-shaped loading rows. Each mirrors a loaded `ConfigureRow` (py-3 + + * the 30pt icon tile beside the two-line text block) so the swap to real rows + * does not move the screen. + */ +function ProfileListSkeleton() { + return ( + + {[0, 1, 2].map(index => ( + + + + + + + + ))} + + ); +} + +export function ProfilesListScreen() { + const router = useRouter(); + const { t } = useTranslation(); + const colors = useThemeColors(); + const { organizationId, isLoaded } = useOrganization(); + const isOrgContext = organizationId != null; + const { + orgProfiles, + personalProfiles, + effectiveDefaultId, + isLoading, + isError, + isRefetching, + refetch, + } = useAgentProfileList(organizationId ?? undefined); + + const sections = buildProfileSections({ orgProfiles, personalProfiles, isOrgContext }); + // Wait for the stored organization before choosing the list context, so a + // personal list never flashes before the organization list. + const isListLoading = !isLoaded || isLoading; + + const openNewProfile = () => { + router.push(NEW_PROFILE_PATH); + }; + + const openProfile = (profile: AgentProfileListItem) => { + router.push(getProfileOverviewPath(profile.id, profileOrganizationId(organizationId, profile))); + }; + + return ( + + + void refetch()} + colors={[colors.mutedForeground]} + tintColor={colors.mutedForeground} + /> + } + > + {isError && ( + void refetch()} + isRetrying={isRefetching} + /> + )} + {!isError && isListLoading && } + {!isError && !isListLoading && sections.length === 0 && ( + + {t('profiles.newProfile')} + + } + /> + )} + {!isError && !isListLoading && sections.length > 0 && ( + <> + {sections.map(section => ( + + {section.titleKey ? ( + + {t(section.titleKey, { + // Reviewed English heading until the `profiles.list.*` + // copy lands with the translation slice. + defaultValue: t( + section.key === 'organization' ? 'common.organization' : 'common.personal' + ), + })} + + ) : null} + {section.profiles.map((profile, index) => { + const subtitle = formatProfileCountItemsShort( + t, + profileListCountItems(profileCounts(profile)) + ).join(' · '); + return ( + { + openProfile(profile); + }} + /> + ); + })} + + ))} + + {/* Persistent create entry: the list keeps a create CTA at the end + once profiles exist, so the flow never dead-ends. */} + + + )} + + + ); +} diff --git a/apps/mobile/src/components/profiles/repo-bindings-model.test.ts b/apps/mobile/src/components/profiles/repo-bindings-model.test.ts new file mode 100644 index 00000000000..e4d61e25df8 --- /dev/null +++ b/apps/mobile/src/components/profiles/repo-bindings-model.test.ts @@ -0,0 +1,104 @@ +import { describe, expect, it } from 'vitest'; + +import { + bindingsForProfile, + filterRepositoryOptions, + findRepoBinding, + mergeRepositoryOptions, + repoPlatformBadge, + repositoryOptionKey, +} from '@/components/profiles/repo-bindings-model'; + +const GITHUB = [ + { fullName: 'acme/api', private: true }, + { fullName: 'acme/web', private: false }, +]; +const GITLAB = [{ fullName: 'acme/infra', private: false }]; + +describe('mergeRepositoryOptions', () => { + it('lists GitHub rows first, then GitLab, tagged with their platform', () => { + expect(mergeRepositoryOptions(GITHUB, GITLAB)).toEqual([ + { platform: 'github', fullName: 'acme/api', private: true }, + { platform: 'github', fullName: 'acme/web', private: false }, + { platform: 'gitlab', fullName: 'acme/infra', private: false }, + ]); + }); + + it('returns an empty list when neither provider has rows', () => { + expect(mergeRepositoryOptions([], [])).toEqual([]); + }); + + it('keeps the same full name on both platforms as two distinct options', () => { + const merged = mergeRepositoryOptions( + [{ fullName: 'acme/api', private: false }], + [{ fullName: 'acme/api', private: true }] + ); + expect(merged.map(repo => repositoryOptionKey(repo))).toEqual([ + 'github:acme/api', + 'gitlab:acme/api', + ]); + }); +}); + +describe('filterRepositoryOptions', () => { + const options = mergeRepositoryOptions(GITHUB, GITLAB); + + it('returns every option for a blank or whitespace query', () => { + expect(filterRepositoryOptions(options, '')).toHaveLength(3); + expect(filterRepositoryOptions(options, ' ')).toHaveLength(3); + }); + + it('matches a full name case-insensitively', () => { + expect(filterRepositoryOptions(options, 'API').map(repo => repo.fullName)).toEqual([ + 'acme/api', + ]); + expect(filterRepositoryOptions(options, 'acme/').map(repo => repo.fullName)).toEqual([ + 'acme/api', + 'acme/web', + 'acme/infra', + ]); + }); + + it('returns no rows when nothing matches', () => { + expect(filterRepositoryOptions(options, 'zzz')).toEqual([]); + }); +}); + +describe('findRepoBinding', () => { + const bindings = [ + { repoFullName: 'Acme/API', platform: 'github', profileId: 'p1', profileName: 'One' }, + { repoFullName: 'acme/api', platform: 'gitlab', profileId: 'p2', profileName: 'Two' }, + ]; + + it('matches platform and full name case-insensitively', () => { + expect(findRepoBinding(bindings, 'acme/api', 'github')?.profileId).toBe('p1'); + expect(findRepoBinding(bindings, 'ACME/API', 'gitlab')?.profileId).toBe('p2'); + }); + + it('does not match across platforms', () => { + expect(findRepoBinding(bindings, 'acme/api', 'gitlab')?.profileId).toBe('p2'); + expect(findRepoBinding(bindings, 'acme/web', 'github')).toBeUndefined(); + }); +}); + +describe('bindingsForProfile', () => { + it('keeps only the bindings owned by the profile', () => { + const bindings = [ + { repoFullName: 'a/one', platform: 'github', profileId: 'p1', profileName: 'One' }, + { repoFullName: 'a/two', platform: 'github', profileId: 'p2', profileName: 'Two' }, + { repoFullName: 'a/three', platform: 'gitlab', profileId: 'p1', profileName: 'One' }, + ]; + expect(bindingsForProfile(bindings, 'p1').map(binding => binding.repoFullName)).toEqual([ + 'a/one', + 'a/three', + ]); + }); +}); + +describe('repoPlatformBadge', () => { + it('renders GL for GitLab and GH for GitHub and anything unknown', () => { + expect(repoPlatformBadge('gitlab')).toBe('GL'); + expect(repoPlatformBadge('github')).toBe('GH'); + expect(repoPlatformBadge('bitbucket')).toBe('GH'); + }); +}); diff --git a/apps/mobile/src/components/profiles/repo-bindings-model.ts b/apps/mobile/src/components/profiles/repo-bindings-model.ts new file mode 100644 index 00000000000..0e1b045996c --- /dev/null +++ b/apps/mobile/src/components/profiles/repo-bindings-model.ts @@ -0,0 +1,98 @@ +/** + * Pure view-model helpers for the repo-profile bindings surface. + * + * Ports the web `RepoProfileBindingsDialog` option/merge logic + * (`apps/web/src/components/cloud-agent/RepoProfileBindingsDialog.tsx:122-147`) + * and the `RepoPinsSection` per-profile filter + * (`apps/web/src/components/cloud-agent/ProfilesListDialog.tsx:952-957`) to the + * phone. No React and no React Native imports: every function is unit-tested + * directly in `repo-bindings-model.test.ts`. + */ + +/** The two providers a binding can name; Bitbucket has no profile bindings. */ +export type RepoBindingPlatform = 'github' | 'gitlab'; + +/** One repository as a picker option, merged from the two provider lists. */ +export type RepoOption = Readonly<{ + platform: RepoBindingPlatform; + fullName: string; + private: boolean; +}>; + +/** The provider row shape the merge reads; only the fields the option needs. */ +export type RepoSource = Readonly<{ fullName: string; private: boolean }>; + +/** The binding row shape `listRepoBindings` returns, structurally. */ +export type RepoBindingLike = Readonly<{ + repoFullName: string; + platform: string; + profileId: string; + profileName: string; +}>; + +/** + * Merge the GitHub and GitLab repository lists into one option list, GitHub + * first — the web dialog's `unifiedRepositories` order. + */ +export function mergeRepositoryOptions( + github: readonly RepoSource[], + gitlab: readonly RepoSource[] +): RepoOption[] { + return [ + ...github.map(repo => ({ + platform: 'github' as const, + fullName: repo.fullName, + private: repo.private, + })), + ...gitlab.map(repo => ({ + platform: 'gitlab' as const, + fullName: repo.fullName, + private: repo.private, + })), + ]; +} + +/** The stable identity of a picker row: platform plus full name. */ +export function repositoryOptionKey(repo: RepoOption): string { + return `${repo.platform}:${repo.fullName}`; +} + +/** Case-insensitive full-name search; a blank query returns every option. */ +export function filterRepositoryOptions( + repositories: readonly RepoOption[], + query: string +): RepoOption[] { + const needle = query.trim().toLowerCase(); + if (needle === '') { + return [...repositories]; + } + return repositories.filter(repo => repo.fullName.toLowerCase().includes(needle)); +} + +/** + * Find the binding that already pins `repoFullName` on `platform`, matching the + * server's lower-cased full name. Returns `undefined` when the repo is free. + */ +export function findRepoBinding( + bindings: readonly T[], + repoFullName: string, + platform: RepoBindingPlatform +): T | undefined { + const target = repoFullName.toLowerCase(); + return bindings.find( + binding => binding.platform === platform && binding.repoFullName.toLowerCase() === target + ); +} + +/** The bindings that belong to one profile, for the Overview pins section. */ +export function bindingsForProfile( + bindings: readonly T[], + profileId: string +): T[] { + return bindings.filter(binding => binding.profileId === profileId); +} + +/** The two-letter provider badge the web dialog renders (`GH`/`GL`). */ +export function repoPlatformBadge(platform: string): 'GH' | 'GL' { + return platform === 'gitlab' ? 'GL' : 'GH'; +} diff --git a/apps/mobile/src/components/profiles/repo-bindings-profile-sheet.mounted.test.tsx b/apps/mobile/src/components/profiles/repo-bindings-profile-sheet.mounted.test.tsx new file mode 100644 index 00000000000..433ef3e2880 --- /dev/null +++ b/apps/mobile/src/components/profiles/repo-bindings-profile-sheet.mounted.test.tsx @@ -0,0 +1,81 @@ +import { createElement } from 'react'; +import { describe, expect, it, vi } from 'vitest'; + +import '@/i18n'; +import { ProfilePickerSheet } from './repo-bindings-profile-sheet'; +import { act } from '@/test/renderer'; +import { renderWithProviders } from '@/test/render-with-providers'; +import { type AgentProfileListItem } from '@/lib/hooks/agent-profile-types'; + +vi.mock('react-native', () => ({ View: 'View', ScrollView: 'ScrollView', Pressable: 'Pressable' })); +vi.mock('react-native-safe-area-context', () => ({ useSafeAreaInsets: () => ({ bottom: 0 }) })); +vi.mock('@/components/agents/session-page-sheet', () => ({ SessionPageSheet: 'SessionPageSheet' })); +vi.mock('@/components/sheet-header', () => ({ SheetHeader: 'SheetHeader' })); +vi.mock('@/components/query-error', () => ({ QueryError: 'QueryError' })); +vi.mock('@/components/empty-state', () => ({ EmptyState: 'EmptyState' })); +vi.mock('@/components/ui/icons', () => ({ SlidersHorizontal: 'SlidersHorizontal' })); +vi.mock('@/components/ui/skeleton', () => ({ Skeleton: 'Skeleton' })); +vi.mock('@/components/ui/text', () => ({ Text: 'Text' })); + +describe('binding profile picker', () => { + it.each(['loading', 'error', 'empty', 'happy'] as const)( + 'renders the %s state and its available actions', + async state => { + const onRetry = vi.fn<() => void>(); + const onSelect = vi.fn<(id: string) => void>(); + const onClose = vi.fn<() => void>(); + const profile: AgentProfileListItem = { + id: 'p1', + name: 'Profile', + isDefault: true, + description: null, + createdAt: '', + updatedAt: '', + varCount: 0, + commandCount: 0, + mcpServerCount: 0, + skillCount: 0, + agentCount: 0, + kiloCommandCount: 0, + }; + const { renderer, unmount } = await renderWithProviders( + createElement(ProfilePickerSheet, { + profiles: state === 'happy' ? [profile] : [], + isLoading: state === 'loading', + isError: state === 'error', + isRefetching: false, + selectedProfileId: '', + onRetry, + onSelect, + onClose, + }) + ); + const nodes = (type: string) => renderer.root.findAll(node => node.type === type); + const one = (type: string) => renderer.root.find(node => node.type === type); + expect(nodes('Skeleton')).toHaveLength(state === 'loading' ? 1 : 0); + expect(nodes('QueryError')).toHaveLength(state === 'error' ? 1 : 0); + expect(nodes('EmptyState')).toHaveLength(state === 'empty' ? 1 : 0); + expect(nodes('Pressable')).toHaveLength(state === 'happy' ? 1 : 0); + if (state === 'empty') { + expect(one('EmptyState').props.title).toBe('No profiles yet'); + } + if (state === 'error') { + act(() => { + (one('QueryError').props.onRetry as () => void)(); + }); + expect(onRetry).toHaveBeenCalledOnce(); + } + if (state === 'happy') { + act(() => { + (one('Pressable').props.onPress as () => void)(); + }); + expect(onSelect).toHaveBeenCalledWith('p1'); + } + act(() => { + (one('SheetHeader').props.onDone as () => void)(); + }); + expect(onClose).toHaveBeenCalledOnce(); + unmount(); + } + ); +}); diff --git a/apps/mobile/src/components/profiles/repo-bindings-profile-sheet.tsx b/apps/mobile/src/components/profiles/repo-bindings-profile-sheet.tsx new file mode 100644 index 00000000000..20e566dfe31 --- /dev/null +++ b/apps/mobile/src/components/profiles/repo-bindings-profile-sheet.tsx @@ -0,0 +1,110 @@ +import { useTranslation } from 'react-i18next'; +import { Pressable, ScrollView, View } from 'react-native'; +import { useSafeAreaInsets } from 'react-native-safe-area-context'; + +import { SessionPageSheet } from '@/components/agents/session-page-sheet'; +import { EmptyState } from '@/components/empty-state'; +import { SlidersHorizontal } from '@/components/ui/icons'; +import { QueryError } from '@/components/query-error'; +import { SheetHeader } from '@/components/sheet-header'; +import { Skeleton } from '@/components/ui/skeleton'; +import { Text } from '@/components/ui/text'; +import { type AgentProfileListItem } from '@/lib/hooks/use-agent-profiles'; + +type ProfilePickerSheetProps = Readonly<{ + profiles: readonly AgentProfileListItem[]; + isLoading: boolean; + isError: boolean; + isRefetching: boolean; + selectedProfileId: string; + onRetry: () => void; + onSelect: (profileId: string) => void; + onClose: () => void; +}>; + +/** + * The Add form's profile picker: one tap-to-select row per profile in the + * context (org profiles in an org context, personal profiles otherwise), + * matching the web dialog's `Select profile` list. Mounted only while open, and + * presented through `SessionPageSheet` so it is a real sheet from either call + * site rather than content laid into the caller's column. + */ +export function ProfilePickerSheet({ + profiles, + isLoading, + isError, + isRefetching, + selectedProfileId, + onRetry, + onSelect, + onClose, +}: Readonly) { + const { t } = useTranslation(); + const insets = useSafeAreaInsets(); + + let body = null; + if (isLoading) { + body = ; + } else if (isError) { + body = ( + + ); + } else if (profiles.length === 0) { + body = ( + + ); + } else { + body = profiles.map(profile => ( + { + onSelect(profile.id); + }} + > + + {profile.name} + + {profile.isDefault ? ( + {t('profiles.defaultSectionTitle')} + ) : null} + + )); + } + + return ( + + + + {body} + + + + ); +} diff --git a/apps/mobile/src/components/profiles/repo-bindings-screen.mounted.test.tsx b/apps/mobile/src/components/profiles/repo-bindings-screen.mounted.test.tsx new file mode 100644 index 00000000000..9b6fd86ea0a --- /dev/null +++ b/apps/mobile/src/components/profiles/repo-bindings-screen.mounted.test.tsx @@ -0,0 +1,310 @@ +import { beforeEach, describe, expect, it, vi } from 'vitest'; + +import '@/i18n'; +import { + changeSearch, + findAll, + findButton, + findOne, + mountScreen, + pressButton, + pressPressable, + rerenderScreen, + testBinding, + type TestBinding, +} from '@/components/profiles/repo-bindings-screen.test-helpers'; +import { act } from '@/test/renderer'; +import { waitFor } from '@/test/render-with-providers'; + +type TestRepoOption = { platform: 'github' | 'gitlab'; fullName: string; private: boolean }; +type TestProfile = { id: string; name: string; isDefault: boolean }; + +const h = vi.hoisted(() => ({ + success: vi.fn(), + error: vi.fn(), + bindingsQuery: { + bindings: [] as TestBinding[], + isLoading: true, + isError: false, + isRefetching: false, + refetch: vi.fn(), + }, + mutations: { + bind: { mutateAsync: vi.fn(), isPending: false }, + unbind: { mutateAsync: vi.fn(), isPending: false }, + }, + repoOptions: { + repositories: [] as TestRepoOption[], + isLoading: false, + isError: false, + isRefetching: false, + refetch: vi.fn(), + }, + profileList: { + orgProfiles: [] as TestProfile[], + personalProfiles: [] as TestProfile[], + effectiveDefaultId: null as string | null, + isLoading: false, + isError: false, + isRefetching: false, + refetch: vi.fn(), + }, +})); + +vi.mock('@/lib/hooks/use-repo-bindings', () => ({ + useRepoBindings: () => h.bindingsQuery, + useRepoBindingMutations: () => h.mutations, + useRepoOptions: () => h.repoOptions, +})); +vi.mock('@/lib/hooks/use-agent-profiles', () => ({ + useAgentProfileList: () => h.profileList, +})); +vi.mock('sonner-native', () => ({ toast: { success: h.success, error: h.error } })); +vi.mock('react-native', () => ({ + View: 'View', + ScrollView: 'ScrollView', + Pressable: 'Pressable', + TextInput: 'TextInput', + I18nManager: { isRTL: false }, +})); +vi.mock('@/lib/hooks/use-theme-colors', () => ({ + useThemeColors: () => ({ + mutedForeground: '#000000', + destructive: '#FF0000', + primary: '#0000FF', + }), +})); +vi.mock('react-native-safe-area-context', () => ({ + useSafeAreaInsets: () => ({ top: 0, bottom: 0, left: 0, right: 0 }), +})); +vi.mock('@/components/agents/session-page-sheet', () => ({ + SessionPageSheet: 'SessionPageSheet', +})); +vi.mock('@/components/sheet-header', () => ({ SheetHeader: 'SheetHeader' })); +vi.mock('@/components/screen-header', () => ({ ScreenHeader: 'ScreenHeader' })); +vi.mock('@/components/query-error', () => ({ QueryError: 'QueryError' })); +vi.mock('@/components/empty-state', () => ({ EmptyState: 'EmptyState' })); +vi.mock('@/components/ui/button', () => ({ Button: 'Button' })); +vi.mock('@/components/ui/skeleton', () => ({ Skeleton: 'Skeleton' })); +vi.mock('@/components/ui/text', () => ({ Text: 'Text' })); +vi.mock('@/components/ui/icons', () => ({ + Check: 'Check', + ChevronDown: 'ChevronDown', + GitBranch: 'GitBranch', + Lock: 'Lock', + Search: 'Search', + SearchX: 'SearchX', + Trash2: 'Trash2', + Unlock: 'Unlock', +})); + +const GITHUB_REPO: TestRepoOption = { platform: 'github', fullName: 'acme/api', private: true }; +const GITLAB_REPO: TestRepoOption = { platform: 'gitlab', fullName: 'acme/infra', private: false }; +const PROFILE: TestProfile = { id: 'profile-1', name: 'Backend debugging', isDefault: false }; + +describe('RepoBindingsScreen', () => { + beforeEach(() => { + vi.clearAllMocks(); + Object.assign(h.bindingsQuery, { + bindings: [], + isLoading: true, + isError: false, + isRefetching: false, + }); + Object.assign(h.mutations.bind, { isPending: false }); + Object.assign(h.mutations.unbind, { isPending: false }); + Object.assign(h.repoOptions, { + repositories: [GITHUB_REPO, GITLAB_REPO], + isLoading: false, + isError: false, + isRefetching: false, + }); + Object.assign(h.profileList, { + orgProfiles: [], + personalProfiles: [PROFILE], + effectiveDefaultId: null, + isLoading: false, + isError: false, + isRefetching: false, + }); + }); + + it('loading: renders skeletons and no binding rows', async () => { + const { renderer, unmount } = await mountScreen(); + + expect(findAll(renderer.root, 'Skeleton').length).toBeGreaterThan(0); + expect( + findAll(renderer.root, 'Pressable').some(n => n.props.accessibilityLabel === 'Unbind') + ).toBe(false); + + unmount(); + }); + + it('retryable: a bindings query failure shows QueryError and Retry refetches', async () => { + Object.assign(h.bindingsQuery, { isError: true, isLoading: false }); + + const { renderer, unmount } = await mountScreen(); + + const queryError = findOne(renderer.root, 'QueryError'); + expect(queryError.props.title).toBe("Couldn't load profiles"); + act(() => { + (queryError.props as { onRetry: () => void }).onRetry(); + }); + expect(h.bindingsQuery.refetch).toHaveBeenCalledTimes(1); + + unmount(); + }); + + it('empty: shows No defaults configured with the explanation', async () => { + Object.assign(h.bindingsQuery, { isLoading: false }); + + const { renderer, unmount } = await mountScreen(); + + const empty = findOne(renderer.root, 'EmptyState'); + expect(empty.props.title).toBe('No defaults configured'); + expect(empty.props.description).toContain('automatically apply a profile'); + + unmount(); + }); + + it('happy: lists a binding with its repo, badge and profile, and unbinds it', async () => { + Object.assign(h.bindingsQuery, { bindings: [testBinding()], isLoading: false }); + h.mutations.unbind.mutateAsync.mockResolvedValue({ success: true }); + + const { renderer, unmount } = await mountScreen(); + + const texts = findAll(renderer.root, 'Text').map(node => node.props.children); + expect(texts).toContain('acme/api'); + expect(texts).toContain('Backend debugging'); + expect(texts).toContain('GH'); + + pressPressable(renderer.root, 'Unbind'); + await waitFor(() => h.mutations.unbind.mutateAsync.mock.calls.length > 0); + expect(h.mutations.unbind.mutateAsync.mock.calls[0]?.[0]).toEqual({ + repoFullName: 'acme/api', + platform: 'github', + }); + + unmount(); + }); + + it('non-retryable: Add stays disabled until a repo and a profile are chosen', async () => { + Object.assign(h.bindingsQuery, { isLoading: false }); + + const { renderer, unmount } = await mountScreen(); + pressButton(renderer.root, 'Add default'); + + expect(findButton(renderer.root, 'Add default').props.disabled).toBe(true); + + pressPressable(renderer.root, 'Select repository…'); + pressPressable(renderer.root, 'acme/api'); + expect(findButton(renderer.root, 'Add default').props.disabled).toBe(true); + + pressPressable(renderer.root, 'Select profile'); + pressPressable(renderer.root, 'Backend debugging'); + expect(findButton(renderer.root, 'Add default').props.disabled).toBe(false); + + unmount(); + }); + + it('presents both Add pickers through the sheet surface', async () => { + Object.assign(h.bindingsQuery, { isLoading: false }); + + const { renderer, unmount } = await mountScreen(); + pressButton(renderer.root, 'Add default'); + expect(findAll(renderer.root, 'SessionPageSheet')).toHaveLength(0); + + pressPressable(renderer.root, 'Select repository…'); + expect(findAll(renderer.root, 'SessionPageSheet')).toHaveLength(1); + + pressPressable(renderer.root, 'acme/api'); + expect(findAll(renderer.root, 'SessionPageSheet')).toHaveLength(0); + + pressPressable(renderer.root, 'Select profile'); + expect(findAll(renderer.root, 'SessionPageSheet')).toHaveLength(1); + + unmount(); + }); + + it('happy: picking a repo and a profile binds through bindToRepo', async () => { + Object.assign(h.bindingsQuery, { isLoading: false }); + h.mutations.bind.mutateAsync.mockResolvedValue({ success: true }); + + const { renderer, unmount } = await mountScreen(); + pressButton(renderer.root, 'Add default'); + pressPressable(renderer.root, 'Select repository…'); + pressPressable(renderer.root, 'acme/infra'); + pressPressable(renderer.root, 'Select profile'); + pressPressable(renderer.root, 'Backend debugging'); + pressButton(renderer.root, 'Add default'); + + await waitFor(() => h.mutations.bind.mutateAsync.mock.calls.length > 0); + expect(h.mutations.bind.mutateAsync.mock.calls[0]?.[0]).toEqual({ + profileId: 'profile-1', + repoFullName: 'acme/infra', + platform: 'gitlab', + }); + + unmount(); + }); + + it('retryable: a bind failure with no usable message toasts the fallback', async () => { + Object.assign(h.bindingsQuery, { isLoading: false }); + const error = new Error('placeholder'); + error.message = ''; + h.mutations.bind.mutateAsync.mockRejectedValue(error); + + const { renderer, unmount } = await mountScreen(); + pressButton(renderer.root, 'Add default'); + pressPressable(renderer.root, 'Select repository…'); + pressPressable(renderer.root, 'acme/api'); + pressPressable(renderer.root, 'Select profile'); + pressPressable(renderer.root, 'Backend debugging'); + pressButton(renderer.root, 'Add default'); + + await waitFor(() => h.error.mock.calls.length > 0); + expect(h.error).toHaveBeenCalledWith("Couldn't set default profile"); + + unmount(); + }); + + it('filters the repo picker as the search text changes', async () => { + Object.assign(h.bindingsQuery, { isLoading: false }); + + const { renderer, unmount } = await mountScreen(); + pressButton(renderer.root, 'Add default'); + pressPressable(renderer.root, 'Select repository…'); + + expect( + findAll(renderer.root, 'Pressable').some(n => n.props.accessibilityLabel === 'acme/api') + ).toBe(true); + await changeSearch(renderer.root, 'infra'); + await waitFor( + () => + !findAll(renderer.root, 'Pressable').some(n => n.props.accessibilityLabel === 'acme/api') + ); + expect( + findAll(renderer.root, 'Pressable').some(n => n.props.accessibilityLabel === 'acme/api') + ).toBe(false); + expect( + findAll(renderer.root, 'Pressable').some(n => n.props.accessibilityLabel === 'acme/infra') + ).toBe(true); + + unmount(); + }); + + it('keeps the rows while a refetch is in flight', async () => { + Object.assign(h.bindingsQuery, { bindings: [testBinding()], isLoading: false }); + + const { renderer, queryClient, unmount } = await mountScreen(); + + h.bindingsQuery.isRefetching = true; + await act(async () => { + rerenderScreen(renderer, queryClient); + await Promise.resolve(); + }); + expect(findAll(renderer.root, 'Text').map(node => node.props.children)).toContain('acme/api'); + + unmount(); + }); +}); diff --git a/apps/mobile/src/components/profiles/repo-bindings-screen.test-helpers.ts b/apps/mobile/src/components/profiles/repo-bindings-screen.test-helpers.ts new file mode 100644 index 00000000000..9931f27b806 --- /dev/null +++ b/apps/mobile/src/components/profiles/repo-bindings-screen.test-helpers.ts @@ -0,0 +1,105 @@ +import { type QueryClient, QueryClientProvider } from '@tanstack/react-query'; +import { createElement } from 'react'; + +import { RepoBindingsScreen } from '@/components/profiles/repo-bindings-screen'; +import { act, type ReactTestInstance, type ReactTestRenderer } from '@/test/renderer'; +import { renderWithProviders } from '@/test/render-with-providers'; + +/** + * Shared harness for the repo-bindings mounted tests. The `vi.mock` calls stay + * in the test file (they must be hoisted there); everything that does not depend + * on the hoisted mock state lives here. + */ + +export type TestBinding = { + repoFullName: string; + platform: string; + profileId: string; + profileName: string; +}; + +export function testBinding(overrides: Partial = {}): TestBinding { + return { + repoFullName: 'acme/api', + platform: 'github', + profileId: 'profile-1', + profileName: 'Backend debugging', + ...overrides, + }; +} + +export function findAll(root: ReactTestInstance, type: string): ReactTestInstance[] { + return root.findAll(node => typeof node.type === 'string' && node.type === type); +} + +export function findOne(root: ReactTestInstance, type: string): ReactTestInstance { + const node = findAll(root, type)[0]; + if (!node) { + throw new Error(`${type} was not rendered`); + } + return node; +} + +/** Press the first Pressable (or Button) carrying an accessibility label. */ +export function pressPressable(root: ReactTestInstance, label: string): void { + const pressable = findAll(root, 'Pressable').find( + node => node.props.accessibilityLabel === label + ); + if (!pressable) { + throw new Error(`pressable ${label} was not rendered`); + } + act(() => { + (pressable.props as { onPress: () => void }).onPress(); + }); +} + +export function findButton(root: ReactTestInstance, label: string): ReactTestInstance { + const button = findAll(root, 'Button').find(node => node.props.accessibilityLabel === label); + if (!button) { + throw new Error(`button ${label} was not rendered`); + } + return button; +} + +export function pressButton(root: ReactTestInstance, label: string): void { + const button = findButton(root, label); + act(() => { + (button.props as { onPress: () => void }).onPress(); + }); +} + +/** + * Type into the picker's search input (its accessibility label matches) and + * yield once so the deferred query — the input stays urgent while the filtered + * rows trail — commits before the caller asserts. + */ +export async function changeSearch(root: ReactTestInstance, value: string): Promise { + const input = findAll(root, 'TextInput')[0]; + if (!input) { + throw new Error('search input was not rendered'); + } + await act(async () => { + (input.props as { onChangeText: (v: string) => void }).onChangeText(value); + await Promise.resolve(); + }); +} + +// eslint-disable-next-line typescript-eslint/promise-function-async -- returning the harness promise unchanged +export function mountScreen(organizationId?: string) { + return renderWithProviders(createElement(RepoBindingsScreen, { organizationId })); +} + +/** Re-render the mounted screen against fresh mock data, keeping the providers. */ +export function rerenderScreen( + renderer: ReactTestRenderer, + queryClient: QueryClient, + organizationId?: string +): void { + renderer.update( + createElement( + QueryClientProvider, + { client: queryClient }, + createElement(RepoBindingsScreen, { organizationId }) + ) + ); +} diff --git a/apps/mobile/src/components/profiles/repo-bindings-screen.tsx b/apps/mobile/src/components/profiles/repo-bindings-screen.tsx new file mode 100644 index 00000000000..0f6cc95bd83 --- /dev/null +++ b/apps/mobile/src/components/profiles/repo-bindings-screen.tsx @@ -0,0 +1,328 @@ +import { useState } from 'react'; +import { useTranslation } from 'react-i18next'; +import { Pressable, ScrollView, View } from 'react-native'; +import { toast } from 'sonner-native'; + +import { EmptyState } from '@/components/empty-state'; +import { ProfilePickerSheet } from '@/components/profiles/repo-bindings-profile-sheet'; +import { RepoPickerSheet } from '@/components/profiles/repo-picker-sheet'; +import { + type RepoBindingPlatform, + type RepoOption, + repoPlatformBadge, + repositoryOptionKey, +} from '@/components/profiles/repo-bindings-model'; +import { QueryError } from '@/components/query-error'; +import { ScreenHeader } from '@/components/screen-header'; +import { Button } from '@/components/ui/button'; +import { ChevronDown, GitBranch, Trash2 } from '@/components/ui/icons'; +import { Skeleton } from '@/components/ui/skeleton'; +import { Text } from '@/components/ui/text'; +import { useAgentProfileList } from '@/lib/hooks/use-agent-profiles'; +import { + useRepoBindingMutations, + useRepoBindings, + useRepoOptions, +} from '@/lib/hooks/use-repo-bindings'; +import { useThemeColors } from '@/lib/hooks/use-theme-colors'; + +/** The mutation hook toasts `error.message`; the screen supplies a fallback otherwise. */ +function hasUsableMessage(error: unknown): boolean { + return error instanceof Error && error.message.trim().length > 0; +} + +/** + * Content-shaped rows in the same slot and height as a loaded binding row, so + * the swap from skeleton to bindings does not move the Add control below it. + */ +function RepoBindingsSkeleton() { + return ( + + {[0, 1, 2].map(index => ( + + ))} + + ); +} + +/** One binding row: repo full name, provider badge, profile name, remove. */ +function RepoBindingRow({ + repoFullName, + platform, + profileName, + isRemoving, + onRemove, +}: Readonly<{ + repoFullName: string; + platform: string; + profileName: string; + isRemoving: boolean; + onRemove: () => void; +}>) { + const { t } = useTranslation(); + const colors = useThemeColors(); + return ( + + + + + {repoFullName} + + + {profileName} + + + + {repoPlatformBadge(platform)} + + + + + + ); +} + +/** A tappable field row that opens one of the Add form's pickers. */ +function PickerField({ + label, + value, + accessibilityLabel, + disabled, + onPress, +}: Readonly<{ + label: string; + value: string | null; + accessibilityLabel: string; + disabled: boolean; + onPress: () => void; +}>) { + const colors = useThemeColors(); + return ( + + + {value ?? label} + + + + ); +} + +/** + * The repo bindings screen: every default profile binding for the context, with + * a per-row remove and an Add default form (repo picker + profile picker). The + * Add action stays disabled until both a repo and a profile are chosen — the + * web dialog's behaviour — so no invalid submit is possible. Selection is a tap + * in a native sheet; there is no drag anywhere. + */ +export function RepoBindingsScreen({ organizationId }: Readonly<{ organizationId?: string }>) { + const { t } = useTranslation(); + const bindingsQuery = useRepoBindings(organizationId); + const { bind, unbind } = useRepoBindingMutations(organizationId); + const profileList = useAgentProfileList(organizationId); + + const [isAdding, setIsAdding] = useState(false); + const [selectedRepo, setSelectedRepo] = useState(null); + const [selectedProfileId, setSelectedProfileId] = useState(''); + const [repoPickerOpen, setRepoPickerOpen] = useState(false); + const [profilePickerOpen, setProfilePickerOpen] = useState(false); + + const repoOptions = useRepoOptions(organizationId, isAdding); + const profileChoices = + organizationId === undefined ? profileList.personalProfiles : profileList.orgProfiles; + const selectedProfile = profileChoices.find(profile => profile.id === selectedProfileId); + const selectedRepoKey = selectedRepo === null ? null : repositoryOptionKey(selectedRepo); + const canAdd = selectedRepo !== null && selectedProfileId !== '' && !bind.isPending; + + const resetAdd = () => { + setSelectedRepo(null); + setSelectedProfileId(''); + setIsAdding(false); + }; + + const handleAdd = async () => { + if (selectedRepo === null || selectedProfileId === '') { + return; + } + try { + await bind.mutateAsync({ + profileId: selectedProfileId, + repoFullName: selectedRepo.fullName, + platform: selectedRepo.platform, + }); + } catch (error) { + if (!hasUsableMessage(error)) { + toast.error(t('profiles.repoBindings.addFailed')); + } + return; + } + toast.success(t('profiles.repoBindings.addedToast', { repo: selectedRepo.fullName })); + resetAdd(); + }; + + const handleUnbind = async (repoFullName: string, platform: RepoBindingPlatform) => { + try { + await unbind.mutateAsync({ repoFullName, platform }); + } catch (error) { + if (!hasUsableMessage(error)) { + toast.error(t('profiles.repoBindings.removeFailed')); + } + return; + } + toast.success(t('profiles.repoBindings.removedToast', { repo: repoFullName })); + }; + + return ( + + + + {bindingsQuery.isError ? ( + void bindingsQuery.refetch()} + isRetrying={bindingsQuery.isRefetching} + /> + ) : null} + + {!bindingsQuery.isError && bindingsQuery.isLoading ? : null} + + {!bindingsQuery.isError && + !bindingsQuery.isLoading && + bindingsQuery.bindings.length === 0 ? ( + + ) : null} + + {!bindingsQuery.isError && !bindingsQuery.isLoading && bindingsQuery.bindings.length > 0 ? ( + + {bindingsQuery.bindings.map(binding => ( + { + void handleUnbind(binding.repoFullName, binding.platform as RepoBindingPlatform); + }} + /> + ))} + + ) : null} + + {isAdding ? ( + + { + setRepoPickerOpen(true); + }} + /> + { + setProfilePickerOpen(true); + }} + /> + + + + + + ) : ( + + )} + + + {repoPickerOpen ? ( + { + repoOptions.refetch(); + }} + selectedKey={selectedRepoKey} + onSelect={repo => { + setSelectedRepo(repo); + setRepoPickerOpen(false); + }} + onClose={() => { + setRepoPickerOpen(false); + }} + /> + ) : null} + + {profilePickerOpen ? ( + { + void profileList.refetch(); + }} + onSelect={profileId => { + setSelectedProfileId(profileId); + setProfilePickerOpen(false); + }} + onClose={() => { + setProfilePickerOpen(false); + }} + /> + ) : null} + + ); +} diff --git a/apps/mobile/src/components/profiles/repo-picker-sheet.tsx b/apps/mobile/src/components/profiles/repo-picker-sheet.tsx new file mode 100644 index 00000000000..b42273b6472 --- /dev/null +++ b/apps/mobile/src/components/profiles/repo-picker-sheet.tsx @@ -0,0 +1,188 @@ +import { useDeferredValue, useMemo, useState } from 'react'; +import { useTranslation } from 'react-i18next'; +import { Pressable, ScrollView, View } from 'react-native'; +import { useSafeAreaInsets } from 'react-native-safe-area-context'; + +import { SessionPageSheet } from '@/components/agents/session-page-sheet'; +import { EmptyState } from '@/components/empty-state'; +import { + filterRepositoryOptions, + type RepoOption, + repositoryOptionKey, +} from '@/components/profiles/repo-bindings-model'; +import { QueryError } from '@/components/query-error'; +import { SheetHeader } from '@/components/sheet-header'; +import { Check, GitBranch, Lock, Search, SearchX, Unlock } from '@/components/ui/icons'; +import { Input } from '@/components/ui/input'; +import { Skeleton } from '@/components/ui/skeleton'; +import { Text } from '@/components/ui/text'; +import { useThemeColors } from '@/lib/hooks/use-theme-colors'; + +type RepoPickerSheetProps = Readonly<{ + repositories: readonly RepoOption[]; + isLoading: boolean; + isError: boolean; + onRetry: () => void; + selectedKey: string | null; + onSelect: (repo: RepoOption) => void; + onClose: () => void; +}>; + +/** Content-shaped rows in the same slot and height as a loaded repo row. */ +function RepoPickerSkeleton() { + return ( + + {[0, 1, 2, 3].map(index => ( + + ))} + + ); +} + +/** + * One repo option: a private/public lock, the full name, the provider badge and + * a check on the selected row. The `accessibilityLabel` is the full name, so a + * screen reader matches the row by the repository it picks. + */ +function RepoOptionRow({ + repo, + isSelected, + onSelect, +}: Readonly<{ repo: RepoOption; isSelected: boolean; onSelect: () => void }>) { + const { t } = useTranslation(); + const colors = useThemeColors(); + return ( + + {repo.private ? ( + + + + ) : ( + + )} + + {repo.fullName} + + + {repo.platform === 'gitlab' ? 'GL' : 'GH'} + + {isSelected ? : null} + + ); +} + +/** + * The repo picker sheet: a search field over the merged GitHub + GitLab options + * and one tap-to-select row per repository. Mounted only while open, so the + * search text seeds fresh each time and the provider queries run only then. + * + * Rendered through `SessionPageSheet`, so it is a real presented sheet from + * either call site (the bindings screen or the Overview pins section) rather + * than content laid into the caller's column. The search field stays urgent + * while the filtered list trails on `useDeferredValue`, so a typing burst over + * a large repository list does not filter and reconcile rows on every key. + * Selection is a tap; there is no drag anywhere in the flow. + */ +export function RepoPickerSheet({ + repositories, + isLoading, + isError, + onRetry, + selectedKey, + onSelect, + onClose, +}: Readonly) { + const { t } = useTranslation(); + const colors = useThemeColors(); + const insets = useSafeAreaInsets(); + const [search, setSearch] = useState(''); + const deferredSearch = useDeferredValue(search); + const visible = useMemo( + () => filterRepositoryOptions(repositories, deferredSearch), + [repositories, deferredSearch] + ); + const isSearching = deferredSearch.trim().length > 0; + + let body = null; + if (isLoading) { + body = ; + } else if (isError) { + body = ( + + ); + } else if (visible.length === 0) { + body = ( + + ); + } else { + body = visible.map(repo => ( + { + onSelect(repo); + }} + /> + )); + } + + return ( + + + + + + + + {body} + + + + ); +} diff --git a/apps/mobile/src/components/profiles/skill-form-sheet.tsx b/apps/mobile/src/components/profiles/skill-form-sheet.tsx new file mode 100644 index 00000000000..c7304abb3c2 --- /dev/null +++ b/apps/mobile/src/components/profiles/skill-form-sheet.tsx @@ -0,0 +1,137 @@ +import { useRef, useState } from 'react'; +import { useTranslation } from 'react-i18next'; +import { ScrollView, View } from 'react-native'; +import { useSafeAreaInsets } from 'react-native-safe-area-context'; + +import { SessionPageSheet } from '@/components/agents/session-page-sheet'; +import { parseSkillMarkdown, skillErrorKey } from '@/components/profiles/skill-markdown'; +import { SheetHeader } from '@/components/sheet-header'; +import { FormField } from '@/components/ui/form-field'; +import { Text } from '@/components/ui/text'; +import { type SkillInputError, validateSkillInput } from '@/lib/agent-profile-forms'; + +/** The validated fields a saved skill is built from. */ +export type SkillFormSubmission = { + name: string; + rawMarkdown: string; + description?: string; +}; + +type SkillFormSheetProps = Readonly<{ + /** The skill being edited, or `null` when adding a new one. */ + skill: { name: string; rawMarkdown: string } | null; + isSaving: boolean; + onClose: () => void; + /** + * Persist the validated fields. The caller owns create-vs-update and closes + * the sheet on success; on failure it returns with the sheet still open. + */ + onSave: (submission: SkillFormSubmission) => void; +}>; + +/** + * The add/edit skill sheet. Mounted only while open, so every open seeds fresh + * fields from the skill it edits. Fields are uncontrolled (refs), per the app's + * iOS text-input rule; the name field remounts via `prefill` when frontmatter + * in the content fills it in, and never overwrites a name the user typed. + * + * Save validates through `validateSkillInput`; a refused input shows the + * matching `profiles.skill*` message under the field at fault and persists + * nothing. + */ +export function SkillFormSheet({ + skill, + isSaving, + onClose, + onSave, +}: Readonly) { + const { t } = useTranslation(); + const insets = useSafeAreaInsets(); + const nameRef = useRef(skill?.name ?? ''); + const contentRef = useRef(skill?.rawMarkdown ?? ''); + const nameEditedRef = useRef(false); + const [prefill, setPrefill] = useState(skill?.name ?? ''); + const [error, setError] = useState(null); + + const nameError = error === 'empty' || error === 'bad-name' ? t(skillErrorKey(error)) : undefined; + const contentError = error === 'no-content' ? t(skillErrorKey(error)) : undefined; + + const submit = () => { + const result = validateSkillInput({ name: nameRef.current, content: contentRef.current }); + if (result.error !== null) { + setError(result.error); + return; + } + setError(null); + onSave({ + name: nameRef.current.trim(), + rawMarkdown: contentRef.current, + ...(result.description === undefined ? {} : { description: result.description }), + }); + }; + + return ( + + + + {t('profiles.skillsHint')} + { + nameEditedRef.current = true; + nameRef.current = value; + if (error === 'empty' || error === 'bad-name') { + setError(null); + } + }} + /> + { + contentRef.current = value; + if (error === 'no-content' && value.trim().length > 0) { + setError(null); + } + if (!nameEditedRef.current) { + const parsed = parseSkillMarkdown(value); + if (parsed.name !== undefined && parsed.name.length > 0 && parsed.name !== prefill) { + nameRef.current = parsed.name; + setPrefill(parsed.name); + } + } + }} + /> + + + + ); +} diff --git a/apps/mobile/src/components/profiles/skill-markdown.test.ts b/apps/mobile/src/components/profiles/skill-markdown.test.ts new file mode 100644 index 00000000000..75bb9296d97 --- /dev/null +++ b/apps/mobile/src/components/profiles/skill-markdown.test.ts @@ -0,0 +1,71 @@ +import { describe, expect, it } from 'vitest'; + +import { + parseSkillMarkdown, + skillErrorKey, + skillRowSubtitle, +} from '@/components/profiles/skill-markdown'; +import { validateSkillInput } from '@/lib/agent-profile-forms'; + +describe('parseSkillMarkdown', () => { + it('parses an unquoted name and description', () => { + const markdown = ['---', 'name: code-review', 'description: Reviews a diff', '---', ''].join( + '\n' + ); + expect(parseSkillMarkdown(markdown)).toEqual({ + name: 'code-review', + description: 'Reviews a diff', + }); + }); + + it('strips matching quotes around the values', () => { + const markdown = [ + '---', + 'name: "code-review"', + "description: 'Reviews a diff'", + '---', + '', + ].join('\n'); + expect(parseSkillMarkdown(markdown)).toEqual({ + name: 'code-review', + description: 'Reviews a diff', + }); + }); + + it('returns nothing without a leading frontmatter block', () => { + expect(parseSkillMarkdown('# Just markdown')).toEqual({}); + expect(parseSkillMarkdown('')).toEqual({}); + }); + + it('returns a frontmatter name verbatim, so validation can reject its case', () => { + const markdown = ['---', 'name: My-Skill', '---', 'Body'].join('\n'); + expect(parseSkillMarkdown(markdown).name).toBe('My-Skill'); + }); +}); + +describe('skillErrorKey', () => { + it('maps each validation code to its copy key', () => { + expect(skillErrorKey('empty')).toBe('profiles.skillNameRequired'); + expect(skillErrorKey('bad-name')).toBe('profiles.skillNameInvalid'); + expect(skillErrorKey('no-content')).toBe('profiles.skillContentRequired'); + }); + + it('maps an uppercase name to the invalid-name copy', () => { + const result = validateSkillInput({ name: 'My-Skill', content: 'Body' }); + expect(result.error).toBe('bad-name'); + expect(skillErrorKey(result.error as 'bad-name')).toBe('profiles.skillNameInvalid'); + }); + + it('maps whitespace-only content to the content-required copy', () => { + const result = validateSkillInput({ name: 'my-skill', content: ' ' }); + expect(result.error).toBe('no-content'); + expect(skillErrorKey(result.error as 'no-content')).toBe('profiles.skillContentRequired'); + }); +}); + +describe('skillRowSubtitle', () => { + it('shows the server source type', () => { + expect(skillRowSubtitle({ sourceType: 'custom' })).toBe('custom'); + expect(skillRowSubtitle({ sourceType: 'marketplace' })).toBe('marketplace'); + }); +}); diff --git a/apps/mobile/src/components/profiles/skill-markdown.ts b/apps/mobile/src/components/profiles/skill-markdown.ts new file mode 100644 index 00000000000..f4914ad5d56 --- /dev/null +++ b/apps/mobile/src/components/profiles/skill-markdown.ts @@ -0,0 +1,40 @@ +/** + * Pure helpers for the profile Skills screen. + * + * No React and no React Native imports. The frontmatter parser and the input + * validation both live in the s2 form module (`@/lib/agent-profile-forms`); + * this module re-exports the parser under the name the Skills screen reads and + * owns the mapping from a validation error to the `profiles.skill*` copy key + * plus the row subtitle. + */ + +import { parseSkillFrontmatter, type SkillInputError } from '@/lib/agent-profile-forms'; + +export { parseSkillFrontmatter as parseSkillMarkdown }; + +/** The `profiles.*` copy key for each `validateSkillInput` error code. */ +const SKILL_ERROR_KEYS = { + empty: 'profiles.skillNameRequired', + 'bad-name': 'profiles.skillNameInvalid', + 'no-content': 'profiles.skillContentRequired', +} satisfies Record; + +/** + * The copy key the sheet shows for a refused skill input. The key names the + * field at fault, so the sheet can render it under the right field. + */ +export function skillErrorKey(error: SkillInputError): string { + return SKILL_ERROR_KEYS[error]; +} + +/** The skill fields the row subtitle reads. Structural, so tests need no tRPC type. */ +export type SkillRowSource = { sourceType: string }; + +/** + * The muted second line of a skill row. The server's `sourceType` (`custom` or + * `marketplace`) is the raw value the web editor shows, so the row keeps the + * same wording without a new catalog key. + */ +export function skillRowSubtitle(skill: SkillRowSource): string { + return skill.sourceType; +} diff --git a/apps/mobile/src/components/ui/preference-row.tsx b/apps/mobile/src/components/ui/preference-row.tsx index 6a08646d344..46be951badb 100644 --- a/apps/mobile/src/components/ui/preference-row.tsx +++ b/apps/mobile/src/components/ui/preference-row.tsx @@ -14,6 +14,12 @@ type PreferenceRowProps = Readonly<{ disabled: boolean; busy?: boolean; onValueChange: (next: boolean) => void; + /** + * Accessibility label for the switch. Defaults to the row title; pass an + * action label when the title names the setting and the action differs + * (e.g. "Default profile" row whose switch says "Remove as default"). + */ + switchAccessibilityLabel?: string; }>; /** Switch row shaped like the Notifications category row. */ @@ -25,6 +31,7 @@ export function PreferenceRow({ disabled, busy = false, onValueChange, + switchAccessibilityLabel, }: PreferenceRowProps) { const colors = useThemeColors(); return ( @@ -47,7 +54,7 @@ export function PreferenceRow({ diff --git a/apps/mobile/src/i18n/catalog-parity.test.ts b/apps/mobile/src/i18n/catalog-parity.test.ts index e393a51915f..5203d2ce820 100644 --- a/apps/mobile/src/i18n/catalog-parity.test.ts +++ b/apps/mobile/src/i18n/catalog-parity.test.ts @@ -27,8 +27,9 @@ const PLURAL_SUFFIX = /_(?:zero|one|two|few|many|other)$/; * cannot ship. Delete an entry the translation slice has landed in every * catalog. * - * The notifications.category.*Unavailable reasons below landed in all 86 - * catalogs, so nothing is pending translation today. + * Empty: the profile-editor validation copy the review-fix slice added (the + * duplicate-key refusal and the MCP bound messages) has landed in every one of + * the 87 catalogs, so the missing-key assertion is strict again. */ const PENDING_TRANSLATION_KEYS = new Set(); diff --git a/apps/mobile/src/i18n/locales/af.json b/apps/mobile/src/i18n/locales/af.json index 26461e01c8a..5ffaeffc125 100644 --- a/apps/mobile/src/i18n/locales/af.json +++ b/apps/mobile/src/i18n/locales/af.json @@ -2191,7 +2191,33 @@ "continueFrom": "Die sessie {{title}} word voortgesit.", "importingSession": "Sessie word ingevoer", "cliCannotContinue": "Hierdie CLI kan nie 'n sessie voortsit nie. Kies Cloud Agent of 'n ander CLI.", - "importFailed": "Hierdie sessie kon nie ingevoer word nie." + "importFailed": "Hierdie sessie kon nie ingevoer word nie.", + "pickProfile": "Kies 'n profiel", + "noProfile": "Geen profiel nie", + "manageProfiles": "Bestuur profiele", + "noProfiles": "Nog geen profiele nie. Skep een om omgewingsveranderlikes, MCP-bedieners en vaardighede by te voeg.", + "advancedConfig": "Gevorderde konfigurasie", + "advancedConfigDescription": "Kies 'n profiel om gestoorde konfigurasies te laai, en konfigureer bykomende omgewingsveranderlikes en opstelopdragte handmatig", + "saveAsProfile": "Stoor as profiel", + "saveProfileDescription": "Stoor die huidige omgewingsveranderlikes en opstelopdragte as 'n herbruikbare profiel.", + "profileSummary": "{{vars}} omgewingsveranderlikes · {{commands}} opstelopdragte", + "profileSaved": "Profiel \"{{name}}\" is gestoor", + "organizationProfiles": "Organisasie profiele", + "personalProfiles": "Persoonlike profiele", + "yourProfiles": "Jou profiele", + "failedToLoadProfiles": "Kon nie profiele laai nie", + "nameTooLong": "Profielnaam is te lank", + "descriptionTooLong": "Beskrywing is te lank", + "secretsEncrypted": "Geheime word geïnkripteer voor berging.", + "profileActive": "Profiel aktief", + "profilesActive": "Profiele aktief", + "customConfigActive": "Pasgemaakte konfigurasie aktief", + "configNeedsAttention": "Config het aandag nodig", + "repoProfile": "Repo profiel", + "selectedProfile": "Geselekteerde profiel", + "manualOverrides": "Handmatige oorheersing", + "openSettingsToReview": "Maak Instellings oop om na te gaan", + "profileSelection": "Profielkeuse" }, "partRenderer": { "updatedFileCount_one": "{{displayCount}} lêer is bygewerk.", @@ -3224,5 +3250,180 @@ "multiplierError": "Voer 'n vermenigvuldiger van 1 of meer in", "permissionDenied": "Jy het nie toestemming om bestedingswaarskuwings te bestuur nie.", "pushOffByCategory": "Af in Kennisgewings" + }, + "profiles": { + "title": "Profiele", + "entrySubtitle": "Hergebruik omgewingsinstellings oor sessies heen", + "newProfile": "Nuwe profiel", + "emptyTitle": "Nog geen profiele nie", + "emptyDescription": "Skep 'n profiel om omgewingsveranderlikes, opstelopdragte en vaardighede oor sessies te hergebruik.", + "loadFailed": "Kon nie profiele laai nie", + "counts": { + "vars": "{{count}} veranderlikes", + "mcp": "{{count}} MCP", + "skills": "{{count}} vaardighede", + "commands": "{{count}} opdragte", + "varsShort": "{{count}}ver", + "skillsShort": "{{count}}vaa", + "commandsShort": "{{count}}opr" + }, + "nameLabel": "Profielnaam", + "namePlaceholder": "bv. Backend-ontfouting", + "descriptionLabel": "Profielbeskrywing", + "descriptionPlaceholder": "Opsionele beskrywing", + "ownerLabel": "Profiel eienaar", + "nameRequired": "Voer 'n profielnaam in", + "createAction": "Skep profiel", + "createFailed": "Kon nie profiel skep nie", + "createdToast": "Profiel \"{{name}}\" geskep", + "settingsSectionTitle": "Konfigurasie", + "defaultSectionTitle": "Verstek profiel", + "personalDefaultDescription": "Outomaties gelaai wanneer geen bewaarplek 'n vasgespelde profiel het nie.", + "organizationDefaultDescription": "Outo-gelaai vir lede wat geen persoonlike verstek het nie.", + "setAsDefault": "Stel as verstek", + "removeDefault": "Verwyder as verstek", + "saveFailed": "Kon nie profiel stoor nie", + "deleteTitle": "Vee profiel uit", + "deleteMessage": "Dit verwyder die profiel en sy instellings permanent. Dit kan nie ongedaan gemaak word nie.", + "deleteFailed": "Kon nie profiel uitvee nie", + "deleteBlocked": "Hierdie profiel word deur 'n webhook-sneller gebruik. Verwyder dit eers van daardie snellers af.", + "deletedToast": "Profiel \"{{name}}\" is uitgevee", + "variablesTitle": "Omgewingsveranderlikes", + "addVariable": "Voeg veranderlike by", + "keyLabel": "Sleutel", + "keyTooLong": "Sleutel moet 256 karakters of minder wees", + "keyDuplicate": "Hierdie sleutel bestaan reeds in die profiel", + "valueLabel": "Waarde", + "secretLabel": "Geheim", + "secrets": { + "markSecret": "Merk as geheim", + "masked": "Versteekte waarde", + "reveal": "Onthul waarde", + "newValuePlaceholder": "Voer nuwe geheime waarde in", + "valuePlaceholder": "Geheime waarde (geïnkripteer)" + }, + "variablesEmpty": "Nog geen veranderlikes nie", + "variablesSaveFailed": "Kon nie veranderlike stoor nie", + "commandsTitle": "Stel opdragte op", + "addCommand": "Voeg opdrag by", + "commandLabel": "Bevel", + "commandPlaceholder": "e.g. pnpm install", + "commandsEmpty": "Nog geen opstelopdragte nie", + "commandsSaveFailed": "Kon nie opstellingopdragte stoor nie", + "moveUp": "Beweeg op", + "moveDown": "Beweeg af", + "skillsTitle": "Vaardighede", + "addSkill": "Voeg vaardigheid by", + "skillNameLabel": "Vaardigheidsnaam", + "skillMarkdownLabel": "Vaardigheidsinhoud", + "skillsEmpty": "Nog geen vaardighede nie", + "skillNameRequired": "Voer 'n vaardigheidsnaam in", + "skillNameInvalid": "Vaardigheidsnaam moet met 'n letter of syfer begin en slegs kleinletters, syfers en strepies gebruik", + "skillContentRequired": "Vaardigheidsinhoud word vereis", + "skillSaveFailed": "Kon nie vaardigheid red nie", + "skillsHint": "Plak 'n SKILL.md en sy naam vul in; voeg 'n vaardigheid vanaf jou foon by sonder om te sleep en te laat val.", + "skillEditTitle": "Wysig vaardigheid", + "mcp": { + "title": "MCP-bedieners", + "add": "Voeg MCP-bediener by", + "empty": "Nog geen MCP-bedieners nie", + "name": "Bediener naam", + "type": "Bediener tipe", + "localType": "Plaaslik", + "remoteType": "Afgeleë", + "command": "Opdragreël", + "url": "URL", + "environment": "Omgewing (JSON)", + "headers": "Opskrifte (JSON)", + "timeout": "Uitteltyd", + "secretCount": "Geheime telling", + "namePlaceholder": "my-server", + "commandPlaceholder": "npx @example/mcp-server", + "jsonPlaceholder": "{\n \"API_KEY\": \"sk-...\"\n}", + "nameRequired": "Voer 'n bedienernaam in", + "nameInvalid": "Naam kan slegs letters, syfers, onderstrepe en strepe gebruik", + "commandRequired": "Voer 'n opdrag in", + "commandTooLong": "Gebruik hoogstens 50 argumente van 500 karakters elk", + "urlRequired": "Voer 'n URL in", + "urlInvalid": "Voer 'n geldige URL in", + "urlTooLong": "Die URL moet 2048 karakters of minder wees", + "jsonInvalid": "Voer geldige JSON in - 'n objek van stringwaardes", + "jsonTooLarge": "Gebruik hoogstens 50 inskrywings, sleutels tot 128 en waardes tot 4096 karakters", + "timeoutInvalid": "Uitteltyd moet 'n hele aantal millisekondes wees", + "edit": "Wysig MCP-bediener", + "saveFailed": "Kon nie MCP-bediener stoor nie" + }, + "agents": { + "title": "Profiel agente", + "add": "Voeg agent by", + "empty": "Nog geen agente nie", + "name": "Agent naam", + "slug": "Agent slak", + "description": "Agent beskrywing", + "model": "Model ignoreer", + "modelPlaceholder": "Gebruik sessie verstek", + "prompt": "Stelsel opdrag", + "steps": "Maks. stappe", + "stepsPlaceholder": "bv. 50", + "temperature": "Temperatuur", + "temperaturePlaceholder": "bv. 0.2", + "topP": "top_p", + "topPPlaceholder": "bv. 0.95", + "slugPlaceholder": "my-agent", + "namePlaceholder": "My agent", + "promptPlaceholder": "Jy is 'n...", + "tools": "Gereedskap", + "visibility": "Sigbaarheid", + "visibilityPrimary": "Primêr — gewys in die kieser", + "visibilitySubagent": "Subagent - slegs oproepbaar vanaf ander", + "visibilityAll": "Almal - albei", + "saveFailed": "Kon nie agent stoor nie", + "nameRequired": "Voer 'n agentnaam in", + "slugRequired": "Voer 'n agentslak in", + "slugInvalid": "Slak moet met 'n letter begin en slegs kleinletters, syfers en strepe gebruik", + "slugConflict": "Daardie slak word deur 'n ingeboude agent gereserveer", + "edit": "Wysig agent" + }, + "slashCommands": { + "title": "Slash opdragte", + "add": "Voeg skuinsstreep-opdrag by", + "empty": "Nog geen skuinsstreepbevele nie", + "name": "Opdrag naam", + "description": "Opdrag beskrywing", + "template": "Sjabloon", + "namePlaceholder": "explain", + "templatePlaceholder": "Verduidelik $ARGUMENTS", + "agent": "Agent", + "subtask": "Hardloop as subtaak", + "edit": "Wysig skuinsstreep opdrag", + "saveFailed": "Kon nie opdrag stoor nie", + "nameRequired": "Voer 'n opdragnaam in", + "nameInvalid": "Naam moet met 'n kleinletter begin en slegs kleinletters, syfers en strepe gebruik", + "templateRequired": "Voer 'n opdragsjabloon in", + "nameConflict": "Daardie naam word gereserveer deur 'n ingeboude opdrag" + }, + "repoBindings": { + "title": "Verstekprofiele vir Repos", + "add": "Voeg verstek by", + "empty": "Geen verstekke opgestel nie", + "emptyDescription": "Voeg 'n verstek by om 'n profiel outomaties toe te pas wanneer sessies vir 'n repo begin.", + "repo": "Kies bewaarplek …", + "profile": "Kies profiel", + "remove": "Ontbind", + "private": "Privaat", + "search": "Soek bewaarplekke …", + "addedToast": "Verstekprofiel gestel vir \"{{repo}}\"", + "addFailed": "Kon nie verstekprofiel stel nie", + "removedToast": "Verstekprofiel verwyder vir \"{{repo}}\"", + "removeFailed": "Kon nie verstekprofiel verwyder nie", + "pinnedTitle": "Vasgespeld aan bewaarplekke", + "pinRepo": "Speld 'n repo vas", + "pinnedEmpty": "Nie vasgespeld aan enige bewaarplekke nie.", + "pinnedDescription": "Outomaties gelaai wanneer 'n sessie een van hierdie bewaarplekke teiken.", + "pinnedToast": "Vasgespeld aan \"{{repo}}\"", + "pinFailed": "Kon nie repo vasspeld nie", + "unboundToast": "Repo ongebonde", + "unbindFailed": "Kon nie repo ontbind nie" + } } } diff --git a/apps/mobile/src/i18n/locales/am.json b/apps/mobile/src/i18n/locales/am.json index 4a73c7ac244..a772b8e8318 100644 --- a/apps/mobile/src/i18n/locales/am.json +++ b/apps/mobile/src/i18n/locales/am.json @@ -2191,7 +2191,33 @@ "continueFrom": "{{title}} እየቀጠለ ነው።", "importingSession": "ክፍለ ጊዜውን በማስመጣት ላይ", "cliCannotContinue": "ይህ CLI ክፍለ ጊዜን መቀጠል አይችልም። Cloud Agent ወይም ሌላ CLI ይምረጡ።", - "importFailed": "ይህን ክፍለ ጊዜ ማስመጣት አልተቻለም።" + "importFailed": "ይህን ክፍለ ጊዜ ማስመጣት አልተቻለም።", + "pickProfile": "መገለጫ ይምረጡ", + "noProfile": "ምንም መገለጫ የለም።", + "manageProfiles": "መገለጫዎችን ያስተዳድሩ", + "noProfiles": "እስካሁን ምንም መገለጫዎች የሉም። የአካባቢ ተለዋዋጮችን፣ MCP አገልጋዮችን እና ክህሎቶችን ለመጨመር አንድ ይፍጠሩ።", + "advancedConfig": "የላቀ ውቅር", + "advancedConfigDescription": "የተቀመጡ ውቅሮችን ለመጫን መገለጫ ይምረጡ እና ተጨማሪ የአካባቢ ተለዋዋጮችን እና የማዋቀር ትዕዛዞችን በእጅ ያዋቅሩ", + "saveAsProfile": "እንደ መገለጫ አስቀምጥ", + "saveProfileDescription": "የአሁኑን የአካባቢ ተለዋዋጮች እና የማዋቀር ትዕዛዞችን እንደ እንደገና ጥቅም ላይ ሊውል የሚችል መገለጫ ያስቀምጡ።", + "profileSummary": "{{vars}} የአካባቢ ተለዋዋጮች · {{commands}} የማዋቀር ትዕዛዞች", + "profileSaved": "መገለጫ {{name}} ተቀምጧል", + "organizationProfiles": "የድርጅት መገለጫዎች", + "personalProfiles": "የግል መገለጫዎች", + "yourProfiles": "የእርስዎ መገለጫዎች", + "failedToLoadProfiles": "መገለጫዎችን መጫን አልተሳካም።", + "nameTooLong": "የመገለጫ ስም በጣም ረጅም ነው።", + "descriptionTooLong": "መግለጫው በጣም ረጅም ነው።", + "secretsEncrypted": "ሚስጥሮች ከመከማቸታቸው በፊት የተመሰጠሩ ናቸው።", + "profileActive": "መገለጫ ንቁ", + "profilesActive": "መገለጫዎች ንቁ ናቸው።", + "customConfigActive": "ብጁ ውቅረት ገቢር ነው።", + "configNeedsAttention": "ኮንፊግ ትኩረት ያስፈልገዋል", + "repoProfile": "Repo መገለጫ", + "selectedProfile": "የተመረጠ መገለጫ", + "manualOverrides": "በእጅ መሻር", + "openSettingsToReview": "ለመገምገም ቅንብሮችን ይክፈቱ", + "profileSelection": "የመገለጫ ምርጫ" }, "partRenderer": { "updatedFileCount_one": "{{displayCount}} ፋይል ተዘምኗል።", @@ -3224,5 +3250,180 @@ "multiplierError": "ከ1 ወይም ከዚያ በላይ የሆነ ብዜት ያስገቡ", "permissionDenied": "የወጪ ማስጠንቀቂያዎችን ለማስተዳደር ፈቃድ የለዎትም።", "pushOffByCategory": "በማሳወቂያዎች ውስጥ ተሰናክሏል" + }, + "profiles": { + "title": "መገለጫዎች", + "entrySubtitle": "በክፍለ-ጊዜዎች ውስጥ የአካባቢ ቅንብሮችን እንደገና ይጠቀሙ", + "newProfile": "አዲስ መገለጫ", + "emptyTitle": "እስካሁን ምንም መገለጫዎች የሉም", + "emptyDescription": "የአካባቢ ተለዋዋጮችን፣ የማዋቀር ትዕዛዞችን እና በክፍለ-ጊዜዎች ያሉ ክህሎቶችን እንደገና ለመጠቀም መገለጫ ይፍጠሩ።", + "loadFailed": "መገለጫዎችን መጫን አልተቻለም", + "counts": { + "vars": "{{count}} ተለዋዋጮች", + "mcp": "{{count}} MCP", + "skills": "{{count}} ችሎታዎች", + "commands": "{{count}} ትዕዛዞች", + "varsShort": "{{count}}ተ", + "skillsShort": "{{count}}ች", + "commandsShort": "{{count}}ት" + }, + "nameLabel": "የመገለጫ ስም", + "namePlaceholder": "ለምሳሌ. የጀርባ ማረም", + "descriptionLabel": "የመገለጫ መግለጫ", + "descriptionPlaceholder": "አማራጭ መግለጫ", + "ownerLabel": "የመገለጫ ባለቤት", + "nameRequired": "የመገለጫ ስም አስገባ", + "createAction": "መገለጫ ይፍጠሩ", + "createFailed": "መገለጫ መፍጠር አልተቻለም", + "createdToast": "መገለጫ {{name}} ተፈጥሯል።", + "settingsSectionTitle": "ማዋቀር", + "defaultSectionTitle": "ነባሪ መገለጫ", + "personalDefaultDescription": "ምንም ማከማቻ የተሰካ መገለጫ ከሌለው በራስ-ሰር የሚጫን።", + "organizationDefaultDescription": "ምንም የግል ነባሪ ለሌላቸው አባላት በራስ-የተጫነ።", + "setAsDefault": "እንደ ነባሪ አዘጋጅ", + "removeDefault": "እንደ ነባሪ አስወግድ", + "saveFailed": "መገለጫ ማስቀመጥ አልተቻለም", + "deleteTitle": "መገለጫን ሰርዝ", + "deleteMessage": "ይሄ መገለጫውን እና ቅንብሮቹን እስከመጨረሻው ያስወግዳል። ይህ ሊቀለበስ አይችልም።", + "deleteFailed": "መገለጫን መሰረዝ አልተቻለም", + "deleteBlocked": "ይህ መገለጫ በዌብ መንጠቆ ቀስቅሴ ጥቅም ላይ ይውላል። በመጀመሪያ ከነዛ ቀስቅሴዎች ያስወግዱት።", + "deletedToast": "መገለጫ {{name}} ተሰርዟል።", + "variablesTitle": "የአካባቢ ተለዋዋጮች", + "addVariable": "ተለዋዋጭ ያክሉ", + "keyLabel": "ቁልፍ", + "keyTooLong": "ቁልፉ 256 ቁምፊዎች ወይም ከዚያ ያነሰ መሆን አለበት", + "keyDuplicate": "ይህ ቁልፍ አስቀድሞ በመገለጫው ውስጥ አለ", + "valueLabel": "ዋጋ", + "secretLabel": "ምስጢር", + "secrets": { + "markSecret": "እንደ ምስጢር ምልክት ያድርጉ", + "masked": "የተደበቀ እሴት", + "reveal": "ዋጋን አሳይ", + "newValuePlaceholder": "አዲስ ምስጢራዊ ዋጋ ያስገቡ", + "valuePlaceholder": "ምስጢራዊ ዋጋ (የተመሰጠረ)" + }, + "variablesEmpty": "እስካሁን ምንም ተለዋዋጮች የሉም", + "variablesSaveFailed": "ተለዋዋጭ ማስቀመጥ አልተቻለም", + "commandsTitle": "ማዋቀር ትዕዛዞች", + "addCommand": "ትዕዛዝ ጨምር", + "commandLabel": "ትዕዛዝ", + "commandPlaceholder": "e.g. pnpm install", + "commandsEmpty": "እስካሁን ምንም የማዋቀር ትዕዛዞች የሉም", + "commandsSaveFailed": "የማዋቀር ትዕዛዞችን ማስቀመጥ አልተቻለም", + "moveUp": "ወደ ላይ ተንቀሳቀስ", + "moveDown": "ወደ ታች ውሰድ", + "skillsTitle": "ችሎታዎች", + "addSkill": "ችሎታ ጨምር", + "skillNameLabel": "የክህሎት ስም", + "skillMarkdownLabel": "የክህሎት ይዘት", + "skillsEmpty": "እስካሁን ምንም ችሎታ የለም።", + "skillNameRequired": "የክህሎት ስም አስገባ", + "skillNameInvalid": "የክህሎት ስም በፊደል ወይም ዲጂት መጀመር አለበት እና ትንሽ ሆሄያትን፣ አሃዞችን እና ሰረዞችን ብቻ መጠቀም አለበት።", + "skillContentRequired": "የክህሎት ይዘት ያስፈልጋል", + "skillSaveFailed": "ችሎታ ማዳን አልተቻለም", + "skillsHint": "SKILL.md ይለጥፉ እና ስሙ ይሞላል; ሳይጎትቱ እና መጣል ሳይኖር ከስልክዎ ላይ ችሎታ ይጨምሩ።", + "skillEditTitle": "ችሎታን ያርትዑ", + "mcp": { + "title": "MCP አገልጋዮች", + "add": "የኤምሲፒ አገልጋይ አክል", + "empty": "እስካሁን ምንም MCP አገልጋዮች የሉም", + "name": "የአገልጋይ ስም", + "type": "የአገልጋይ ዓይነት", + "localType": "አካባቢያዊ", + "remoteType": "የርቀት", + "command": "የትእዛዝ መስመር", + "url": "URL", + "environment": "አካባቢ (JSON)", + "headers": "ራስጌዎች (JSON)", + "timeout": "ጊዜው አልቋል", + "secretCount": "ሚስጥራዊ ቆጠራ", + "namePlaceholder": "my-server", + "commandPlaceholder": "npx @example/mcp-server", + "jsonPlaceholder": "{\n \"API_KEY\": \"sk-...\"\n}", + "nameRequired": "የአገልጋይ ስም አስገባ", + "nameInvalid": "ስም ፊደሎችን፣ ቁጥሮችን፣ ሰረዞችን እና ሰረዞችን ብቻ መጠቀም ይችላል።", + "commandRequired": "ትእዛዝ አስገባ", + "commandTooLong": "ቢበዛ 50 አርጉመንቶችን ይጠቀሙ፣ እያንዳንዱ 500 ቁምፊ", + "urlRequired": "URL አስገባ", + "urlInvalid": "የሚሰራ URL አስገባ", + "urlTooLong": "URL 2048 ቁምፊ ወይም ከዚያ ያነሰ መሆን አለበት", + "jsonInvalid": "የሚሰራ JSON ያስገቡ - የሕብረቁምፊ እሴቶች ነገር", + "jsonTooLarge": "ቢበዛ 50 ግቤቶችን ይጠቀሙ፣ ቁልፎች እስከ 128 እና እሴቶች እስከ 4096 ቁምፊ", + "timeoutInvalid": "ጊዜው ያለፈበት ሙሉ የሚሊሰከንዶች ቁጥር መሆን አለበት።", + "edit": "የMCP አገልጋይ ያርትዑ", + "saveFailed": "የMCP አገልጋይን ማስቀመጥ አልተቻለም" + }, + "agents": { + "title": "የመገለጫ ወኪሎች", + "add": "ወኪል ያክሉ", + "empty": "እስካሁን ምንም ወኪሎች የሉም", + "name": "የወኪል ስም", + "slug": "ወኪል ስሉክ", + "description": "የወኪል መግለጫ", + "model": "ሞዴል መሻር", + "modelPlaceholder": "የክፍለ ጊዜ ነባሪ ተጠቀም", + "prompt": "የስርዓት ጥያቄ", + "steps": "ከፍተኛ ደረጃዎች", + "stepsPlaceholder": "ለምሳሌ 50", + "temperature": "የሙቀት መጠን", + "temperaturePlaceholder": "ለምሳሌ 0.2", + "topP": "top_p", + "topPPlaceholder": "ለምሳሌ 0.95", + "slugPlaceholder": "my-agent", + "namePlaceholder": "የእኔ ወኪል", + "promptPlaceholder": "አንተ የ...", + "tools": "መሳሪያዎች", + "visibility": "ታይነት", + "visibilityPrimary": "ዋና - በቃሚው ውስጥ ይታያል", + "visibilitySubagent": "ሱባጀንት - ከሌሎች ብቻ የሚጠራ", + "visibilityAll": "ሁሉም - ሁለቱም", + "saveFailed": "ወኪል ማስቀመጥ አልተቻለም", + "nameRequired": "የወኪል ስም አስገባ", + "slugRequired": "የወኪል ማሰሪያ አስገባ", + "slugInvalid": "ስሉግ በፊደል መጀመር እና ትንንሽ ሆሄያትን፣ አሃዞችን እና ሰረዞችን ብቻ መጠቀም አለበት።", + "slugConflict": "ያ ስሉግ አብሮ በተሰራ ወኪል የተጠበቀ ነው።", + "edit": "ወኪል አርትዕ" + }, + "slashCommands": { + "title": "Slash ትዕዛዞች", + "add": "የዝውውር ትዕዛዝ ያክሉ", + "empty": "እስካሁን ምንም የዝውውር ትዕዛዞች የሉም", + "name": "የትእዛዝ ስም", + "description": "የትዕዛዝ መግለጫ", + "template": "አብነት", + "namePlaceholder": "explain", + "templatePlaceholder": "$ARGUMENTS ያብራሩ", + "agent": "ወኪል", + "subtask": "እንደ ንዑስ ተግባር ያሂዱ", + "edit": "የጭረት ትእዛዝን ያርትዑ", + "saveFailed": "ትዕዛዝ ማስቀመጥ አልተቻለም", + "nameRequired": "የትእዛዝ ስም ያስገቡ", + "nameInvalid": "ስም በትንሽ ሆሄ መጀመር እና በትናንሽ ሆሄያት፣ አሃዞች እና ሰረዞች ብቻ መጠቀም አለበት።", + "templateRequired": "የትእዛዝ አብነት አስገባ", + "nameConflict": "ይህ ስም አብሮ በተሰራ ትእዛዝ የተያዘ ነው።" + }, + "repoBindings": { + "title": "ለሪፖዎች ነባሪ መገለጫዎች", + "add": "ነባሪ ያክሉ", + "empty": "ምንም ነባሪዎች አልተዋቀሩም።", + "emptyDescription": "ለሪፖ ክፍለ ጊዜዎችን ሲጀምሩ መገለጫን በራስ-ሰር ለመተግበር ነባሪ ያክሉ።", + "repo": "ማከማቻ ምረጥ…", + "profile": "መገለጫ ይምረጡ", + "remove": "ንቀል", + "private": "የግል", + "search": "ማከማቻዎችን ፈልግ…", + "addedToast": "ለ\"{{repo}}\" ነባሪ መገለጫ ተቀናብሯል", + "addFailed": "ነባሪ መገለጫ ማዘጋጀት አልተቻለም", + "removedToast": "ለ\"{{repo}}\" ነባሪ መገለጫ ተወግዷል", + "removeFailed": "ነባሪ መገለጫን ማስወገድ አልተቻለም", + "pinnedTitle": "ወደ ማከማቻዎች ተሰክቷል።", + "pinRepo": "ሪፖን ይሰኩ", + "pinnedEmpty": "በማናቸውም ማከማቻዎች ላይ አልተሰካም።", + "pinnedDescription": "አንድ ክፍለ ጊዜ ከእነዚህ ማከማቻዎች ውስጥ አንዱን ባነጣጠረ ቁጥር በራስ-የሚጫን።", + "pinnedToast": "በ\"{{repo}}\" ላይ ተሰክቷል", + "pinFailed": "ሪፖን ማያያዝ አልተቻለም", + "unboundToast": "ሪፖ ያልታሰረ", + "unbindFailed": "ሪፖን ማላቀቅ አልተቻለም" + } } } diff --git a/apps/mobile/src/i18n/locales/ar.json b/apps/mobile/src/i18n/locales/ar.json index 425326c853a..7d28a50c69c 100644 --- a/apps/mobile/src/i18n/locales/ar.json +++ b/apps/mobile/src/i18n/locales/ar.json @@ -1735,7 +1735,33 @@ "continueFrom": "تجري متابعة {{title}}.", "importingSession": "جارٍ استيراد الجلسة", "cliCannotContinue": "إصدار CLI هذا لا يدعم متابعة الجلسات. اختر Cloud Agent أو CLI آخر.", - "importFailed": "تعذر استيراد هذه الجلسة." + "importFailed": "تعذر استيراد هذه الجلسة.", + "pickProfile": "اختر ملفًا شخصيًا", + "noProfile": "لا يوجد ملف شخصي", + "manageProfiles": "إدارة الملفات الشخصية", + "noProfiles": "لا توجد ملفات شخصية حتى الآن. قم بإنشاء واحد لإضافة متغيرات البيئة وخوادم MCP والمهارات.", + "advancedConfig": "التكوين المتقدم", + "advancedConfigDescription": "حدد ملف تعريف لتحميل التكوينات المحفوظة، وقم بتكوين متغيرات البيئة الإضافية وأوامر الإعداد يدويًا", + "saveAsProfile": "حفظ كملف تعريف", + "saveProfileDescription": "احفظ متغيرات البيئة الحالية وأوامر الإعداد كملف تعريف قابل لإعادة الاستخدام.", + "profileSummary": "{{vars}} متغيرات البيئة · أوامر الإعداد {{commands}}", + "profileSaved": "تم حفظ الملف الشخصي \"{{name}}\".", + "organizationProfiles": "ملفات تعريف المنظمة", + "personalProfiles": "الملفات الشخصية", + "yourProfiles": "الملفات الشخصية الخاصة بك", + "failedToLoadProfiles": "فشل تحميل الملفات الشخصية", + "nameTooLong": "اسم الملف الشخصي طويل جدًا", + "descriptionTooLong": "الوصف طويل جدًا", + "secretsEncrypted": "يتم تشفير الأسرار قبل تخزينها.", + "profileActive": "الملف الشخصي نشط", + "profilesActive": "الملفات الشخصية نشطة", + "customConfigActive": "التكوين المخصص نشط", + "configNeedsAttention": "التكوين يحتاج إلى الاهتمام", + "repoProfile": "الملف الشخصي الريبو", + "selectedProfile": "الملف الشخصي المحدد", + "manualOverrides": "التجاوزات اليدوية", + "openSettingsToReview": "افتح الإعدادات للمراجعة", + "profileSelection": "اختيار الملف الشخصي" }, "partRenderer": { "updatedFileCount_one": "تم تحديث ملف واحد ({{displayCount}}).", @@ -3396,5 +3422,180 @@ "multiplierError": "أدخل مضاعفًا يساوي 1 أو أكثر", "permissionDenied": "ليست لديك صلاحية لإدارة تنبيهات الإنفاق.", "pushOffByCategory": "معطّل في الإشعارات" + }, + "profiles": { + "title": "الملفات الشخصية", + "entrySubtitle": "إعادة استخدام إعدادات البيئة عبر الجلسات", + "newProfile": "ملف تعريف جديد", + "emptyTitle": "لا توجد ملفات شخصية حتى الآن", + "emptyDescription": "قم بإنشاء ملف تعريف لإعادة استخدام متغيرات البيئة وأوامر الإعداد والمهارات عبر الجلسات.", + "loadFailed": "تعذر تحميل الملفات الشخصية", + "counts": { + "vars": "{{count}} متغيرات", + "mcp": "{{count}} MCP", + "skills": "{{count}} مهارات", + "commands": "{{count}} أوامر", + "varsShort": "{{count}}مت", + "skillsShort": "{{count}}مه", + "commandsShort": "{{count}}أ" + }, + "nameLabel": "اسم الملف الشخصي", + "namePlaceholder": "على سبيل المثال تصحيح أخطاء الواجهة الخلفية", + "descriptionLabel": "وصف الملف الشخصي", + "descriptionPlaceholder": "وصف اختياري", + "ownerLabel": "مالك الملف الشخصي", + "nameRequired": "أدخل اسم الملف الشخصي", + "createAction": "إنشاء الملف الشخصي", + "createFailed": "تعذر إنشاء الملف الشخصي", + "createdToast": "تم إنشاء الملف الشخصي \"{{name}}\".", + "settingsSectionTitle": "إعدادات", + "defaultSectionTitle": "الملف الشخصي الافتراضي", + "personalDefaultDescription": "يتم تحميله تلقائيًا عندما لا يكون لدى أي مستودع ملف تعريف مثبت.", + "organizationDefaultDescription": "يتم تحميلها تلقائيًا للأعضاء الذين ليس لديهم أي تقصير شخصي.", + "setAsDefault": "تعيين كافتراضي", + "removeDefault": "إزالة كافتراضي", + "saveFailed": "تعذر حفظ الملف الشخصي", + "deleteTitle": "حذف الملف الشخصي", + "deleteMessage": "يؤدي هذا إلى إزالة الملف الشخصي وإعداداته نهائيًا. لا يمكن التراجع عن هذا.", + "deleteFailed": "تعذر حذف الملف الشخصي", + "deleteBlocked": "يتم استخدام ملف التعريف هذا بواسطة مشغل webhook. قم بإزالته من تلك المشغلات أولاً.", + "deletedToast": "تم حذف الملف الشخصي \"{{name}}\".", + "variablesTitle": "متغيرات البيئة", + "addVariable": "إضافة متغير", + "keyLabel": "مفتاح", + "keyTooLong": "يجب ألا يزيد المفتاح عن 256 حرفًا", + "keyDuplicate": "هذا المفتاح موجود بالفعل في الملف الشخصي", + "valueLabel": "قيمة", + "secretLabel": "سر", + "secrets": { + "markSecret": "ضع علامة على أنها سرية", + "masked": "القيمة المخفية", + "reveal": "كشف القيمة", + "newValuePlaceholder": "أدخل قيمة سرية جديدة", + "valuePlaceholder": "قيمة سرية (مشفرة)" + }, + "variablesEmpty": "لا توجد متغيرات بعد", + "variablesSaveFailed": "تعذر حفظ المتغير", + "commandsTitle": "أوامر الإعداد", + "addCommand": "إضافة أمر", + "commandLabel": "يأمر", + "commandPlaceholder": "e.g. pnpm install", + "commandsEmpty": "لا توجد أوامر الإعداد حتى الآن", + "commandsSaveFailed": "تعذر حفظ أوامر الإعداد", + "moveUp": "تحرك للأعلى", + "moveDown": "تحرك للأسفل", + "skillsTitle": "مهارات", + "addSkill": "أضف مهارة", + "skillNameLabel": "اسم المهارة", + "skillMarkdownLabel": "محتوى المهارة", + "skillsEmpty": "لا توجد مهارات بعد", + "skillNameRequired": "أدخل اسم المهارة", + "skillNameInvalid": "يجب أن يبدأ اسم المهارة بحرف أو رقم ويستخدم فقط الأحرف الصغيرة والأرقام والشرطات", + "skillContentRequired": "مطلوب محتوى المهارة", + "skillSaveFailed": "لا يمكن حفظ المهارة", + "skillsHint": "الصق SKILL.md وسيملأ اسمه؛ أضف مهارة من هاتفك دون السحب والإفلات.", + "skillEditTitle": "تحرير المهارة", + "mcp": { + "title": "خوادم MCP", + "add": "إضافة خادم MCP", + "empty": "لا توجد خوادم MCP حتى الآن", + "name": "اسم الخادم", + "type": "نوع الخادم", + "localType": "محلي", + "remoteType": "بعيد", + "command": "سطر الأوامر", + "url": "عنوان URL", + "environment": "البيئة (JSON)", + "headers": "الرؤوس (JSON)", + "timeout": "نفذ الوقت", + "secretCount": "العد السري", + "namePlaceholder": "my-server", + "commandPlaceholder": "npx @example/mcp-server", + "jsonPlaceholder": "{\n \"API_KEY\": \"sk-...\"\n}", + "nameRequired": "أدخل اسم الخادم", + "nameInvalid": "يمكن أن يستخدم الاسم الحروف والأرقام والشرطات السفلية والشرطات فقط", + "commandRequired": "أدخل أمرًا", + "commandTooLong": "استخدم 50 وسيطًا كحد أقصى، بحيث لا يزيد كل منها عن 500 حرف", + "urlRequired": "أدخل عنوان URL", + "urlInvalid": "أدخل عنوان URL صالحًا", + "urlTooLong": "يجب ألا يزيد عنوان URL عن 2048 حرفًا", + "jsonInvalid": "أدخل JSON صالحًا — كائن ذو قيم سلسلة", + "jsonTooLarge": "استخدم 50 إدخالًا كحد أقصى، بمفاتيح حتى 128 وقيم حتى 4096 حرفًا", + "timeoutInvalid": "يجب أن تكون المهلة عددًا صحيحًا من المللي ثانية", + "edit": "تحرير خادم MCP", + "saveFailed": "تعذر حفظ خادم MCP" + }, + "agents": { + "title": "وكلاء الملف الشخصي", + "add": "إضافة وكيل", + "empty": "لا يوجد وكلاء بعد", + "name": "اسم الوكيل", + "slug": "سبيكة الوكيل", + "description": "وصف الوكيل", + "model": "تجاوز النموذج", + "modelPlaceholder": "استخدم الجلسة الافتراضية", + "prompt": "موجه النظام", + "steps": "الحد الأقصى للخطوات", + "stepsPlaceholder": "مثال: 50", + "temperature": "درجة الحرارة", + "temperaturePlaceholder": "مثال: 0.2", + "topP": "top_p", + "topPPlaceholder": "مثال: 0.95", + "slugPlaceholder": "my-agent", + "namePlaceholder": "وكيل أعمالي", + "promptPlaceholder": "أنت ...", + "tools": "أدوات", + "visibility": "الرؤية", + "visibilityPrimary": "الأساسي - يظهر في المنتقي", + "visibilitySubagent": "وكيل فرعي - يمكن الاتصال به فقط من الآخرين", + "visibilityAll": "الكل - كلاهما", + "saveFailed": "تعذر حفظ الوكيل", + "nameRequired": "أدخل اسم الوكيل", + "slugRequired": "أدخل سبيكة وكيل", + "slugInvalid": "يجب أن يبدأ الرمز بحرف ويستخدم فقط الأحرف الصغيرة والأرقام والشرطات", + "slugConflict": "هذه سبيكة محجوزة بواسطة وكيل مدمج", + "edit": "تحرير الوكيل" + }, + "slashCommands": { + "title": "أوامر القطع", + "add": "إضافة أمر شرطة مائلة", + "empty": "لا توجد أوامر مائلة حتى الآن", + "name": "اسم الأمر", + "description": "وصف الأمر", + "template": "نموذج", + "namePlaceholder": "explain", + "templatePlaceholder": "شرح $ARGUMENTS", + "agent": "عامل", + "subtask": "تشغيل كمهمة فرعية", + "edit": "تحرير أمر الشرطة المائلة", + "saveFailed": "تعذر حفظ الأمر", + "nameRequired": "أدخل اسم الأمر", + "nameInvalid": "يجب أن يبدأ الاسم بحرف صغير ويستخدم فقط الأحرف الصغيرة والأرقام والشرطات", + "templateRequired": "أدخل قالب الأمر", + "nameConflict": "هذا الاسم محجوز بواسطة أمر مضمن" + }, + "repoBindings": { + "title": "الملفات الشخصية الافتراضية لـ Repos", + "add": "أضف الافتراضي", + "empty": "لم يتم تكوين أي إعدادات افتراضية", + "emptyDescription": "أضف افتراضيًا لتطبيق ملف التعريف تلقائيًا عند بدء جلسات الريبو.", + "repo": "حدد المستودع…", + "profile": "حدد الملف الشخصي", + "remove": "فك الارتباط", + "private": "خاص", + "search": "بحث في المستودعات...", + "addedToast": "تم تعيين ملف التعريف الافتراضي لـ \"{{repo}}\"", + "addFailed": "لا يمكن تعيين ملف التعريف الافتراضي", + "removedToast": "تمت إزالة الملف الشخصي الافتراضي لـ \"{{repo}}\"", + "removeFailed": "تعذر إزالة الملف الشخصي الافتراضي", + "pinnedTitle": "مثبت في المستودعات", + "pinRepo": "تثبيت الريبو", + "pinnedEmpty": "لم يتم تثبيته على أي مستودعات.", + "pinnedDescription": "يتم تحميله تلقائيًا عندما تستهدف الجلسة أحد هذه المستودعات.", + "pinnedToast": "تم التثبيت في \"{{repo}}\"", + "pinFailed": "تعذر تثبيت الريبو", + "unboundToast": "الريبو غير منضم", + "unbindFailed": "تعذر إلغاء ربط الريبو" + } } } diff --git a/apps/mobile/src/i18n/locales/az.json b/apps/mobile/src/i18n/locales/az.json index adc73faf7df..fd2cf1ffcbe 100644 --- a/apps/mobile/src/i18n/locales/az.json +++ b/apps/mobile/src/i18n/locales/az.json @@ -2191,7 +2191,33 @@ "continueFrom": "{{title}} davam etdirilir.", "importingSession": "Sessiya idxal olunur", "cliCannotContinue": "Bu CLI sessiyanı davam etdirə bilmir. Cloud Agent və ya başqa CLI seç.", - "importFailed": "Bu sessiyanı idxal etmək mümkün olmadı." + "importFailed": "Bu sessiyanı idxal etmək mümkün olmadı.", + "pickProfile": "Profil seçin", + "noProfile": "Profil yoxdur", + "manageProfiles": "Profilləri idarə edin", + "noProfiles": "Hələ profil yoxdur. Ətraf mühit dəyişənləri, MCP serverləri və bacarıqları əlavə etmək üçün birini yaradın.", + "advancedConfig": "Qabaqcıl Konfiqurasiya", + "advancedConfigDescription": "Saxlanmış konfiqurasiyaları yükləmək üçün profil seçin və əlavə mühit dəyişənlərini və quraşdırma əmrlərini əl ilə konfiqurasiya edin", + "saveAsProfile": "Profil kimi yadda saxlayın", + "saveProfileDescription": "Cari mühit dəyişənlərini və quraşdırma əmrlərini təkrar istifadə edilə bilən profil kimi yadda saxlayın.", + "profileSummary": "{{vars}} mühit dəyişənləri · {{commands}} quraşdırma əmrləri", + "profileSaved": "\"{{name}}\" profili yadda saxlanıldı", + "organizationProfiles": "Təşkilat Profilləri", + "personalProfiles": "Şəxsi Profillər", + "yourProfiles": "Profilləriniz", + "failedToLoadProfiles": "Profilləri yükləmək alınmadı", + "nameTooLong": "Profil adı çox uzundur", + "descriptionTooLong": "Təsvir çox uzundur", + "secretsEncrypted": "Saxlamadan əvvəl sirlər şifrələnir.", + "profileActive": "Profil aktivdir", + "profilesActive": "Profillər aktivdir", + "customConfigActive": "Fərdi konfiqurasiya aktivdir", + "configNeedsAttention": "Konfiqurasiya diqqət tələb edir", + "repoProfile": "Repo profili", + "selectedProfile": "Seçilmiş profil", + "manualOverrides": "Əl ilə ləğvlər", + "openSettingsToReview": "Nəzərdən keçirmək üçün Parametrləri açın", + "profileSelection": "Profil seçimi" }, "partRenderer": { "updatedFileCount_one": "{{displayCount}} fayl yeniləndi.", @@ -3224,5 +3250,180 @@ "multiplierError": "1 və ya daha böyük əmsal daxil et", "permissionDenied": "Xərc bildirişlərini idarə etmək icazən yoxdur.", "pushOffByCategory": "Bildirişlərdə söndürülüb" + }, + "profiles": { + "title": "Profillər", + "entrySubtitle": "Seanslar arasında mühit parametrlərini təkrar istifadə edin", + "newProfile": "Yeni profil", + "emptyTitle": "Hələ profil yoxdur", + "emptyDescription": "Mühit dəyişənlərindən, quraşdırma əmrlərindən və sessiyalar arasında bacarıqlardan təkrar istifadə etmək üçün profil yaradın.", + "loadFailed": "Profilləri yükləmək mümkün olmadı", + "counts": { + "vars": "{{count}} dəyişən", + "mcp": "{{count}} MCP", + "skills": "{{count}} bacarıq", + "commands": "{{count}} əmr", + "varsShort": "{{count}}d", + "skillsShort": "{{count}}b", + "commandsShort": "{{count}}ə" + }, + "nameLabel": "Profil adı", + "namePlaceholder": "məs. Backend sazlama", + "descriptionLabel": "Profil təsviri", + "descriptionPlaceholder": "Könüllü təsvir", + "ownerLabel": "Profil sahibi", + "nameRequired": "Profil adı daxil edin", + "createAction": "Profil yaradın", + "createFailed": "Profil yaratmaq mümkün olmadı", + "createdToast": "\"{{name}}\" profili yaradıldı", + "settingsSectionTitle": "Konfiqurasiya", + "defaultSectionTitle": "Defolt profil", + "personalDefaultDescription": "Heç bir repozitorda bərkidilmiş profil olmadıqda avtomatik yüklənir.", + "organizationDefaultDescription": "Şəxsi defoltu olmayan üzvlər üçün avtomatik yüklənir.", + "setAsDefault": "Defolt olaraq təyin edin", + "removeDefault": "Defolt olaraq silin", + "saveFailed": "Profili saxlamaq mümkün olmadı", + "deleteTitle": "Profili silin", + "deleteMessage": "Bu, profili və onun parametrlərini həmişəlik silir. Bu geri qaytarıla bilməz.", + "deleteFailed": "Profili silmək mümkün olmadı", + "deleteBlocked": "Bu profil webhook trigger tərəfindən istifadə olunur. Əvvəlcə onu bu tətiklərdən çıxarın.", + "deletedToast": "\"{{name}}\" profili silindi", + "variablesTitle": "Ətraf mühit dəyişənləri", + "addVariable": "Dəyişən əlavə edin", + "keyLabel": "Açar", + "keyTooLong": "Açar 256 simvol və ya daha az olmalıdır", + "keyDuplicate": "Bu açar artıq profildə mövcuddur", + "valueLabel": "Dəyər", + "secretLabel": "Gizli", + "secrets": { + "markSecret": "Gizli olaraq qeyd edin", + "masked": "Gizli dəyər", + "reveal": "Dəyəri aşkar etmək", + "newValuePlaceholder": "Yeni gizli dəyər daxil edin", + "valuePlaceholder": "Gizli dəyər (şifrələnmiş)" + }, + "variablesEmpty": "Hələ heç bir dəyişən yoxdur", + "variablesSaveFailed": "Dəyişən yadda saxlamaq mümkün olmadı", + "commandsTitle": "Quraşdırma əmrləri", + "addCommand": "Komanda əlavə edin", + "commandLabel": "Əmr", + "commandPlaceholder": "e.g. pnpm install", + "commandsEmpty": "Hələ quraşdırma əmrləri yoxdur", + "commandsSaveFailed": "Quraşdırma əmrlərini saxlamaq mümkün olmadı", + "moveUp": "Yuxarı hərəkət edin", + "moveDown": "Aşağı hərəkət edin", + "skillsTitle": "Bacarıqlar", + "addSkill": "Bacarıq əlavə edin", + "skillNameLabel": "Bacarıq adı", + "skillMarkdownLabel": "Bacarıq məzmunu", + "skillsEmpty": "Hələ bacarıq yoxdur", + "skillNameRequired": "Bacarıq adı daxil edin", + "skillNameInvalid": "Bacarıq adı hərf və ya rəqəmlə başlamalı və yalnız kiçik hərflər, rəqəmlər və tirelərdən istifadə etməlidir", + "skillContentRequired": "Bacarıq məzmunu tələb olunur", + "skillSaveFailed": "Bacarıq saxlamaq mümkün olmadı", + "skillsHint": "SKILL.md yapışdırın və onun adı doldurulur; sürükləyib buraxmadan telefonunuzdan bacarıq əlavə edin.", + "skillEditTitle": "Bacarıqları redaktə edin", + "mcp": { + "title": "MCP serverləri", + "add": "MCP server əlavə edin", + "empty": "Hələ MCP serverləri yoxdur", + "name": "Server adı", + "type": "Server növü", + "localType": "yerli", + "remoteType": "Uzaqdan", + "command": "Komanda xətti", + "url": "URL", + "environment": "Ətraf mühit (JSON)", + "headers": "Başlıqlar (JSON)", + "timeout": "Taymout", + "secretCount": "Gizli hesab", + "namePlaceholder": "my-server", + "commandPlaceholder": "npx @example/mcp-server", + "jsonPlaceholder": "{\n \"API_KEY\": \"sk-...\"\n}", + "nameRequired": "Server adını daxil edin", + "nameInvalid": "Ad yalnız hərflərdən, rəqəmlərdən, alt xəttlərdən və tirelərdən istifadə edə bilər", + "commandRequired": "Komanda daxil edin", + "commandTooLong": "Ən çoxu 500 simvoldan ibarət 50 arqumentdən istifadə edin", + "urlRequired": "URL daxil edin", + "urlInvalid": "Etibarlı URL daxil edin", + "urlTooLong": "URL 2048 simvol və ya daha az olmalıdır", + "jsonInvalid": "Etibarlı JSON daxil edin — sətir dəyərlərinin obyekti", + "jsonTooLarge": "Ən çoxu 50 giriş, 128-ə qədər açar və 4096 simvola qədər dəyər istifadə edin", + "timeoutInvalid": "Taymout tam sayda millisaniyə olmalıdır", + "edit": "MCP serverini redaktə edin", + "saveFailed": "MCP serverini saxlamaq mümkün olmadı" + }, + "agents": { + "title": "Profil agentləri", + "add": "Agent əlavə edin", + "empty": "Hələ agent yoxdur", + "name": "Agent adı", + "slug": "Agent şlak", + "description": "Agentin təsviri", + "model": "Modelin ləğvi", + "modelPlaceholder": "Defolt sessiyadan istifadə edin", + "prompt": "Sistem sorğusu", + "steps": "Maks. addımlar", + "stepsPlaceholder": "məs. 50", + "temperature": "Temperatur", + "temperaturePlaceholder": "məs. 0.2", + "topP": "top_p", + "topPPlaceholder": "məs. 0.95", + "slugPlaceholder": "my-agent", + "namePlaceholder": "Mənim Agentim", + "promptPlaceholder": "Siz bir...", + "tools": "Alətlər", + "visibility": "Görünüş", + "visibilityPrimary": "Əsas — seçicidə göstərilir", + "visibilitySubagent": "Subagent — yalnız başqalarından zəng etmək olar", + "visibilityAll": "Hamısı - hər ikisi", + "saveFailed": "Agenti saxlamaq mümkün olmadı", + "nameRequired": "Agent adını daxil edin", + "slugRequired": "Agent slug daxil edin", + "slugInvalid": "Slug hərflə başlamalı və yalnız kiçik hərflərdən, rəqəmlərdən və tirelərdən istifadə etməlidir", + "slugConflict": "Bu şlak daxili agent tərəfindən qorunur", + "edit": "Agenti redaktə edin" + }, + "slashCommands": { + "title": "Slash əmrləri", + "add": "Slash əmri əlavə edin", + "empty": "Hələ slash əmrləri yoxdur", + "name": "Komanda adı", + "description": "Komandanın təsviri", + "template": "Şablon", + "namePlaceholder": "explain", + "templatePlaceholder": "$ARGUMENTS-i izah edin", + "agent": "Agent", + "subtask": "Alt tapşırıq kimi işləyin", + "edit": "Slash əmrini redaktə edin", + "saveFailed": "Komandanı saxlamaq mümkün olmadı", + "nameRequired": "Komanda adını daxil edin", + "nameInvalid": "Ad kiçik hərflə başlamalı və yalnız kiçik hərflərdən, rəqəmlərdən və tirelərdən istifadə etməlidir", + "templateRequired": "Komanda şablonu daxil edin", + "nameConflict": "Bu ad daxili əmrlə qorunur" + }, + "repoBindings": { + "title": "Repos üçün defolt profillər", + "add": "Defolt əlavə edin", + "empty": "Defolt konfiqurasiya edilməyib", + "emptyDescription": "Repo üçün sessiyalara başladıqda profili avtomatik tətbiq etmək üçün defolt əlavə edin.", + "repo": "Repozitoru seçin...", + "profile": "Profil seçin", + "remove": "Açın", + "private": "Şəxsi", + "search": "Repozitoriyaları axtarın...", + "addedToast": "\"{{repo}}\" üçün defolt profil ayarlandı", + "addFailed": "Defolt profili qurmaq mümkün olmadı", + "removedToast": "\"{{repo}}\" üçün defolt profil silindi", + "removeFailed": "Defolt profili silmək mümkün olmadı", + "pinnedTitle": "Repozitorlara bərkidildi", + "pinRepo": "Repo bağlayın", + "pinnedEmpty": "Heç bir depoya bağlanmayıb.", + "pinnedDescription": "Sessiya bu depolardan birini hədəf aldıqda avtomatik yüklənir.", + "pinnedToast": "\"{{repo}}\" üzərinə bərkidildi", + "pinFailed": "Repo bağlamaq mümkün olmadı", + "unboundToast": "Repo bağlanmamışdır", + "unbindFailed": "Repo bağlamaq mümkün olmadı" + } } } diff --git a/apps/mobile/src/i18n/locales/be.json b/apps/mobile/src/i18n/locales/be.json index c89e1900425..9a7f44bd8cd 100644 --- a/apps/mobile/src/i18n/locales/be.json +++ b/apps/mobile/src/i18n/locales/be.json @@ -2247,7 +2247,33 @@ "continueFrom": "Працягваецца сеанс «{{title}}».", "importingSession": "Імпартаванне сеанса", "cliCannotContinue": "Гэты CLI не падтрымлівае працяг сеанса. Выберыце Cloud Agent або іншы CLI.", - "importFailed": "Не ўдалося імпартаваць гэты сеанс." + "importFailed": "Не ўдалося імпартаваць гэты сеанс.", + "pickProfile": "Выберыце профіль", + "noProfile": "Няма профілю", + "manageProfiles": "Кіраванне профілямі", + "noProfiles": "Профіляў пакуль няма. Стварыце яго, каб дадаць зменныя асяроддзя, серверы MCP і навыкі.", + "advancedConfig": "Пашыраная канфігурацыя", + "advancedConfigDescription": "Выберыце профіль для загрузкі захаваных канфігурацый і ўручную наладзьце дадатковыя зменныя асяроддзя і каманды ўстаноўкі", + "saveAsProfile": "Захаваць як профіль", + "saveProfileDescription": "Захавайце бягучыя зменныя асяроддзя і каманды наладкі як шматразовы профіль.", + "profileSummary": "{{vars}} зменныя асяроддзя · {{commands}} каманды наладкі", + "profileSaved": "Профіль \"{{name}}\" захаваны", + "organizationProfiles": "Профілі арганізацыі", + "personalProfiles": "Асабістыя профілі", + "yourProfiles": "Вашы профілі", + "failedToLoadProfiles": "Не ўдалося загрузіць профілі", + "nameTooLong": "Імя профілю занадта доўгае", + "descriptionTooLong": "Апісанне занадта доўгае", + "secretsEncrypted": "Сакрэты шыфруюцца перад захаваннем.", + "profileActive": "Профіль актыўны", + "profilesActive": "Профілі актыўныя", + "customConfigActive": "Карыстальніцкая канфігурацыя актыўная", + "configNeedsAttention": "Канфігурацыя патрабуе ўвагі", + "repoProfile": "Профіль РЭПО", + "selectedProfile": "Абраны профіль", + "manualOverrides": "Ручныя перавызначэнні", + "openSettingsToReview": "Адкрыйце налады для прагляду", + "profileSelection": "Выбар профілю" }, "partRenderer": { "updatedFileCount_one": "Абноўлены {{displayCount}} файл.", @@ -3310,5 +3336,180 @@ "multiplierError": "Увядзіце множнік 1 або больш", "permissionDenied": "У вас няма дазволу кіраваць апавяшчэннямі аб выдатаках.", "pushOffByCategory": "Выключана ў апавяшчэннях" + }, + "profiles": { + "title": "Профілі", + "entrySubtitle": "Паўторнае выкарыстанне налад асяроддзя ў сеансах", + "newProfile": "Новы профіль", + "emptyTitle": "Профіляў пакуль няма", + "emptyDescription": "Стварыце профіль для паўторнага выкарыстання зменных асяроддзя, каманд наладкі і навыкаў у сеансах.", + "loadFailed": "Не ўдалося загрузіць профілі", + "counts": { + "vars": "{{count}} зменных", + "mcp": "{{count}} MCP", + "skills": "{{count}} навыкаў", + "commands": "{{count}} каманд", + "varsShort": "{{count}}з", + "skillsShort": "{{count}}н", + "commandsShort": "{{count}}к" + }, + "nameLabel": "Імя профілю", + "namePlaceholder": "напр. Бэкэнд-адладка", + "descriptionLabel": "Апісанне профілю", + "descriptionPlaceholder": "Неабавязковае апісанне", + "ownerLabel": "Уладальнік профілю", + "nameRequired": "Увядзіце назву профілю", + "createAction": "Стварыць профіль", + "createFailed": "Не ўдалося стварыць профіль", + "createdToast": "Профіль \"{{name}}\" створаны", + "settingsSectionTitle": "Канфігурацыя", + "defaultSectionTitle": "Стандартны профіль", + "personalDefaultDescription": "Аўтаматычна загружаецца, калі ні ў адным сховішчы няма замацаванага профілю.", + "organizationDefaultDescription": "Аўтаматычна загружаецца для ўдзельнікаў, якія не маюць асабістых значэнняў па змаўчанні.", + "setAsDefault": "Усталяваць па змаўчанні", + "removeDefault": "Выдаліць па змаўчанні", + "saveFailed": "Не ўдалося захаваць профіль", + "deleteTitle": "Выдаліць профіль", + "deleteMessage": "Гэта назаўжды выдаляе профіль і яго налады. Гэта нельга адмяніць.", + "deleteFailed": "Не ўдалося выдаліць профіль", + "deleteBlocked": "Гэты профіль выкарыстоўваецца трыгерам webhook. Спачатку выдаліце яго з гэтых трыгераў.", + "deletedToast": "Профіль \"{{name}}\" выдалены", + "variablesTitle": "Зменныя асяроддзя", + "addVariable": "Дадаць зменную", + "keyLabel": "ключ", + "keyTooLong": "Ключ павінен змяшчаць не больш за 256 сімвалаў", + "keyDuplicate": "Гэты ключ ужо існуе ў профілі", + "valueLabel": "Каштоўнасць", + "secretLabel": "сакрэт", + "secrets": { + "markSecret": "Пазначыць як сакрэтнае", + "masked": "Схаваная каштоўнасць", + "reveal": "Раскрыйце значэнне", + "newValuePlaceholder": "Увядзіце новае сакрэтнае значэнне", + "valuePlaceholder": "Сакрэтнае значэнне (зашыфраванае)" + }, + "variablesEmpty": "Зменных пакуль няма", + "variablesSaveFailed": "Не ўдалося захаваць зменную", + "commandsTitle": "Каманды наладкі", + "addCommand": "Дадаць каманду", + "commandLabel": "Камандаванне", + "commandPlaceholder": "e.g. pnpm install", + "commandsEmpty": "Пакуль няма каманд наладкі", + "commandsSaveFailed": "Не ўдалося захаваць каманды наладкі", + "moveUp": "Рухайцеся ўверх", + "moveDown": "Рухайцеся ўніз", + "skillsTitle": "Навыкі", + "addSkill": "Дадаць майстэрства", + "skillNameLabel": "Назва навыку", + "skillMarkdownLabel": "Змест навыкаў", + "skillsEmpty": "Пакуль няма навыкаў", + "skillNameRequired": "Увядзіце назву навыку", + "skillNameInvalid": "Назва навыку павінна пачынацца з літары або лічбы і выкарыстоўваць толькі малыя літары, лічбы і працяжнікі", + "skillContentRequired": "Змест навыкаў патрабуецца", + "skillSaveFailed": "Не ўдалося захаваць навык", + "skillsHint": "Устаўце SKILL.md і запоўніце яго імя; дадаць навык са свайго тэлефона без перацягвання.", + "skillEditTitle": "Майстэрства рэдагавання", + "mcp": { + "title": "MCP серверы", + "add": "Дадаць сервер MCP", + "empty": "Сервераў MCP пакуль няма", + "name": "Імя сервера", + "type": "Тып сервера", + "localType": "Мясцовы", + "remoteType": "Дыстанцыйнае", + "command": "Камандны радок", + "url": "URL", + "environment": "Асяроддзе (JSON)", + "headers": "Загалоўкі (JSON)", + "timeout": "Тайм-аўт", + "secretCount": "Сакрэтны падлік", + "namePlaceholder": "my-server", + "commandPlaceholder": "npx @example/mcp-server", + "jsonPlaceholder": "{\n \"API_KEY\": \"sk-...\"\n}", + "nameRequired": "Увядзіце імя сервера", + "nameInvalid": "У назве можна выкарыстоўваць толькі літары, лічбы, сімвалы падкрэслівання і працяжнікі", + "commandRequired": "Увядзіце каманду", + "commandTooLong": "Выкарыстайце не больш за 50 аргументаў па 500 сімвалаў кожны", + "urlRequired": "Увядзіце URL", + "urlInvalid": "Увядзіце сапраўдны URL", + "urlTooLong": "URL павінен змяшчаць не больш за 2048 сімвалаў", + "jsonInvalid": "Увядзіце сапраўдны JSON — аб'ект радковых значэнняў", + "jsonTooLarge": "Выкарыстайце не больш за 50 запісаў, ключы да 128 і значэнні да 4096 сімвалаў", + "timeoutInvalid": "Тайм-аўт павінен складаць цэлую колькасць мілісекунд", + "edit": "Рэдагаваць сервер MCP", + "saveFailed": "Не ўдалося захаваць сервер MCP" + }, + "agents": { + "title": "Профільныя агенты", + "add": "Дадаць агента", + "empty": "Агентаў пакуль няма", + "name": "Імя агента", + "slug": "Агент смоўж", + "description": "Апісанне агента", + "model": "Перавызначэнне мадэлі", + "modelPlaceholder": "Выкарыстоўваць сеанс па змаўчанні", + "prompt": "Сістэмная падказка", + "steps": "Макс. крокаў", + "stepsPlaceholder": "напр. 50", + "temperature": "Тэмпература", + "temperaturePlaceholder": "напр. 0.2", + "topP": "top_p", + "topPPlaceholder": "напр. 0.95", + "slugPlaceholder": "my-agent", + "namePlaceholder": "Мой агент", + "promptPlaceholder": "Вы...", + "tools": "інструменты", + "visibility": "Бачнасць", + "visibilityPrimary": "Асноўны — паказваецца ў выбары", + "visibilitySubagent": "Субагент — можна выклікаць толькі ад іншых", + "visibilityAll": "Усё — абодва", + "saveFailed": "Не ўдалося захаваць агента", + "nameRequired": "Увядзіце імя агента", + "slugRequired": "Увядзіце смоўж агента", + "slugInvalid": "Смоўж павінен пачынацца з літары і выкарыстоўваць толькі малыя літары, лічбы і працяжнікі", + "slugConflict": "Гэты смоўж зарэзерваваны ўбудаваным агентам", + "edit": "Агент рэдагавання" + }, + "slashCommands": { + "title": "Каманды касой рысы", + "add": "Дадаць каманду касой рысы", + "empty": "Пакуль няма каманд касой рысы", + "name": "Назва каманды", + "description": "Апісанне каманды", + "template": "Шаблон", + "namePlaceholder": "explain", + "templatePlaceholder": "Растлумачце $ARGUMENTS", + "agent": "Агент", + "subtask": "Запуск як падзадача", + "edit": "Рэдагаваць каманду касой рысы", + "saveFailed": "Не ўдалося захаваць каманду", + "nameRequired": "Увядзіце назву каманды", + "nameInvalid": "Імя павінна пачынацца з малой літары і выкарыстоўваць толькі малыя літары, лічбы і працяжнікі", + "templateRequired": "Увядзіце шаблон каманды", + "nameConflict": "Гэта імя зарэзервавана ўбудаванай камандай" + }, + "repoBindings": { + "title": "Профілі па змаўчанні для рэпазітараў", + "add": "Дадаць па змаўчанні", + "empty": "Стандартныя параметры не настроены", + "emptyDescription": "Дадайце стандартнае для аўтаматычнага прымянення профілю пры запуску сеансаў для рэпазітара.", + "repo": "Выберыце сховішча…", + "profile": "Выберыце профіль", + "remove": "Развязаць", + "private": "Прыватны", + "search": "Пошук у сховішчах…", + "addedToast": "Стандартны профіль усталяваны для \"{{repo}}\"", + "addFailed": "Немагчыма ўсталяваць профіль па змаўчанні", + "removedToast": "Стандартны профіль выдалены для \"{{repo}}\"", + "removeFailed": "Не ўдалося выдаліць стандартны профіль", + "pinnedTitle": "Замацаваны ў сховішчах", + "pinRepo": "Замацаваць рэпазітар", + "pinnedEmpty": "Не замацаваны ні ў якіх сховішчах.", + "pinnedDescription": "Аўтаматычна загружаецца кожны раз, калі сеанс накіраваны на адно з гэтых сховішчаў.", + "pinnedToast": "Замацавана на \"{{repo}}\"", + "pinFailed": "Не ўдалося замацаваць рэпазітар", + "unboundToast": "РЭПО без прывязкі", + "unbindFailed": "Не ўдалося адвязаць рэпазітар" + } } } diff --git a/apps/mobile/src/i18n/locales/bg.json b/apps/mobile/src/i18n/locales/bg.json index 6bbd4a16d6c..83cf705a7dc 100644 --- a/apps/mobile/src/i18n/locales/bg.json +++ b/apps/mobile/src/i18n/locales/bg.json @@ -2191,7 +2191,33 @@ "continueFrom": "Продължаваш сесията {{title}}.", "importingSession": "Импортиране на сесията", "cliCannotContinue": "Този CLI не може да продължи сесия. Избери Cloud Agent или друг CLI.", - "importFailed": "Тази сесия не можа да бъде импортирана." + "importFailed": "Тази сесия не можа да бъде импортирана.", + "pickProfile": "Изберете профил", + "noProfile": "Няма профил", + "manageProfiles": "Управление на профили", + "noProfiles": "Все още няма профили. Създайте такъв, за да добавите променливи на средата, MCP сървъри и умения.", + "advancedConfig": "Разширена конфигурация", + "advancedConfigDescription": "Изберете профил за зареждане на запазени конфигурации и ръчно конфигурирайте допълнителни променливи на средата и команди за настройка", + "saveAsProfile": "Запази като профил", + "saveProfileDescription": "Запазете текущите променливи на средата и командите за настройка като профил за многократна употреба.", + "profileSummary": "{{vars}} променливи на средата · {{commands}} команди за настройка", + "profileSaved": "Профилът „{{name}}“ е запазен", + "organizationProfiles": "Профили на организацията", + "personalProfiles": "Лични профили", + "yourProfiles": "Вашите профили", + "failedToLoadProfiles": "Неуспешно зареждане на профили", + "nameTooLong": "Името на профила е твърде дълго", + "descriptionTooLong": "Описанието е твърде дълго", + "secretsEncrypted": "Тайните се криптират преди съхранение.", + "profileActive": "Профилът е активен", + "profilesActive": "Активни профили", + "customConfigActive": "Персонализираната конфигурация е активна", + "configNeedsAttention": "Конфигурацията изисква внимание", + "repoProfile": "Репо профил", + "selectedProfile": "Избран профил", + "manualOverrides": "Ръчни замени", + "openSettingsToReview": "Отворете Настройки за преглед", + "profileSelection": "Избор на профил" }, "partRenderer": { "updatedFileCount_one": "Актуализиран е {{displayCount}} файл.", @@ -3224,5 +3250,180 @@ "multiplierError": "Въведи множител 1 или повече", "permissionDenied": "Нямаш разрешение да управляваш известията за разходи.", "pushOffByCategory": "Изключено в „Известия“" + }, + "profiles": { + "title": "Профили", + "entrySubtitle": "Повторно използване на настройките на средата в сесиите", + "newProfile": "Нов профил", + "emptyTitle": "Все още няма профили", + "emptyDescription": "Създайте профил за повторно използване на променливи на средата, команди за настройка и умения в сесиите.", + "loadFailed": "Профилите не можаха да се заредят", + "counts": { + "vars": "{{count}} променливи", + "mcp": "{{count}} MCP", + "skills": "{{count}} умения", + "commands": "{{count}} команди", + "varsShort": "{{count}}п", + "skillsShort": "{{count}}у", + "commandsShort": "{{count}}к" + }, + "nameLabel": "Име на профила", + "namePlaceholder": "напр. Отстраняване на грешки в задната част", + "descriptionLabel": "Описание на профила", + "descriptionPlaceholder": "Описание по избор", + "ownerLabel": "Собственик на профил", + "nameRequired": "Въведете име на профил", + "createAction": "Създаване на профил", + "createFailed": "Не можа да се създаде профил", + "createdToast": "Профилът „{{name}}“ е създаден", + "settingsSectionTitle": "Конфигурация", + "defaultSectionTitle": "Профил по подразбиране", + "personalDefaultDescription": "Зарежда се автоматично, когато нито едно хранилище няма фиксиран профил.", + "organizationDefaultDescription": "Зарежда се автоматично за членове, които нямат персонални настройки по подразбиране.", + "setAsDefault": "Задайте по подразбиране", + "removeDefault": "Премахване по подразбиране", + "saveFailed": "Профилът не можа да бъде запазен", + "deleteTitle": "Изтриване на профил", + "deleteMessage": "Това премахва завинаги профила и неговите настройки. Това не може да бъде отменено.", + "deleteFailed": "Профилът не можа да бъде изтрит", + "deleteBlocked": "Този профил се използва от тригер за уеб кукичка. Първо го премахнете от тези тригери.", + "deletedToast": "Профилът „{{name}}“ е изтрит", + "variablesTitle": "Променливи на средата", + "addVariable": "Добавете променлива", + "keyLabel": "Ключ", + "keyTooLong": "Ключът трябва да е 256 знака или по-малко", + "keyDuplicate": "Този ключ вече съществува в профила", + "valueLabel": "Стойност", + "secretLabel": "Тайна", + "secrets": { + "markSecret": "Маркирайте като секретно", + "masked": "Скрита стойност", + "reveal": "Разкриване на стойност", + "newValuePlaceholder": "Въведете нова тайна стойност", + "valuePlaceholder": "Тайна стойност (криптирана)" + }, + "variablesEmpty": "Все още няма променливи", + "variablesSaveFailed": "Променливата не можа да бъде запазена", + "commandsTitle": "Команди за настройка", + "addCommand": "Добавете команда", + "commandLabel": "командване", + "commandPlaceholder": "e.g. pnpm install", + "commandsEmpty": "Все още няма команди за настройка", + "commandsSaveFailed": "Командите за настройка не можаха да бъдат запазени", + "moveUp": "Придвижете се нагоре", + "moveDown": "Придвижете се надолу", + "skillsTitle": "Умения", + "addSkill": "Добавете умение", + "skillNameLabel": "Име на умението", + "skillMarkdownLabel": "Съдържание на умения", + "skillsEmpty": "Все още няма умения", + "skillNameRequired": "Въведете име на умение", + "skillNameInvalid": "Името на умението трябва да започва с буква или цифра и да се използват само малки букви, цифри и тирета", + "skillContentRequired": "Изисква се съдържание за умения", + "skillSaveFailed": "Умението не можа да бъде запазено", + "skillsHint": "Поставете SKILL.md и името му се попълва; добавете умение от телефона си без плъзгане и пускане.", + "skillEditTitle": "Умение за редактиране", + "mcp": { + "title": "MCP сървъри", + "add": "Добавете MCP сървър", + "empty": "Все още няма MCP сървъри", + "name": "Име на сървъра", + "type": "Тип сървър", + "localType": "Местен", + "remoteType": "Дистанционно", + "command": "Команден ред", + "url": "URL адрес", + "environment": "Околна среда (JSON)", + "headers": "Заглавки (JSON)", + "timeout": "Време за изчакване", + "secretCount": "Тайно броене", + "namePlaceholder": "my-server", + "commandPlaceholder": "npx @example/mcp-server", + "jsonPlaceholder": "{\n \"API_KEY\": \"sk-...\"\n}", + "nameRequired": "Въведете име на сървър", + "nameInvalid": "Името може да съдържа само букви, цифри, долна черта и тирета", + "commandRequired": "Въведете команда", + "commandTooLong": "Използвайте най-много 50 аргумента от по 500 знака всеки", + "urlRequired": "Въведете URL", + "urlInvalid": "Въведете валиден URL", + "urlTooLong": "URL адресът трябва да е 2048 знака или по-малко", + "jsonInvalid": "Въведете валиден JSON — обект от низови стойности", + "jsonTooLarge": "Използвайте най-много 50 записа, ключове до 128 и стойности до 4096 знака", + "timeoutInvalid": "Времето за изчакване трябва да бъде цял брой милисекунди", + "edit": "Редактиране на MCP сървър", + "saveFailed": "MCP сървърът не можа да бъде запазен" + }, + "agents": { + "title": "Профилни агенти", + "add": "Добавете агент", + "empty": "Все още няма агенти", + "name": "Име на агент", + "slug": "Агент плужек", + "description": "Описание на агента", + "model": "Отмяна на модела", + "modelPlaceholder": "Използване на сесия по подразбиране", + "prompt": "Системна подкана", + "steps": "Макс. стъпки", + "stepsPlaceholder": "напр. 50", + "temperature": "Температура", + "temperaturePlaceholder": "напр. 0.2", + "topP": "top_p", + "topPPlaceholder": "напр. 0.95", + "slugPlaceholder": "my-agent", + "namePlaceholder": "Моят агент", + "promptPlaceholder": "ти си...", + "tools": "Инструменти", + "visibility": "Видимост", + "visibilityPrimary": "Основен — показва се в инструмента за избор", + "visibilitySubagent": "Субагент — може да се извиква само от други", + "visibilityAll": "Всички - и двете", + "saveFailed": "Агентът не можа да бъде запазен", + "nameRequired": "Въведете име на агент", + "slugRequired": "Въведете агент slug", + "slugInvalid": "Slug трябва да започва с буква и да използва само малки букви, цифри и тирета", + "slugConflict": "Този охлюв е запазен от вграден агент", + "edit": "Редактиране на агент" + }, + "slashCommands": { + "title": "Команди за наклонена черта", + "add": "Добавете команда за наклонена черта", + "empty": "Все още няма команди за наклонена черта", + "name": "Име на командата", + "description": "Описание на командата", + "template": "Шаблон", + "namePlaceholder": "explain", + "templatePlaceholder": "Обяснете $ARGUMENTS", + "agent": "агент", + "subtask": "Изпълнение като подзадача", + "edit": "Команда за редактиране на наклонена черта", + "saveFailed": "Командата не можа да бъде запазена", + "nameRequired": "Въведете име на команда", + "nameInvalid": "Името трябва да започва с малка буква и да се използват само малки букви, цифри и тирета", + "templateRequired": "Въведете шаблон за команда", + "nameConflict": "Това име е запазено от вградена команда" + }, + "repoBindings": { + "title": "Профили по подразбиране за Repos", + "add": "Добавете по подразбиране", + "empty": "Няма конфигурирани настройки по подразбиране", + "emptyDescription": "Добавете настройка по подразбиране за автоматично прилагане на профил при стартиране на сесии за репо.", + "repo": "Изберете хранилище...", + "profile": "Изберете профил", + "remove": "Развържете", + "private": "Частно", + "search": "Търсене в хранилища...", + "addedToast": "Профилът по подразбиране е зададен за \"{{repo}}\"", + "addFailed": "Профилът по подразбиране не можа да бъде зададен", + "removedToast": "Профилът по подразбиране е премахнат за „{{repo}}“", + "removeFailed": "Профилът по подразбиране не можа да бъде премахнат", + "pinnedTitle": "Фиксирано към хранилища", + "pinRepo": "Закачете репо", + "pinnedEmpty": "Не е фиксиран към никакви хранилища.", + "pinnedDescription": "Зарежда се автоматично, когато дадена сесия е насочена към едно от тези хранилища.", + "pinnedToast": "Фиксирано към „{{repo}}“", + "pinFailed": "Репото не можа да бъде фиксирано", + "unboundToast": "Репо без обвързване", + "unbindFailed": "Не можа да се откачи репото" + } } } diff --git a/apps/mobile/src/i18n/locales/bn.json b/apps/mobile/src/i18n/locales/bn.json index f990a858352..2d3da763b59 100644 --- a/apps/mobile/src/i18n/locales/bn.json +++ b/apps/mobile/src/i18n/locales/bn.json @@ -2191,7 +2191,33 @@ "continueFrom": "{{title}} চালিয়ে যাওয়া হচ্ছে।", "importingSession": "সেশন ইমপোর্ট হচ্ছে", "cliCannotContinue": "এই CLI দিয়ে আগের সেশন চালিয়ে যাওয়া যায় না। Cloud Agent বা অন্য একটি CLI বেছে নিন।", - "importFailed": "এই সেশনটি ইমপোর্ট করা যায়নি।" + "importFailed": "এই সেশনটি ইমপোর্ট করা যায়নি।", + "pickProfile": "একটি প্রোফাইল বাছাই করুন", + "noProfile": "কোনো প্রোফাইল নেই", + "manageProfiles": "প্রোফাইল পরিচালনা করুন", + "noProfiles": "এখনো কোনো প্রোফাইল নেই. পরিবেশ ভেরিয়েবল, MCP সার্ভার এবং দক্ষতা যোগ করতে একটি তৈরি করুন।", + "advancedConfig": "উন্নত কনফিগারেশন", + "advancedConfigDescription": "সংরক্ষিত কনফিগারেশন লোড করতে একটি প্রোফাইল নির্বাচন করুন এবং অতিরিক্ত পরিবেশ ভেরিয়েবল এবং সেটআপ কমান্ড ম্যানুয়ালি কনফিগার করুন", + "saveAsProfile": "প্রোফাইল হিসাবে সংরক্ষণ করুন", + "saveProfileDescription": "একটি পুনঃব্যবহারযোগ্য প্রোফাইল হিসাবে বর্তমান পরিবেশ ভেরিয়েবল এবং সেটআপ কমান্ড সংরক্ষণ করুন।", + "profileSummary": "{{vars}} পরিবেশ ভেরিয়েবল · {{commands}} সেটআপ কমান্ড", + "profileSaved": "প্রোফাইল \"{{name}}\" সংরক্ষিত", + "organizationProfiles": "প্রতিষ্ঠানের প্রোফাইল", + "personalProfiles": "ব্যক্তিগত প্রোফাইল", + "yourProfiles": "আপনার প্রোফাইল", + "failedToLoadProfiles": "প্রোফাইল লোড করতে ব্যর্থ হয়েছে", + "nameTooLong": "প্রোফাইল নামটি অনেক বড়৷", + "descriptionTooLong": "বর্ণনা খুব দীর্ঘ", + "secretsEncrypted": "গোপনীয়তা স্টোরেজ আগে এনক্রিপ্ট করা হয়.", + "profileActive": "প্রোফাইল সক্রিয়", + "profilesActive": "প্রোফাইল সক্রিয়", + "customConfigActive": "কাস্টম কনফিগারেশন সক্রিয়", + "configNeedsAttention": "কনফিগারেশন মনোযোগ প্রয়োজন", + "repoProfile": "রেপো প্রোফাইল", + "selectedProfile": "নির্বাচিত প্রোফাইল", + "manualOverrides": "ম্যানুয়াল ওভাররাইড", + "openSettingsToReview": "পর্যালোচনা করতে সেটিংস খুলুন", + "profileSelection": "প্রোফাইল নির্বাচন" }, "partRenderer": { "updatedFileCount_one": "{{displayCount}}টি ফাইল আপডেট হয়েছে।", @@ -3224,5 +3250,180 @@ "multiplierError": "1 বা তার বেশি গুণক লিখুন", "permissionDenied": "খরচের সতর্কতা পরিচালনার অনুমতি আপনার নেই।", "pushOffByCategory": "বিজ্ঞপ্তিতে বন্ধ" + }, + "profiles": { + "title": "প্রোফাইল", + "entrySubtitle": "সেশন জুড়ে পরিবেশ সেটিংস পুনরায় ব্যবহার করুন", + "newProfile": "নতুন প্রোফাইল", + "emptyTitle": "এখনো কোনো প্রোফাইল নেই", + "emptyDescription": "সেশন জুড়ে পরিবেশ ভেরিয়েবল, সেটআপ কমান্ড এবং দক্ষতা পুনরায় ব্যবহার করার জন্য একটি প্রোফাইল তৈরি করুন।", + "loadFailed": "প্রোফাইল লোড করা যায়নি", + "counts": { + "vars": "{{count}} ভেরিয়েবল", + "mcp": "{{count}} MCP", + "skills": "{{count}} দক্ষতা", + "commands": "{{count}} কমান্ড", + "varsShort": "{{count}}ভ", + "skillsShort": "{{count}}দ", + "commandsShort": "{{count}}ক" + }, + "nameLabel": "প্রোফাইল নাম", + "namePlaceholder": "যেমন ব্যাকএন্ড ডিবাগিং", + "descriptionLabel": "প্রোফাইল বিবরণ", + "descriptionPlaceholder": "ঐচ্ছিক বর্ণনা", + "ownerLabel": "প্রোফাইল মালিক", + "nameRequired": "একটি প্রোফাইল নাম লিখুন", + "createAction": "প্রোফাইল তৈরি করুন", + "createFailed": "প্রোফাইল তৈরি করা যায়নি", + "createdToast": "প্রোফাইল \"{{name}}\" তৈরি করা হয়েছে৷", + "settingsSectionTitle": "কনফিগারেশন", + "defaultSectionTitle": "ডিফল্ট প্রোফাইল", + "personalDefaultDescription": "কোনো সংগ্রহস্থলে পিন করা প্রোফাইল না থাকলে স্বয়ংক্রিয়ভাবে লোড হয়।", + "organizationDefaultDescription": "কোন ব্যক্তিগত ডিফল্ট নেই যারা সদস্যদের জন্য স্বয়ংক্রিয় লোড.", + "setAsDefault": "ডিফল্ট হিসেবে সেট করুন", + "removeDefault": "ডিফল্ট হিসাবে সরান", + "saveFailed": "প্রোফাইল সংরক্ষণ করা যায়নি", + "deleteTitle": "প্রোফাইল মুছুন", + "deleteMessage": "এটি স্থায়ীভাবে প্রোফাইল এবং এর সেটিংস সরিয়ে দেয়। এটি পূর্বাবস্থায় ফেরানো যাবে না।", + "deleteFailed": "প্রোফাইল মুছে ফেলা যায়নি", + "deleteBlocked": "এই প্রোফাইলটি একটি ওয়েবহুক ট্রিগার দ্বারা ব্যবহৃত হয়৷ প্রথমে যারা ট্রিগার থেকে এটি সরান.", + "deletedToast": "প্রোফাইল \"{{name}}\" মুছে ফেলা হয়েছে৷", + "variablesTitle": "পরিবেশ পরিবর্তনশীল", + "addVariable": "পরিবর্তনশীল যোগ করুন", + "keyLabel": "চাবি", + "keyTooLong": "কী 256টি অক্ষর বা তার কম হতে হবে", + "keyDuplicate": "এই কীটি ইতিমধ্যে প্রোফাইলে বিদ্যমান", + "valueLabel": "মান", + "secretLabel": "গোপন", + "secrets": { + "markSecret": "গোপন হিসাবে চিহ্নিত করুন", + "masked": "লুকানো মান", + "reveal": "মান প্রকাশ করুন", + "newValuePlaceholder": "নতুন গোপন মান লিখুন", + "valuePlaceholder": "গোপন মান (এনক্রিপ্ট করা)" + }, + "variablesEmpty": "এখনও কোন পরিবর্তনশীল", + "variablesSaveFailed": "ভেরিয়েবল সংরক্ষণ করা যায়নি", + "commandsTitle": "সেটআপ কমান্ড", + "addCommand": "কমান্ড যোগ করুন", + "commandLabel": "আদেশ", + "commandPlaceholder": "e.g. pnpm install", + "commandsEmpty": "এখনো কোনো সেটআপ কমান্ড নেই", + "commandsSaveFailed": "সেটআপ কমান্ড সংরক্ষণ করা যায়নি", + "moveUp": "উপরে সরান", + "moveDown": "নিচে সরান", + "skillsTitle": "দক্ষতা", + "addSkill": "দক্ষতা যোগ করুন", + "skillNameLabel": "দক্ষতার নাম", + "skillMarkdownLabel": "দক্ষতা বিষয়বস্তু", + "skillsEmpty": "এখনো কোনো দক্ষতা নেই", + "skillNameRequired": "একটি দক্ষতার নাম লিখুন", + "skillNameInvalid": "দক্ষতার নাম অবশ্যই একটি অক্ষর বা অঙ্ক দিয়ে শুরু করতে হবে এবং শুধুমাত্র ছোট হাতের অক্ষর, সংখ্যা এবং ড্যাশ ব্যবহার করতে হবে", + "skillContentRequired": "দক্ষতা বিষয়বস্তু প্রয়োজন", + "skillSaveFailed": "দক্ষতা বাঁচাতে পারেনি", + "skillsHint": "একটি SKILL.md পেস্ট করুন এবং এর নামটি পূরণ করুন; ড্র্যাগ এবং ড্রপ ছাড়াই আপনার ফোন থেকে একটি দক্ষতা যোগ করুন।", + "skillEditTitle": "দক্ষতা সম্পাদনা করুন", + "mcp": { + "title": "MCP সার্ভার", + "add": "MCP সার্ভার যোগ করুন", + "empty": "এখনও কোন MCP সার্ভার নেই", + "name": "সার্ভারের নাম", + "type": "সার্ভারের ধরন", + "localType": "স্থানীয়", + "remoteType": "দূরবর্তী", + "command": "কমান্ড লাইন", + "url": "URL", + "environment": "পরিবেশ (JSON)", + "headers": "হেডার (JSON)", + "timeout": "টাইমআউট", + "secretCount": "গোপন গণনা", + "namePlaceholder": "my-server", + "commandPlaceholder": "npx @example/mcp-server", + "jsonPlaceholder": "{\n \"API_KEY\": \"sk-...\"\n}", + "nameRequired": "একটি সার্ভারের নাম লিখুন", + "nameInvalid": "নাম শুধুমাত্র অক্ষর, সংখ্যা, আন্ডারস্কোর এবং ড্যাশ ব্যবহার করতে পারে", + "commandRequired": "একটি কমান্ড লিখুন", + "commandTooLong": "সর্বোচ্চ 50টি আর্গুমেন্ট ব্যবহার করুন, প্রতিটি 500টি অক্ষরের", + "urlRequired": "একটি URL লিখুন", + "urlInvalid": "একটি বৈধ URL লিখুন", + "urlTooLong": "URL অবশ্যই 2048টি অক্ষর বা তার কম হতে হবে", + "jsonInvalid": "বৈধ JSON লিখুন - স্ট্রিং মানের একটি বস্তু", + "jsonTooLarge": "সর্বোচ্চ 50টি এন্ট্রি ব্যবহার করুন, কী 128টি পর্যন্ত এবং মান 4096টি অক্ষর পর্যন্ত", + "timeoutInvalid": "টাইমআউট অবশ্যই মিলিসেকেন্ডের পূর্ণ সংখ্যা হতে হবে", + "edit": "MCP সার্ভার সম্পাদনা করুন", + "saveFailed": "MCP সার্ভার সংরক্ষণ করা যায়নি" + }, + "agents": { + "title": "প্রোফাইল এজেন্ট", + "add": "এজেন্ট যোগ করুন", + "empty": "এখনো কোনো এজেন্ট নেই", + "name": "এজেন্টের নাম", + "slug": "এজেন্ট স্লাগ", + "description": "এজেন্ট বিবরণ", + "model": "মডেল ওভাররাইড", + "modelPlaceholder": "সেশন ডিফল্ট ব্যবহার করুন", + "prompt": "সিস্টেম প্রম্পট", + "steps": "সর্বোচ্চ ধাপ", + "stepsPlaceholder": "যেমন 50", + "temperature": "তাপমাত্রা", + "temperaturePlaceholder": "যেমন 0.2", + "topP": "top_p", + "topPPlaceholder": "যেমন 0.95", + "slugPlaceholder": "my-agent", + "namePlaceholder": "আমার এজেন্ট", + "promptPlaceholder": "আপনি একটি...", + "tools": "টুলস", + "visibility": "দৃশ্যমানতা", + "visibilityPrimary": "প্রাথমিক — পিকারে দেখানো হয়েছে", + "visibilitySubagent": "Subagent — শুধুমাত্র অন্যদের থেকে কলযোগ্য", + "visibilityAll": "সব — উভয়", + "saveFailed": "এজেন্ট সংরক্ষণ করতে পারেনি", + "nameRequired": "একটি এজেন্ট নাম লিখুন", + "slugRequired": "একটি এজেন্ট স্লাগ লিখুন", + "slugInvalid": "স্লাগ একটি অক্ষর দিয়ে শুরু করতে হবে এবং শুধুমাত্র ছোট হাতের অক্ষর, সংখ্যা এবং ড্যাশ ব্যবহার করতে হবে", + "slugConflict": "যে স্লাগ একটি অন্তর্নির্মিত এজেন্ট দ্বারা সংরক্ষিত হয়", + "edit": "এজেন্ট সম্পাদনা করুন" + }, + "slashCommands": { + "title": "স্ল্যাশ কমান্ড", + "add": "স্ল্যাশ কমান্ড যোগ করুন", + "empty": "এখনও কোন স্ল্যাশ কমান্ড নেই", + "name": "কমান্ডের নাম", + "description": "কমান্ডের বিবরণ", + "template": "টেমপ্লেট", + "namePlaceholder": "explain", + "templatePlaceholder": "$ARGUMENTS ব্যাখ্যা করুন", + "agent": "এজেন্ট", + "subtask": "সাবটাস্ক হিসাবে চালান", + "edit": "স্ল্যাশ কমান্ড সম্পাদনা করুন", + "saveFailed": "কমান্ড সংরক্ষণ করা যায়নি", + "nameRequired": "একটি কমান্ডের নাম লিখুন", + "nameInvalid": "নাম অবশ্যই ছোট হাতের অক্ষর দিয়ে শুরু করতে হবে এবং শুধুমাত্র ছোট হাতের অক্ষর, সংখ্যা এবং ড্যাশ ব্যবহার করতে হবে", + "templateRequired": "একটি কমান্ড টেমপ্লেট লিখুন", + "nameConflict": "সেই নামটি একটি অন্তর্নির্মিত কমান্ড দ্বারা সংরক্ষিত" + }, + "repoBindings": { + "title": "Repos এর জন্য ডিফল্ট প্রোফাইল", + "add": "ডিফল্ট যোগ করুন", + "empty": "কোন ডিফল্ট কনফিগার করা", + "emptyDescription": "একটি রেপোর জন্য সেশন শুরু করার সময় স্বয়ংক্রিয়ভাবে একটি প্রোফাইল প্রয়োগ করতে একটি ডিফল্ট যোগ করুন।", + "repo": "সংগ্রহস্থল নির্বাচন করুন...", + "profile": "প্রোফাইল নির্বাচন করুন", + "remove": "আবদ্ধ করুন", + "private": "ব্যক্তিগত", + "search": "সংগ্রহস্থল অনুসন্ধান করুন...", + "addedToast": "\"{{repo}}\" এর জন্য ডিফল্ট প্রোফাইল সেট করা হয়েছে", + "addFailed": "ডিফল্ট প্রোফাইল সেট করা যায়নি", + "removedToast": "\"{{repo}}\" এর জন্য ডিফল্ট প্রোফাইল সরানো হয়েছে", + "removeFailed": "ডিফল্ট প্রোফাইল সরানো যায়নি", + "pinnedTitle": "সংগ্রহস্থলগুলিতে পিন করা হয়েছে৷", + "pinRepo": "একটি রেপো পিন করুন", + "pinnedEmpty": "কোনো সংগ্রহস্থলে পিন করা নেই।", + "pinnedDescription": "যখনই একটি সেশন এই সংগ্রহস্থলগুলির একটিকে লক্ষ্য করে তখনই স্বয়ংক্রিয়ভাবে লোড হয়।", + "pinnedToast": "\"{{repo}}\" এ পিন করা হয়েছে", + "pinFailed": "রেপো পিন করা যায়নি", + "unboundToast": "রেপো আনবাউন্ড", + "unbindFailed": "রেপো আনবাইন্ড করা যায়নি" + } } } diff --git a/apps/mobile/src/i18n/locales/bs.json b/apps/mobile/src/i18n/locales/bs.json index 045e30f040c..90e88d99869 100644 --- a/apps/mobile/src/i18n/locales/bs.json +++ b/apps/mobile/src/i18n/locales/bs.json @@ -2219,7 +2219,33 @@ "continueFrom": "Nastavlja se sesija {{title}}.", "importingSession": "Uvoz sesije", "cliCannotContinue": "Ovaj CLI ne može nastaviti sesiju. Odaberi Cloud Agent ili drugi CLI.", - "importFailed": "Nije moguće uvesti ovu sesiju." + "importFailed": "Nije moguće uvesti ovu sesiju.", + "pickProfile": "Odaberite profil", + "noProfile": "Nema profila", + "manageProfiles": "Upravljajte profilima", + "noProfiles": "Još nema profila. Napravite jedan za dodavanje varijabli okruženja, MCP servera i vještina.", + "advancedConfig": "Napredna konfiguracija", + "advancedConfigDescription": "Odaberite profil za učitavanje sačuvanih konfiguracija i ručno konfigurirajte dodatne varijable okruženja i naredbe za postavljanje", + "saveAsProfile": "Sačuvaj kao profil", + "saveProfileDescription": "Sačuvajte trenutne varijable okruženja i naredbe za podešavanje kao profil za višekratnu upotrebu.", + "profileSummary": "{{vars}} varijable okruženja · {{commands}} naredbe za podešavanje", + "profileSaved": "Profil \"{{name}}\" je sačuvan", + "organizationProfiles": "Profili organizacije", + "personalProfiles": "Lični profili", + "yourProfiles": "Vaši profili", + "failedToLoadProfiles": "Učitavanje profila nije uspjelo", + "nameTooLong": "Ime profila je predugačko", + "descriptionTooLong": "Opis je predugačak", + "secretsEncrypted": "Tajne se šifriraju prije skladištenja.", + "profileActive": "Profil aktivan", + "profilesActive": "Profili aktivni", + "customConfigActive": "Prilagođena konfiguracija aktivna", + "configNeedsAttention": "Konfiguraciji treba obratiti pažnju", + "repoProfile": "Repo profil", + "selectedProfile": "Odabrani profil", + "manualOverrides": "Ručno nadjačavanje", + "openSettingsToReview": "Otvorite Postavke za pregled", + "profileSelection": "Izbor profila" }, "partRenderer": { "updatedFileCount_one": "Ažurirana je {{displayCount}} datoteka.", @@ -3267,5 +3293,180 @@ "multiplierError": "Unesi množilac 1 ili veći", "permissionDenied": "Nemaš dozvolu za upravljanje upozorenjima o potrošnji.", "pushOffByCategory": "Isključeno u Obavijestima" + }, + "profiles": { + "title": "Profili", + "entrySubtitle": "Ponovo koristite postavke okruženja u svim sesijama", + "newProfile": "Novi profil", + "emptyTitle": "Još nema profila", + "emptyDescription": "Kreirajte profil za ponovno korištenje varijabli okruženja, naredbi za postavljanje i vještina u svim sesijama.", + "loadFailed": "Učitavanje profila nije uspjelo", + "counts": { + "vars": "{{count}} varijabli", + "mcp": "{{count}} MCP", + "skills": "{{count}} vještina", + "commands": "{{count}} komandi", + "varsShort": "{{count}}var", + "skillsShort": "{{count}}vje", + "commandsShort": "{{count}}kom" + }, + "nameLabel": "Ime profila", + "namePlaceholder": "npr. Backend debugging", + "descriptionLabel": "Opis profila", + "descriptionPlaceholder": "Opcioni opis", + "ownerLabel": "Vlasnik profila", + "nameRequired": "Unesite naziv profila", + "createAction": "Kreirajte profil", + "createFailed": "Nije moguće kreirati profil", + "createdToast": "Profil \"{{name}}\" kreiran", + "settingsSectionTitle": "Konfiguracija", + "defaultSectionTitle": "Zadani profil", + "personalDefaultDescription": "Automatski se učitava kada nijedno spremište nema zakačeni profil.", + "organizationDefaultDescription": "Automatski se učitava za članove koji nemaju lične postavke.", + "setAsDefault": "Postavi kao zadano", + "removeDefault": "Ukloni kao zadano", + "saveFailed": "Nije moguće sačuvati profil", + "deleteTitle": "Izbriši profil", + "deleteMessage": "Ovo trajno uklanja profil i njegove postavke. Ovo se ne može poništiti.", + "deleteFailed": "Nije moguće izbrisati profil", + "deleteBlocked": "Ovaj profil koristi webhook okidač. Prvo ga uklonite sa tih okidača.", + "deletedToast": "Profil \"{{name}}\" je izbrisan", + "variablesTitle": "Varijable okruženja", + "addVariable": "Dodaj varijablu", + "keyLabel": "Ključ", + "keyTooLong": "Ključ može imati najviše 256 znakova", + "keyDuplicate": "Ovaj ključ već postoji u profilu", + "valueLabel": "Vrijednost", + "secretLabel": "Tajna", + "secrets": { + "markSecret": "Označi kao tajnu", + "masked": "Skrivena vrijednost", + "reveal": "Otkrijte vrijednost", + "newValuePlaceholder": "Unesite novu tajnu vrijednost", + "valuePlaceholder": "Tajna vrijednost (šifrovana)" + }, + "variablesEmpty": "Još nema varijabli", + "variablesSaveFailed": "Nije moguće sačuvati varijablu", + "commandsTitle": "Komande za podešavanje", + "addCommand": "Dodaj naredbu", + "commandLabel": "Zapovjedi", + "commandPlaceholder": "e.g. pnpm install", + "commandsEmpty": "Još nema naredbi za postavljanje", + "commandsSaveFailed": "Nije moguće sačuvati naredbe za postavljanje", + "moveUp": "Kreni gore", + "moveDown": "Pomeri se dole", + "skillsTitle": "Vještine", + "addSkill": "Dodajte vještinu", + "skillNameLabel": "Naziv vještine", + "skillMarkdownLabel": "Sadržaj vještina", + "skillsEmpty": "Još nema vještina", + "skillNameRequired": "Unesite naziv vještine", + "skillNameInvalid": "Ime vještine mora početi slovom ili cifrom i koristiti samo mala slova, cifre i crtice", + "skillContentRequired": "Sadržaj vještina je obavezan", + "skillSaveFailed": "Nije moguće sačuvati vještinu", + "skillsHint": "Zalijepite SKILL.md i njegovo ime se popunjava; dodajte vještinu sa svog telefona bez povlačenja i ispuštanja.", + "skillEditTitle": "Uredi vještinu", + "mcp": { + "title": "MCP serveri", + "add": "Dodajte MCP server", + "empty": "Još nema MCP servera", + "name": "Ime servera", + "type": "Tip servera", + "localType": "Lokalno", + "remoteType": "Daljinski", + "command": "Komandna linija", + "url": "URL", + "environment": "Okruženje (JSON)", + "headers": "Zaglavlja (JSON)", + "timeout": "Timeout", + "secretCount": "Tajni broj", + "namePlaceholder": "my-server", + "commandPlaceholder": "npx @example/mcp-server", + "jsonPlaceholder": "{\n \"API_KEY\": \"sk-...\"\n}", + "nameRequired": "Unesite ime servera", + "nameInvalid": "Ime može koristiti samo slova, brojeve, donje crte i crtice", + "commandRequired": "Unesite komandu", + "commandTooLong": "Koristite najviše 50 argumenata od po 500 znakova", + "urlRequired": "Unesite URL", + "urlInvalid": "Unesite važeći URL", + "urlTooLong": "URL može imati najviše 2048 znakova", + "jsonInvalid": "Unesite važeći JSON — objekt vrijednosti niza", + "jsonTooLarge": "Koristite najviše 50 unosa, ključevi do 128 i vrijednosti do 4096 znakova", + "timeoutInvalid": "Vremensko ograničenje mora biti cijeli broj milisekundi", + "edit": "Uredite MCP server", + "saveFailed": "Nije moguće sačuvati MCP server" + }, + "agents": { + "title": "Profil agenti", + "add": "Dodaj agenta", + "empty": "Još nema agenata", + "name": "Ime agenta", + "slug": "Agent puž", + "description": "Opis agenta", + "model": "Nadjačavanje modela", + "modelPlaceholder": "Koristi zadanu sesiju", + "prompt": "Sistemski prompt", + "steps": "Maks. koraka", + "stepsPlaceholder": "npr. 50", + "temperature": "Temperatura", + "temperaturePlaceholder": "npr. 0.2", + "topP": "top_p", + "topPPlaceholder": "npr. 0.95", + "slugPlaceholder": "my-agent", + "namePlaceholder": "Moj agent", + "promptPlaceholder": "ti si...", + "tools": "Alati", + "visibility": "Vidljivost", + "visibilityPrimary": "Primarni — prikazano u biraču", + "visibilitySubagent": "Subagent — može se pozvati samo od drugih", + "visibilityAll": "Sve — oboje", + "saveFailed": "Nije moguće sačuvati agenta", + "nameRequired": "Unesite ime agenta", + "slugRequired": "Unesite puž agenta", + "slugInvalid": "Slug mora početi slovom i koristiti samo mala slova, cifre i crtice", + "slugConflict": "Taj puž je rezervisan od strane ugrađenog agenta", + "edit": "Uredi agenta" + }, + "slashCommands": { + "title": "Slash komande", + "add": "Dodaj naredbu kose crte", + "empty": "Još nema komandi kose crte", + "name": "Ime komande", + "description": "Opis komande", + "template": "Predložak", + "namePlaceholder": "explain", + "templatePlaceholder": "Objasnite $ARGUMENTS", + "agent": "Agent", + "subtask": "Pokreni kao podzadatak", + "edit": "Uredi naredbu kose crte", + "saveFailed": "Nije moguće sačuvati naredbu", + "nameRequired": "Unesite naziv komande", + "nameInvalid": "Ime mora početi malim slovom i koristiti samo mala slova, cifre i crtice", + "templateRequired": "Unesite predložak komande", + "nameConflict": "To ime je rezervisano ugrađenom komandom" + }, + "repoBindings": { + "title": "Zadani profili za Repos", + "add": "Dodaj zadano", + "empty": "Nema konfiguriranih zadanih postavki", + "emptyDescription": "Dodajte zadano za automatsku primjenu profila pri pokretanju sesija za repo.", + "repo": "Odaberite spremište…", + "profile": "Odaberite profil", + "remove": "Odveži", + "private": "Privatno", + "search": "Pretražite spremišta…", + "addedToast": "Zadani profil postavljen za \"{{repo}}\"", + "addFailed": "Nije moguće postaviti zadani profil", + "removedToast": "Zadani profil je uklonjen za \"{{repo}}\"", + "removeFailed": "Nije moguće ukloniti zadani profil", + "pinnedTitle": "Zakačeno u spremišta", + "pinRepo": "Zakačite repo", + "pinnedEmpty": "Nije zakačeno ni za jedno spremište.", + "pinnedDescription": "Automatski se učitava kad god sesija cilja jedno od ovih spremišta.", + "pinnedToast": "Zakačeno na \"{{repo}}\"", + "pinFailed": "Nije moguće zakačiti repo", + "unboundToast": "Repo nevezan", + "unbindFailed": "Nije moguće poništiti vezivanje repo" + } } } diff --git a/apps/mobile/src/i18n/locales/ca.json b/apps/mobile/src/i18n/locales/ca.json index e650db26cda..e6163ce4f05 100644 --- a/apps/mobile/src/i18n/locales/ca.json +++ b/apps/mobile/src/i18n/locales/ca.json @@ -2219,7 +2219,33 @@ "continueFrom": "S'està continuant la sessió {{title}}.", "importingSession": "Important la sessió…", "cliCannotContinue": "Aquest CLI no pot continuar una sessió. Tria Cloud Agent o un altre CLI.", - "importFailed": "No s'ha pogut importar aquesta sessió." + "importFailed": "No s'ha pogut importar aquesta sessió.", + "pickProfile": "Tria un perfil", + "noProfile": "Sense perfil", + "manageProfiles": "Gestionar perfils", + "noProfiles": "Encara no hi ha perfils. Creeu-ne un per afegir variables d'entorn, servidors MCP i habilitats.", + "advancedConfig": "Configuració avançada", + "advancedConfigDescription": "Seleccioneu un perfil per carregar configuracions desades i configureu manualment variables d'entorn addicionals i ordres de configuració", + "saveAsProfile": "Desa com a perfil", + "saveProfileDescription": "Deseu les variables d'entorn actuals i configureu les ordres com a perfil reutilitzable.", + "profileSummary": "{{vars}} variables d'entorn · {{commands}} ordres de configuració", + "profileSaved": "S'ha desat el perfil \"{{name}}\".", + "organizationProfiles": "Perfils de l'organització", + "personalProfiles": "Perfils personals", + "yourProfiles": "Els teus perfils", + "failedToLoadProfiles": "No s'han pogut carregar els perfils", + "nameTooLong": "El nom del perfil és massa llarg", + "descriptionTooLong": "La descripció és massa llarga", + "secretsEncrypted": "Els secrets es xifren abans de l'emmagatzematge.", + "profileActive": "Perfil actiu", + "profilesActive": "Perfils actius", + "customConfigActive": "Configuració personalitzada activa", + "configNeedsAttention": "La configuració necessita atenció", + "repoProfile": "Perfil de repo", + "selectedProfile": "Perfil seleccionat", + "manualOverrides": "Anul·lació manual", + "openSettingsToReview": "Obriu Configuració per revisar-lo", + "profileSelection": "Selecció de perfil" }, "partRenderer": { "updatedFileCount_one": "S'ha actualitzat {{displayCount}} fitxer.", @@ -3267,5 +3293,180 @@ "multiplierError": "Introdueix un multiplicador d'1 o més", "permissionDenied": "No tens permís per gestionar les alertes de despesa.", "pushOffByCategory": "Desactivat a Notificacions" + }, + "profiles": { + "title": "Perfils", + "entrySubtitle": "Reutilitza la configuració de l'entorn entre sessions", + "newProfile": "Nou perfil", + "emptyTitle": "Encara no hi ha perfils", + "emptyDescription": "Creeu un perfil per reutilitzar variables d'entorn, ordres de configuració i habilitats entre sessions.", + "loadFailed": "No s'han pogut carregar els perfils", + "counts": { + "vars": "{{count}} variables", + "mcp": "{{count}} MCP", + "skills": "{{count}} habilitats", + "commands": "{{count}} ordres", + "varsShort": "{{count}}v", + "skillsShort": "{{count}}h", + "commandsShort": "{{count}}o" + }, + "nameLabel": "Nom del perfil", + "namePlaceholder": "p. ex. Depuració del backend", + "descriptionLabel": "Descripció del perfil", + "descriptionPlaceholder": "Descripció opcional", + "ownerLabel": "Propietari del perfil", + "nameRequired": "Introduïu un nom de perfil", + "createAction": "Crear perfil", + "createFailed": "No s'ha pogut crear el perfil", + "createdToast": "S'ha creat el perfil \"{{name}}\".", + "settingsSectionTitle": "Configuració", + "defaultSectionTitle": "Perfil predeterminat", + "personalDefaultDescription": "Es carrega automàticament quan cap dipòsit té un perfil fixat.", + "organizationDefaultDescription": "Es carrega automàticament per als membres que no tenen cap predeterminat personal.", + "setAsDefault": "Estableix com a predeterminat", + "removeDefault": "Elimina com a predeterminat", + "saveFailed": "No s'ha pogut desar el perfil", + "deleteTitle": "Suprimeix el perfil", + "deleteMessage": "Això elimina permanentment el perfil i la seva configuració. Això no es pot desfer.", + "deleteFailed": "No s'ha pogut suprimir el perfil", + "deleteBlocked": "Aquest perfil l'utilitza un activador de webhook. Traieu-lo primer d'aquests activadors.", + "deletedToast": "S'ha suprimit el perfil \"{{name}}\".", + "variablesTitle": "Variables d'entorn", + "addVariable": "Afegeix variable", + "keyLabel": "clau", + "keyTooLong": "La clau ha de tenir 256 caràcters o menys", + "keyDuplicate": "Aquesta clau ja existeix al perfil", + "valueLabel": "Valor", + "secretLabel": "Secret", + "secrets": { + "markSecret": "Marca com a secret", + "masked": "Valor ocult", + "reveal": "Revelar valor", + "newValuePlaceholder": "Introdueix un valor secret nou", + "valuePlaceholder": "Valor secret (xifrat)" + }, + "variablesEmpty": "Encara no hi ha variables", + "variablesSaveFailed": "No s'ha pogut desar la variable", + "commandsTitle": "Configurar ordres", + "addCommand": "Afegeix l'ordre", + "commandLabel": "Comandament", + "commandPlaceholder": "e.g. pnpm install", + "commandsEmpty": "Encara no hi ha ordres de configuració", + "commandsSaveFailed": "No s'han pogut desar les ordres de configuració", + "moveUp": "Mou cap amunt", + "moveDown": "Mou cap avall", + "skillsTitle": "Habilitats", + "addSkill": "Afegeix habilitat", + "skillNameLabel": "Nom de l'habilitat", + "skillMarkdownLabel": "Contingut d'habilitat", + "skillsEmpty": "Encara no hi ha habilitats", + "skillNameRequired": "Introduïu un nom d'habilitat", + "skillNameInvalid": "El nom de l'habilitat ha de començar amb una lletra o un dígit i utilitzar només lletres minúscules, dígits i guions", + "skillContentRequired": "Es requereix contingut d'habilitat", + "skillSaveFailed": "No s'ha pogut estalviar l'habilitat", + "skillsHint": "Enganxeu un SKILL.md i el seu nom s'omple; afegeix una habilitat des del teu telèfon sense arrossegar i deixar anar.", + "skillEditTitle": "Edita l'habilitat", + "mcp": { + "title": "Servidors MCP", + "add": "Afegeix un servidor MCP", + "empty": "Encara no hi ha servidors MCP", + "name": "Nom del servidor", + "type": "Tipus de servidor", + "localType": "Local", + "remoteType": "Remot", + "command": "Línia d'ordres", + "url": "URL", + "environment": "Entorn (JSON)", + "headers": "Capçaleres (JSON)", + "timeout": "Temps mort", + "secretCount": "Recompte secret", + "namePlaceholder": "my-server", + "commandPlaceholder": "npx @example/mcp-server", + "jsonPlaceholder": "{\n \"API_KEY\": \"sk-...\"\n}", + "nameRequired": "Introduïu un nom de servidor", + "nameInvalid": "El nom només pot utilitzar lletres, números, guions baixos i guions", + "commandRequired": "Introduïu una ordre", + "commandTooLong": "Utilitzeu com a màxim 50 arguments de 500 caràcters cadascun", + "urlRequired": "Introduïu un URL", + "urlInvalid": "Introduïu un URL vàlid", + "urlTooLong": "L'URL ha de tenir 2048 caràcters o menys", + "jsonInvalid": "Introduïu JSON vàlid: un objecte de valors de cadena", + "jsonTooLarge": "Utilitzeu com a màxim 50 entrades, claus de fins a 128 i valors de fins a 4096 caràcters", + "timeoutInvalid": "El temps d'espera ha de ser un nombre sencer de mil·lisegons", + "edit": "Edita el servidor MCP", + "saveFailed": "No s'ha pogut desar el servidor MCP" + }, + "agents": { + "title": "Agents de perfil", + "add": "Afegeix un agent", + "empty": "Encara no hi ha agents", + "name": "Nom de l'agent", + "slug": "Agent llimac", + "description": "Descripció de l'agent", + "model": "Anul·lació del model", + "modelPlaceholder": "Utilitza la sessió predeterminada", + "prompt": "Indicador del sistema", + "steps": "Passos màxims", + "stepsPlaceholder": "p. ex. 50", + "temperature": "Temperatura", + "temperaturePlaceholder": "p. ex. 0.2", + "topP": "top_p", + "topPPlaceholder": "p. ex. 0.95", + "slugPlaceholder": "my-agent", + "namePlaceholder": "El meu agent", + "promptPlaceholder": "Ets un...", + "tools": "Eines", + "visibility": "Visibilitat", + "visibilityPrimary": "Principal: es mostra al selector", + "visibilitySubagent": "Subagent: només es pot trucar des d'altres", + "visibilityAll": "Tots, tots dos", + "saveFailed": "No s'ha pogut desar l'agent", + "nameRequired": "Introduïu un nom d'agent", + "slugRequired": "Introduïu un llimac d'agent", + "slugInvalid": "Slug ha de començar amb una lletra i utilitzar només lletres minúscules, dígits i guions", + "slugConflict": "Aquest llimac està reservat per un agent integrat", + "edit": "Edita l'agent" + }, + "slashCommands": { + "title": "Comandes de barra", + "add": "Afegeix l'ordre de barra inclinada", + "empty": "Encara no hi ha ordres de barra inclinada", + "name": "Nom de l'ordre", + "description": "Descripció de l'ordre", + "template": "Plantilla", + "namePlaceholder": "explain", + "templatePlaceholder": "Explica $ARGUMENTS", + "agent": "Agent", + "subtask": "Executar com a subtasca", + "edit": "Edita l'ordre de barra inclinada", + "saveFailed": "No s'ha pogut desar l'ordre", + "nameRequired": "Introduïu un nom d'ordre", + "nameInvalid": "El nom ha de començar amb una lletra minúscula i utilitzar només lletres minúscules, dígits i guions", + "templateRequired": "Introduïu una plantilla d'ordres", + "nameConflict": "Aquest nom està reservat per una ordre integrada" + }, + "repoBindings": { + "title": "Perfils predeterminats per a Repos", + "add": "Afegeix per defecte", + "empty": "No s'ha configurat cap valor predeterminat", + "emptyDescription": "Afegiu un valor predeterminat per aplicar automàticament un perfil quan inicieu sessions per a un repo.", + "repo": "Seleccioneu el repositori...", + "profile": "Seleccioneu el perfil", + "remove": "Desvincular", + "private": "Privat", + "search": "Cerca repositoris...", + "addedToast": "Perfil predeterminat definit per a \"{{repo}}\"", + "addFailed": "No s'ha pogut establir el perfil predeterminat", + "removedToast": "S'ha eliminat el perfil predeterminat per a \"{{repo}}\"", + "removeFailed": "No s'ha pogut eliminar el perfil predeterminat", + "pinnedTitle": "Fixat als repositoris", + "pinRepo": "Fixeu un repo", + "pinnedEmpty": "No s'ha fixat a cap dipòsit.", + "pinnedDescription": "Es carrega automàticament sempre que una sessió s'orienti a un d'aquests dipòsits.", + "pinnedToast": "Fixat a \"{{repo}}\"", + "pinFailed": "No s'ha pogut fixar el repo", + "unboundToast": "Repo no lligat", + "unbindFailed": "No s'ha pogut desenllaçar el repo" + } } } diff --git a/apps/mobile/src/i18n/locales/ckb.json b/apps/mobile/src/i18n/locales/ckb.json index d843351421e..854854937e3 100644 --- a/apps/mobile/src/i18n/locales/ckb.json +++ b/apps/mobile/src/i18n/locales/ckb.json @@ -2191,7 +2191,33 @@ "continueFrom": "بەردەوام دەبین لە {{title}}.", "importingSession": "سێشنەکە هاوردە دەکرێت", "cliCannotContinue": "ئەم CLI ـیە ناتوانێت سێشنێک بەردەوام بکات. Cloud Agent یان CLI ـیەکی تر هەڵبژێرە.", - "importFailed": "نەتوانرا ئەم سێشنە هاوردە بکرێت." + "importFailed": "نەتوانرا ئەم سێشنە هاوردە بکرێت.", + "pickProfile": "پڕۆفایلی هەڵبژێرە", + "noProfile": "هیچ پڕۆفایلی نییە", + "manageProfiles": "بەڕێوەبردنی پڕۆفایلی", + "noProfiles": "هێشتا هیچ پڕۆفایلی نییە. یەکێکیان دروست بکە بۆ زیادکردنی گۆڕاوە ژینگەییەکان، سێرڤەرەکانی MCP و کارامەییەکان.", + "advancedConfig": "ڕێکخستنی پێشکەوتوو", + "advancedConfigDescription": "پرۆفایلی هەڵبژێرە بۆ بارکردنی ڕێکخستنە پاشەکەوتکراوەکان، و بە دەست گۆڕاوە ژینگەییەکانی زیادە و فەرمانەکانی ڕێکخستن ڕێکبخە", + "saveAsProfile": "وەک پڕۆفایلی پاشەکەوت بکە", + "saveProfileDescription": "گۆڕاوە ژینگەییەکانی ئێستا و فەرمانەکانی ڕێکخستن وەک پرۆفایلی دووبارە بەکارهێنەرەوە هەڵبگرە.", + "profileSummary": "{{vars}} گۆڕاوە ژینگەییەکان · {{commands}} فەرمانەکانی ڕێکخستن", + "profileSaved": "پڕۆفایلی \"{{name}}\" پاشەکەوت کراوە", + "organizationProfiles": "پڕۆفایلی ڕێکخراوەکان", + "personalProfiles": "پڕۆفایلی کەسی", + "yourProfiles": "پڕۆفایلی ئێوە", + "failedToLoadProfiles": "شکستی هێنا لە بارکردنی پرۆفایلی", + "nameTooLong": "ناوی پڕۆفایلی زۆر درێژە", + "descriptionTooLong": "وەسفکردن زۆر درێژە", + "secretsEncrypted": "نهێنییەکان پێش هەڵگرتن کۆد دەکرێن.", + "profileActive": "پڕۆفایلی چالاکە", + "profilesActive": "پڕۆفایلی چالاکە", + "customConfigActive": "ڕێکخستنی تایبەت چالاکە", + "configNeedsAttention": "Config پێویستی بە گرنگیدان هەیە", + "repoProfile": "پرۆفایلی Repo", + "selectedProfile": "پرۆفایلی هەڵبژێردراو", + "manualOverrides": "سەرپێچیکردنی دەستی", + "openSettingsToReview": "ڕێکخستنەکان بکەرەوە بۆ پێداچوونەوە", + "profileSelection": "هەڵبژاردنی پڕۆفایلی" }, "partRenderer": { "updatedFileCount_one": "{{displayCount}} پەڕگە نوێ کرایەوە.", @@ -3224,5 +3250,180 @@ "multiplierError": "لێکدەرێکی 1 یان زیاتر بنووسە", "permissionDenied": "مۆڵەتی بەڕێوەبردنی ئاگادارکردنەوەکانی خەرجیت نییە.", "pushOffByCategory": "لە ئاگادارکردنەوەکاندا ناچالاکە" + }, + "profiles": { + "title": "پڕۆفایلی", + "entrySubtitle": "دووبارە بەکارهێنانەوەی ڕێکخستنەکانی ژینگە لە سەرانسەری دانیشتنەکاندا", + "newProfile": "پڕۆفایلی نوێ", + "emptyTitle": "هێشتا هیچ پڕۆفایلی نییە", + "emptyDescription": "پرۆفایلی دروست بکە بۆ دووبارە بەکارهێنانەوەی گۆڕاوە ژینگەییەکان، فەرمانەکانی ڕێکخستن و لێهاتووییەکان لە سەرانسەری دانیشتنەکاندا.", + "loadFailed": "نەتوانرا پڕۆفایلی باربکرێت", + "counts": { + "vars": "{{count}} گۆڕاوە", + "mcp": "{{count}} MCP", + "skills": "{{count}} کارامەیی", + "commands": "{{count}} فەرمان", + "varsShort": "{{count}}گ", + "skillsShort": "{{count}}ک", + "commandsShort": "{{count}}ف" + }, + "nameLabel": "ناوی پڕۆفایلی", + "namePlaceholder": "بۆ نموونە. چاککردنەوەی باکئەند", + "descriptionLabel": "وەسفکردنی پڕۆفایلی", + "descriptionPlaceholder": "وەسفێکی ئیختیاری", + "ownerLabel": "خاوەنی پڕۆفایلی", + "nameRequired": "ناوی پڕۆفایلی داخڵ بکە", + "createAction": "دروستکردنی پڕۆفایلی", + "createFailed": "نەتوانرا پڕۆفایلی دروست بکرێت", + "createdToast": "پڕۆفایلی \"{{name}}\" دروستکراوە", + "settingsSectionTitle": "ڕێکخستن", + "defaultSectionTitle": "پرۆفایلی پێشوەختە", + "personalDefaultDescription": "کاتێک هیچ کۆگایەک پرۆفایلی پێنکراوی نییە بە شێوەیەکی ئۆتۆماتیکی باردەکرێت.", + "organizationDefaultDescription": "خۆکارانە باردەکرێت بۆ ئەو ئەندامانەی کە هیچ دیفۆڵتی کەسییان نییە.", + "setAsDefault": "وەکو پێشوەختە ڕێکبخە", + "removeDefault": "وەکو پێشوەختە لاببە", + "saveFailed": "نەتوانرا پڕۆفایلی پاشەکەوت بکات", + "deleteTitle": "سڕینەوەی پڕۆفایلی", + "deleteMessage": "ئەمەش بۆ هەمیشە پرۆفایلی و ڕێکخستنەکانی لا دەبات. ئەمەش ناتوانرێت هەڵبوەشێتەوە.", + "deleteFailed": "نەتوانرا پڕۆفایلی بسڕدرێتەوە", + "deleteBlocked": "ئەم پرۆفایلە لەلایەن تریگەرێکی وێبهۆکەوە بەکاردەهێنرێت. سەرەتا لەو تریگەرانە دەریبهێنە.", + "deletedToast": "پڕۆفایلی \"{{name}}\" سڕاوەتەوە", + "variablesTitle": "گۆڕاوە ژینگەییەکان", + "addVariable": "زیادکردنی گۆڕاو", + "keyLabel": "کلیل", + "keyTooLong": "کلیلەکە دەبێت 256 نووسە یان کەمتر بێت", + "keyDuplicate": "ئەم کلیلە پێشتر لە پڕۆفایلەکەدا هەیە", + "valueLabel": "بەها", + "secretLabel": "نهێنی", + "secrets": { + "markSecret": "وەک نهێنی مارک بکە", + "masked": "بەهای شاراوە", + "reveal": "بەها ئاشکرا بکە", + "newValuePlaceholder": "بەهای نهێنی نوێ بنووسە", + "valuePlaceholder": "بەهای نهێنی (شێفرکراو)" + }, + "variablesEmpty": "هێشتا هیچ گۆڕاوێک نییە", + "variablesSaveFailed": "نەتوانرا گۆڕاوەکە پاشەکەوت بکات", + "commandsTitle": "فەرمانەکانی ڕێکخستن", + "addCommand": "فەرمان زیاد بکە", + "commandLabel": "فەرمان", + "commandPlaceholder": "e.g. pnpm install", + "commandsEmpty": "هێشتا هیچ فەرمانێکی ڕێکخستن نییە", + "commandsSaveFailed": "نەتوانرا فەرمانەکانی ڕێکخستن پاشەکەوت بکرێت", + "moveUp": "بەرەو سەرەوە بڕۆ", + "moveDown": "بڕۆ بۆ خوارەوە", + "skillsTitle": "کارامەییەکان", + "addSkill": "لێهاتوویی زیاد بکە", + "skillNameLabel": "ناوی لێهاتوویی", + "skillMarkdownLabel": "ناوەڕۆکی لێهاتوویی", + "skillsEmpty": "هێشتا هیچ لێهاتوویییەکی نییە", + "skillNameRequired": "ناوی کارامەیییەک داخڵ بکە", + "skillNameInvalid": "ناوی کارامەیی دەبێت بە پیت یان ژمارەیەک دەست پێبکات و تەنها پیتی بچووک و ژمارە و هێڵکاری بەکاربهێنێت", + "skillContentRequired": "ناوەڕۆکی لێهاتوویی پێویستە", + "skillSaveFailed": "نەیتوانی لێهاتوویی ڕزگار بکات", + "skillsHint": "SKILL.md ێک لێبدە و ناوەکەی پڕ دەبێتەوە؛ لە مۆبایلەکەتەوە کارامەیییەک زیاد بکە بەبێ ڕاکێشان و دابەزاندن.", + "skillEditTitle": "لێهاتوویی دەستکاریکردن", + "mcp": { + "title": "سێرڤەرەکانی MCP", + "add": "زیادکردنی سێرڤەری MCP", + "empty": "هێشتا هیچ سێرڤەرێکی MCP نییە", + "name": "ناوی سێرڤەر", + "type": "جۆری سێرڤەر", + "localType": "ناوخۆیی", + "remoteType": "دوور", + "command": "هێڵی فەرمان", + "url": "URL", + "environment": "ژینگە (JSON)", + "headers": "سەردێڕەکان (JSON)", + "timeout": "کات بەسەرچوون", + "secretCount": "ژماردنی نهێنی", + "namePlaceholder": "my-server", + "commandPlaceholder": "npx @example/mcp-server", + "jsonPlaceholder": "{\n \"API_KEY\": \"sk-...\"\n}", + "nameRequired": "ناوی سێرڤەرێک دابنێ", + "nameInvalid": "ناو دەتوانێت تەنها پیت و ژمارە و هێڵی ژێرەوە و هێڵکاری بەکاربهێنێت", + "commandRequired": "فەرمانێک داخڵ بکە", + "commandTooLong": "زیاتر لە 50 ئارگیومێنت بەکارمەهێنە، هەریەکە 500 نووسە", + "urlRequired": "URLێک داخڵ بکە", + "urlInvalid": "URL ێکی دروست داخڵ بکە", + "urlTooLong": "URL دەبێت 2048 نووسە یان کەمتر بێت", + "jsonInvalid": "JSON ی دروست داخڵ بکە — شتێکی بەهاکانی ڕیز", + "jsonTooLarge": "زیاتر لە 50 تۆمار بەکارمەهێنە، کلیلەکان تا 128 و بەهاکان تا 4096 نووسە", + "timeoutInvalid": "کات بەسەرچوون دەبێت ژمارەیەکی تەواو لە میلی چرکە بێت", + "edit": "دەستکاری سێرڤەری MCP بکە", + "saveFailed": "نەتوانرا سێرڤەری MCP پاشەکەوت بکات" + }, + "agents": { + "title": "بریکاری پڕۆفایلی", + "add": "زیادکردنی بریکار", + "empty": "هێشتا هیچ بریکارێک نییە", + "name": "ناوی بریکار", + "slug": "بریکاری شلە", + "description": "وەسفکردنی بریکار", + "model": "مۆدێلی سەرپێچیکردن", + "modelPlaceholder": "بەپێی پێشوەختەی دانیشتن بەکاربهێنە", + "prompt": "ئامۆژگاری سیستەم", + "steps": "زۆرترین هەنگاوەکان", + "stepsPlaceholder": "بۆ نموونە 50", + "temperature": "پلەی گەرمی", + "temperaturePlaceholder": "بۆ نموونە 0.2", + "topP": "top_p", + "topPPlaceholder": "بۆ نموونە 0.95", + "slugPlaceholder": "my-agent", + "namePlaceholder": "بریکاری من", + "promptPlaceholder": "تۆ کەسێکی ...", + "tools": "ئامرازەکان", + "visibility": "بینین", + "visibilityPrimary": "سەرەتایی — لە هەڵگرەکەدا نیشان دراوە", + "visibilitySubagent": "Subagent — تەنها لە کەسانی ترەوە پەیوەندی پێوە دەکرێت", + "visibilityAll": "هەموو — هەردووکیان", + "saveFailed": "نەیتوانی بریکار ڕزگار بکات", + "nameRequired": "ناوی بریکارێک بنووسە", + "slugRequired": "داخڵکردنی بریکاری slug", + "slugInvalid": "سلۆگ دەبێت بە پیتێک دەست پێبکات و تەنها پیتی بچووک و ژمارە و هێڵکاری بەکاربهێنێت", + "slugConflict": "ئەو شلەیە لەلایەن بریکارێکی ناوەکییەوە تەرخان دەکرێت", + "edit": "بریکاری دەستکاریکردن" + }, + "slashCommands": { + "title": "فەرمانەکانی سلاش", + "add": "فەرمانی slash زیاد بکە", + "empty": "هێشتا هیچ فەرمانێکی سلاش نییە", + "name": "ناوی فەرمان", + "description": "وەسفکردنی فەرمان", + "template": "قاڵب", + "namePlaceholder": "explain", + "templatePlaceholder": "$ARGUMENTS ڕوون بکەرەوە", + "agent": "بریکار", + "subtask": "وەکو ئەرکی لاوەکی کارپێبکە", + "edit": "فەرمانی دەستکاریکردنی slash", + "saveFailed": "نەتوانرا فەرمانەکە پاشەکەوت بکرێت", + "nameRequired": "ناوی فەرمانێک داخڵ بکە", + "nameInvalid": "ناو دەبێت بە پیتی بچووک دەست پێبکات و تەنها پیتی بچووک و ژمارە و هێڵکاری بەکاربهێنێت", + "templateRequired": "قاڵبێکی فەرمان داخڵ بکە", + "nameConflict": "ئەو ناوە بە فەرمانێکی ناوەکی تەرخانکراوە" + }, + "repoBindings": { + "title": "پرۆفایلی پێشوەختە بۆ Repos", + "add": "زیادکردنی پێشوەختە", + "empty": "هیچ پێشوەختەیەک ڕێکنەخراوە", + "emptyDescription": "پێشوەختەیەک زیاد بکە بۆ ئەوەی بە شێوەیەکی ئۆتۆماتیکی پرۆفایلی جێبەجێ بکەیت لە کاتی دەستپێکردنی دانیشتنەکان بۆ ڕیپۆ.", + "repo": "کۆگا هەڵبژێرە...", + "profile": "پرۆفایلی هەڵبژێرە", + "remove": "بێ بەستنەوە", + "private": "تایبەت", + "search": "گەڕان بەدوای کۆگاکاندا...", + "addedToast": "پرۆفایلی پێشوەختە بۆ \"{{repo}}\" دانراوە.", + "addFailed": "نەتوانرا پرۆفایلی پێشوەختە دابنێت", + "removedToast": "پرۆفایلی پێشوەختە بۆ \"{{repo}}\" لابرا.", + "removeFailed": "نەتوانرا پرۆفایلی پێشوەختە لاببات", + "pinnedTitle": "پێن کراوە بە کۆگاکان", + "pinRepo": "ڕیپۆیەک پین بکە", + "pinnedEmpty": "بە هیچ کۆگایەکەوە نەبەستراوەتەوە.", + "pinnedDescription": "هەرکاتێک دانیشتنێک یەکێک لەم کۆگایانە بکاتە ئامانج بە شێوەیەکی ئۆتۆماتیکی باردەکرێت.", + "pinnedToast": "پێن کراوە بۆ \"{{repo}}\"", + "pinFailed": "نەتوانرا ڕیپۆ پین بکرێت", + "unboundToast": "ڕیپۆ بێ بەستنەوە", + "unbindFailed": "نەتوانرا ڕیپۆ بێ بەستنەوە بکرێت" + } } } diff --git a/apps/mobile/src/i18n/locales/cs.json b/apps/mobile/src/i18n/locales/cs.json index 2537106aa42..a471cf26a58 100644 --- a/apps/mobile/src/i18n/locales/cs.json +++ b/apps/mobile/src/i18n/locales/cs.json @@ -2247,7 +2247,33 @@ "continueFrom": "Pokračujete v relaci {{title}}.", "importingSession": "Import relace", "cliCannotContinue": "Toto CLI nepodporuje pokračování v relaci. Vyberte službu Cloud Agent nebo jiné CLI.", - "importFailed": "Tuto relaci se nepodařilo importovat." + "importFailed": "Tuto relaci se nepodařilo importovat.", + "pickProfile": "Vyberte profil", + "noProfile": "Žádný profil", + "manageProfiles": "Správa profilů", + "noProfiles": "Zatím žádné profily. Vytvořte jeden a přidejte proměnné prostředí, servery MCP a dovednosti.", + "advancedConfig": "Pokročilá konfigurace", + "advancedConfigDescription": "Vyberte profil pro načtení uložených konfigurací a ručně nakonfigurujte další proměnné prostředí a příkazy nastavení", + "saveAsProfile": "Uložit jako profil", + "saveProfileDescription": "Uložte aktuální proměnné prostředí a příkazy nastavení jako opakovaně použitelný profil.", + "profileSummary": "Proměnné prostředí {{vars}} · Příkazy nastavení {{commands}}", + "profileSaved": "Profil „{{name}}“ byl uložen", + "organizationProfiles": "Profily organizací", + "personalProfiles": "Osobní profily", + "yourProfiles": "Vaše profily", + "failedToLoadProfiles": "Načtení profilů se nezdařilo", + "nameTooLong": "Název profilu je příliš dlouhý", + "descriptionTooLong": "Popis je příliš dlouhý", + "secretsEncrypted": "Tajemství jsou před uložením zašifrována.", + "profileActive": "Profil je aktivní", + "profilesActive": "Profily aktivní", + "customConfigActive": "Vlastní konfigurace je aktivní", + "configNeedsAttention": "Konfigurace vyžaduje pozornost", + "repoProfile": "Repo profil", + "selectedProfile": "Vybraný profil", + "manualOverrides": "Ruční ovládání", + "openSettingsToReview": "Pro kontrolu otevřete Nastavení", + "profileSelection": "Výběr profilu" }, "partRenderer": { "updatedFileCount_one": "Byl aktualizován {{displayCount}} soubor.", @@ -3310,5 +3336,180 @@ "multiplierError": "Zadej násobitel 1 nebo větší", "permissionDenied": "Nemáš oprávnění spravovat upozornění na útratu.", "pushOffByCategory": "Vypnuto v Oznámeních" + }, + "profiles": { + "title": "Profily", + "entrySubtitle": "Znovu použijte nastavení prostředí napříč relacemi", + "newProfile": "Nový profil", + "emptyTitle": "Zatím žádné profily", + "emptyDescription": "Vytvořte profil pro opětovné použití proměnných prostředí, instalačních příkazů a dovedností napříč relacemi.", + "loadFailed": "Profily se nepodařilo načíst", + "counts": { + "vars": "{{count}} proměnných", + "mcp": "{{count}} MCP", + "skills": "{{count}} dovedností", + "commands": "{{count}} příkazů", + "varsShort": "{{count}}prom", + "skillsShort": "{{count}}dov", + "commandsShort": "{{count}}pří" + }, + "nameLabel": "Název profilu", + "namePlaceholder": "např. Ladění backendu", + "descriptionLabel": "Popis profilu", + "descriptionPlaceholder": "Volitelný popis", + "ownerLabel": "Vlastník profilu", + "nameRequired": "Zadejte název profilu", + "createAction": "Vytvořit profil", + "createFailed": "Profil se nepodařilo vytvořit", + "createdToast": "Profil \"{{name}}\" byl vytvořen", + "settingsSectionTitle": "Konfigurace", + "defaultSectionTitle": "Výchozí profil", + "personalDefaultDescription": "Automaticky načteno, když žádné úložiště nemá připnutý profil.", + "organizationDefaultDescription": "Automaticky načteno pro členy, kteří nemají žádné osobní výchozí nastavení.", + "setAsDefault": "Nastavit jako výchozí", + "removeDefault": "Odebrat jako výchozí", + "saveFailed": "Profil se nepodařilo uložit", + "deleteTitle": "Smazat profil", + "deleteMessage": "Tím se profil a jeho nastavení trvale odstraní. Toto nelze vrátit zpět.", + "deleteFailed": "Profil se nepodařilo smazat", + "deleteBlocked": "Tento profil používá spouštěč webhooku. Nejprve jej odstraňte z těchto spouštěčů.", + "deletedToast": "Profil „{{name}}“ byl smazán", + "variablesTitle": "Proměnné prostředí", + "addVariable": "Přidat proměnnou", + "keyLabel": "Klíč", + "keyTooLong": "Klíč musí mít 256 znaků nebo méně", + "keyDuplicate": "Tento klíč už v profilu existuje", + "valueLabel": "Hodnota", + "secretLabel": "Tajný", + "secrets": { + "markSecret": "Označit jako tajné", + "masked": "Skrytá hodnota", + "reveal": "Odhalit hodnotu", + "newValuePlaceholder": "Zadejte novou tajnou hodnotu", + "valuePlaceholder": "Tajná hodnota (šifrovaná)" + }, + "variablesEmpty": "Zatím žádné proměnné", + "variablesSaveFailed": "Proměnnou se nepodařilo uložit", + "commandsTitle": "Nastavení příkazů", + "addCommand": "Přidat příkaz", + "commandLabel": "Příkaz", + "commandPlaceholder": "e.g. pnpm install", + "commandsEmpty": "Zatím žádné příkazy k nastavení", + "commandsSaveFailed": "Příkazy nastavení se nepodařilo uložit", + "moveUp": "Posuňte se nahoru", + "moveDown": "Posuňte se dolů", + "skillsTitle": "Dovednosti", + "addSkill": "Přidejte dovednost", + "skillNameLabel": "Název dovednosti", + "skillMarkdownLabel": "Obsah dovedností", + "skillsEmpty": "Zatím žádné dovednosti", + "skillNameRequired": "Zadejte název dovednosti", + "skillNameInvalid": "Název dovednosti musí začínat písmenem nebo číslicí a používat pouze malá písmena, číslice a pomlčky", + "skillContentRequired": "Je vyžadován obsah dovedností", + "skillSaveFailed": "Nepodařilo se zachránit dovednost", + "skillsHint": "Vložte SKILL.md a jeho název se vyplní; přidejte dovednost z telefonu bez přetahování.", + "skillEditTitle": "Upravit dovednost", + "mcp": { + "title": "MCP servery", + "add": "Přidat MCP server", + "empty": "Zatím žádné servery MCP", + "name": "Název serveru", + "type": "Typ serveru", + "localType": "Místní", + "remoteType": "Vzdálený", + "command": "Příkazový řádek", + "url": "URL", + "environment": "Prostředí (JSON)", + "headers": "Záhlaví (JSON)", + "timeout": "Časový limit", + "secretCount": "Tajný počet", + "namePlaceholder": "my-server", + "commandPlaceholder": "npx @example/mcp-server", + "jsonPlaceholder": "{\n \"API_KEY\": \"sk-...\"\n}", + "nameRequired": "Zadejte název serveru", + "nameInvalid": "Název může používat pouze písmena, čísla, podtržítka a pomlčky", + "commandRequired": "Zadejte příkaz", + "commandTooLong": "Použijte nejvýše 50 argumentů o 500 znacích", + "urlRequired": "Zadejte adresu URL", + "urlInvalid": "Zadejte platnou adresu URL", + "urlTooLong": "Adresa URL musí mít 2048 znaků nebo méně", + "jsonInvalid": "Zadejte platný JSON — objekt řetězcových hodnot", + "jsonTooLarge": "Použijte nejvýše 50 položek, klíče do 128 a hodnoty do 4096 znaků", + "timeoutInvalid": "Časový limit musí být celý počet milisekund", + "edit": "Upravit server MCP", + "saveFailed": "Server MCP se nepodařilo uložit" + }, + "agents": { + "title": "Profiloví agenti", + "add": "Přidat agenta", + "empty": "Zatím žádní agenti", + "name": "Jméno agenta", + "slug": "Agent slimák", + "description": "Popis agenta", + "model": "Přepsání modelu", + "modelPlaceholder": "Použít výchozí nastavení relace", + "prompt": "Systémová výzva", + "steps": "Max. kroků", + "stepsPlaceholder": "např. 50", + "temperature": "Teplota", + "temperaturePlaceholder": "např. 0.2", + "topP": "top_p", + "topPPlaceholder": "např. 0.95", + "slugPlaceholder": "my-agent", + "namePlaceholder": "Můj agent", + "promptPlaceholder": "ty jsi...", + "tools": "Nástroje", + "visibility": "Viditelnost", + "visibilityPrimary": "Primární – zobrazí se ve výběru", + "visibilitySubagent": "Subagent — lze volat pouze od ostatních", + "visibilityAll": "Všichni – oba", + "saveFailed": "Agenta se nepodařilo uložit", + "nameRequired": "Zadejte jméno agenta", + "slugRequired": "Zadejte agenta slimáka", + "slugInvalid": "Slug musí začínat písmenem a používat pouze malá písmena, číslice a pomlčky", + "slugConflict": "Ten slimák je rezervován vestavěným agentem", + "edit": "Upravit agenta" + }, + "slashCommands": { + "title": "Příkazy lomítka", + "add": "Přidat příkaz lomítko", + "empty": "Zatím žádné příkazy lomítka", + "name": "Název příkazu", + "description": "Popis příkazu", + "template": "Šablona", + "namePlaceholder": "explain", + "templatePlaceholder": "Vysvětlete $ARGUMENTS", + "agent": "Činidlo", + "subtask": "Spustit jako dílčí úkol", + "edit": "Příkaz upravit lomítko", + "saveFailed": "Příkaz se nepodařilo uložit", + "nameRequired": "Zadejte název příkazu", + "nameInvalid": "Název musí začínat malým písmenem a používat pouze malá písmena, číslice a pomlčky", + "templateRequired": "Zadejte šablonu příkazu", + "nameConflict": "Tento název je rezervován vestavěným příkazem" + }, + "repoBindings": { + "title": "Výchozí profily pro úložiště", + "add": "Přidat výchozí", + "empty": "Nejsou nakonfigurována žádná výchozí nastavení", + "emptyDescription": "Přidejte výchozí nastavení pro automatické použití profilu při zahájení relací pro repo.", + "repo": "Vyberte úložiště…", + "profile": "Vyberte profil", + "remove": "Zrušit vazbu", + "private": "Soukromé", + "search": "Prohledat úložiště…", + "addedToast": "Výchozí profil nastavený pro „{{repo}}“", + "addFailed": "Nepodařilo se nastavit výchozí profil", + "removedToast": "Výchozí profil odstraněn pro „{{repo}}“", + "removeFailed": "Výchozí profil se nepodařilo odstranit", + "pinnedTitle": "Připnuto k úložištím", + "pinRepo": "Připnout repo", + "pinnedEmpty": "Není připnuto k žádným úložištím.", + "pinnedDescription": "Automaticky načteno, kdykoli relace zacílí na jedno z těchto úložišť.", + "pinnedToast": "Připnuto k „{{repo}}“", + "pinFailed": "Repo se nepodařilo připnout", + "unboundToast": "Repo odpojeno", + "unbindFailed": "Repo se nepodařilo zrušit" + } } } diff --git a/apps/mobile/src/i18n/locales/cy.json b/apps/mobile/src/i18n/locales/cy.json index f60a54d620e..d401875e8ea 100644 --- a/apps/mobile/src/i18n/locales/cy.json +++ b/apps/mobile/src/i18n/locales/cy.json @@ -2303,7 +2303,33 @@ "continueFrom": "Yn parhau â {{title}}.", "importingSession": "Yn mewnforio sesiwn", "cliCannotContinue": "Ni all y CLI hwn barhau â sesiwn. Dewiswch Cloud Agent neu CLI arall.", - "importFailed": "Methu mewnforio'r sesiwn hon." + "importFailed": "Methu mewnforio'r sesiwn hon.", + "pickProfile": "Dewiswch broffil", + "noProfile": "Dim proffil", + "manageProfiles": "Rheoli proffiliau", + "noProfiles": "Dim proffiliau eto. Creu un i ychwanegu newidynnau amgylchedd, gweinyddwyr MCP, a sgiliau.", + "advancedConfig": "Ffurfweddiad Uwch", + "advancedConfigDescription": "Dewiswch broffil i lwytho ffurfweddiadau sydd wedi'u cadw, a ffurfweddu â llaw newidynnau amgylchedd ychwanegol a gorchmynion gosod", + "saveAsProfile": "Cadw fel Proffil", + "saveProfileDescription": "Arbedwch y newidynnau amgylchedd cyfredol a'r gorchmynion gosod fel proffil y gellir ei ailddefnyddio.", + "profileSummary": "{{vars}} newidynnau amgylchedd · {{commands}} gorchmynion gosod", + "profileSaved": "Proffil \"{{name}}\" wedi'i gadw", + "organizationProfiles": "Proffiliau Sefydliad", + "personalProfiles": "Proffiliau Personol", + "yourProfiles": "Eich Proffiliau", + "failedToLoadProfiles": "Wedi methu llwytho proffiliau", + "nameTooLong": "Mae enw'r proffil yn rhy hir", + "descriptionTooLong": "Mae'r disgrifiad yn rhy hir", + "secretsEncrypted": "Mae cyfrinachau'n cael eu hamgryptio cyn eu storio.", + "profileActive": "Proffil yn weithredol", + "profilesActive": "Proffiliau yn weithredol", + "customConfigActive": "Ffurfweddiad personol yn weithredol", + "configNeedsAttention": "Config angen sylw", + "repoProfile": "Proffil repo", + "selectedProfile": "Proffil dethol", + "manualOverrides": "Diystyru â llaw", + "openSettingsToReview": "Agor Gosodiadau i'w hadolygu", + "profileSelection": "Dewis proffil" }, "partRenderer": { "updatedFileCount_one": "Diweddarwyd {{displayCount}} ffeil.", @@ -3396,5 +3422,180 @@ "multiplierError": "Nodwch luosydd o 1 neu fwy", "permissionDenied": "Nid oes gennyt ganiatâd i reoli rhybuddion gwariant.", "pushOffByCategory": "I ffwrdd yn Hysbysiadau" + }, + "profiles": { + "title": "Proffiliau", + "entrySubtitle": "Ailddefnyddio gosodiadau amgylchedd ar draws sesiynau", + "newProfile": "Proffil newydd", + "emptyTitle": "Dim proffiliau eto", + "emptyDescription": "Creu proffil i ailddefnyddio newidynnau amgylchedd, gorchmynion gosod, a sgiliau ar draws sesiynau.", + "loadFailed": "Methu llwytho proffiliau", + "counts": { + "vars": "{{count}} newidynnau", + "mcp": "{{count}} MCP", + "skills": "{{count}} sgiliau", + "commands": "{{count}} gorchmynion", + "varsShort": "{{count}}n", + "skillsShort": "{{count}}s", + "commandsShort": "{{count}}g" + }, + "nameLabel": "Enw proffil", + "namePlaceholder": "e.e. Difa chwilod cefn", + "descriptionLabel": "Disgrifiad proffil", + "descriptionPlaceholder": "Disgrifiad dewisol", + "ownerLabel": "Perchennog proffil", + "nameRequired": "Rhowch enw proffil", + "createAction": "Creu proffil", + "createFailed": "Methu creu proffil", + "createdToast": "Proffil \"{{name}}\" wedi'i greu", + "settingsSectionTitle": "Cyfluniad", + "defaultSectionTitle": "Proffil diofyn", + "personalDefaultDescription": "Wedi'i lwytho'n awtomatig pan nad oes gan unrhyw ystorfa broffil wedi'i binio.", + "organizationDefaultDescription": "Wedi'i lwytho'n awtomatig ar gyfer aelodau nad oes ganddynt unrhyw ragosodiad personol.", + "setAsDefault": "Gosod fel rhagosodedig", + "removeDefault": "Dileu fel rhagosodiad", + "saveFailed": "Ni fu modd cadw'r proffil", + "deleteTitle": "Dileu proffil", + "deleteMessage": "Mae hyn yn dileu'r proffil a'i osodiadau yn barhaol. Ni ellir dadwneud hyn.", + "deleteFailed": "Ni fu modd dileu proffil", + "deleteBlocked": "Defnyddir y proffil hwn gan sbardun bachyn gwe. Tynnwch ef o'r sbardunau hynny yn gyntaf.", + "deletedToast": "Proffil \"{{name}}\" wedi'i ddileu", + "variablesTitle": "Newidynnau amgylcheddol", + "addVariable": "Ychwanegu newidyn", + "keyLabel": "Allwedd", + "keyTooLong": "Rhaid i'r allwedd fod yn 256 nod neu lai", + "keyDuplicate": "Mae'r allwedd hon eisoes yn bodoli yn y proffil", + "valueLabel": "Gwerth", + "secretLabel": "Cyfrinach", + "secrets": { + "markSecret": "Marciwch fel cyfrinach", + "masked": "Gwerth cudd", + "reveal": "Datgelu gwerth", + "newValuePlaceholder": "Nodwch werth cyfrinachol newydd", + "valuePlaceholder": "Gwerth cyfrinachol (wedi'i amgryptio)" + }, + "variablesEmpty": "Dim newidynnau eto", + "variablesSaveFailed": "Methu â chadw'r newidyn", + "commandsTitle": "Gosod gorchmynion", + "addCommand": "Ychwanegu gorchymyn", + "commandLabel": "Gorchymyn", + "commandPlaceholder": "e.g. pnpm install", + "commandsEmpty": "Dim gorchmynion gosod eto", + "commandsSaveFailed": "Ni fu modd cadw'r gorchmynion gosod", + "moveUp": "Symud i fyny", + "moveDown": "Symud i lawr", + "skillsTitle": "Sgiliau", + "addSkill": "Ychwanegu sgil", + "skillNameLabel": "Enw sgil", + "skillMarkdownLabel": "Cynnwys sgil", + "skillsEmpty": "Dim sgiliau eto", + "skillNameRequired": "Rhowch enw sgil", + "skillNameInvalid": "Rhaid i enw sgil ddechrau gyda llythyren neu ddigid a defnyddio llythrennau bach, digidau a llinellau llinellau yn unig", + "skillContentRequired": "Mae angen cynnwys sgil", + "skillSaveFailed": "Methu arbed sgil", + "skillsHint": "Gludwch SKILL.md ac mae ei enw yn llenwi; ychwanegu sgil o'ch ffôn heb lusgo a gollwng.", + "skillEditTitle": "Sgil golygu", + "mcp": { + "title": "gweinyddwyr MCP", + "add": "Ychwanegu gweinydd MCP", + "empty": "Dim gweinyddwyr MCP eto", + "name": "Enw gweinydd", + "type": "Math o weinydd", + "localType": "Lleol", + "remoteType": "Anghysbell", + "command": "Llinell orchymyn", + "url": "URL", + "environment": "Amgylchedd (JSON)", + "headers": "Penawdau (JSON)", + "timeout": "Goramser", + "secretCount": "Cyfrif cyfrinachol", + "namePlaceholder": "my-server", + "commandPlaceholder": "npx @example/mcp-server", + "jsonPlaceholder": "{\n \"API_KEY\": \"sk-...\"\n}", + "nameRequired": "Rhowch enw gweinydd", + "nameInvalid": "Dim ond llythrennau, rhifau, tanlinellau a llinellau toriad y gall enw eu defnyddio", + "commandRequired": "Rhowch orchymyn", + "commandTooLong": "Defnyddiwch 50 dadl ar y mwyaf, pob un yn 500 nod", + "urlRequired": "Rhowch URL", + "urlInvalid": "Rhowch URL dilys", + "urlTooLong": "Rhaid i'r URL fod yn 2048 nod neu lai", + "jsonInvalid": "Rhowch JSON dilys - gwrthrych o werthoedd llinynnol", + "jsonTooLarge": "Defnyddiwch 50 cofnod ar y mwyaf, allweddi hyd at 128 a gwerthoedd hyd at 4096 nod", + "timeoutInvalid": "Rhaid i'r terfyn amser fod yn nifer gyfan o filieiliadau", + "edit": "Golygu gweinydd MCP", + "saveFailed": "Methu â chadw gweinydd MCP" + }, + "agents": { + "title": "Asiantau proffil", + "add": "Ychwanegu asiant", + "empty": "Dim asiantau eto", + "name": "Enw asiant", + "slug": "Gwlithen asiant", + "description": "Disgrifiad asiant", + "model": "Diystyru'r model", + "modelPlaceholder": "Defnyddio rhagosodiad sesiwn", + "prompt": "System brydlon", + "steps": "Camau uchaf", + "stepsPlaceholder": "e.e. 50", + "temperature": "Tymheredd", + "temperaturePlaceholder": "e.e. 0.2", + "topP": "top_p", + "topPPlaceholder": "e.e. 0.95", + "slugPlaceholder": "my-agent", + "namePlaceholder": "Fy Asiant", + "promptPlaceholder": "Rydych chi'n ...", + "tools": "Offer", + "visibility": "Gwelededd", + "visibilityPrimary": "Cynradd - a ddangosir yn y codwr", + "visibilitySubagent": "Subagent — dim ond ei alw gan eraill", + "visibilityAll": "Pawb—y ddau", + "saveFailed": "Ni fu modd cadw'r asiant", + "nameRequired": "Rhowch enw asiant", + "slugRequired": "Rhowch wlithen asiant", + "slugInvalid": "Rhaid i wlithen ddechrau gyda llythyren a defnyddio llythrennau bach, digidau a llinellau llinellau yn unig", + "slugConflict": "Mae'r wlithen honno'n cael ei chadw gan asiant adeiledig", + "edit": "Golygu asiant" + }, + "slashCommands": { + "title": "Gorchmynion slaes", + "add": "Ychwanegu gorchymyn slaes", + "empty": "Dim gorchmynion slaes eto", + "name": "Enw gorchymyn", + "description": "Disgrifiad gorchymyn", + "template": "Templed", + "namePlaceholder": "explain", + "templatePlaceholder": "Eglurwch $ARGUMENTS", + "agent": "Asiant", + "subtask": "Rhedeg fel is-dasg", + "edit": "Golygu gorchymyn slaes", + "saveFailed": "Ni fu modd cadw'r gorchymyn", + "nameRequired": "Rhowch enw gorchymyn", + "nameInvalid": "Rhaid i'r enw ddechrau gyda llythyren fach a defnyddio llythrennau bach, digidau a llinellau llinellau yn unig", + "templateRequired": "Rhowch dempled gorchymyn", + "nameConflict": "Mae'r enw hwnnw wedi'i gadw gan orchymyn adeiledig" + }, + "repoBindings": { + "title": "Proffiliau Rhagosodedig ar gyfer Repos", + "add": "Ychwanegu rhagosodiad", + "empty": "Dim rhagosodiadau wedi'u ffurfweddu", + "emptyDescription": "Ychwanegu rhagosodiad i gymhwyso proffil yn awtomatig wrth gychwyn sesiynau ar gyfer repo.", + "repo": "Dewiswch ystorfa…", + "profile": "Dewiswch broffil", + "remove": "Dadrwymo", + "private": "Preifat", + "search": "Chwilio cadwrfeydd…", + "addedToast": "Gosod proffil rhagosodedig ar gyfer \"{{repo}}\"", + "addFailed": "Methu gosod y proffil rhagosodedig", + "removedToast": "Tynnwyd y proffil rhagosodedig ar gyfer \"{{repo}}\"", + "removeFailed": "Ni fu modd tynnu'r proffil rhagosodedig", + "pinnedTitle": "Wedi'i binio i ystorfeydd", + "pinRepo": "Piniwch repo", + "pinnedEmpty": "Heb ei binio i unrhyw gadwrfeydd.", + "pinnedDescription": "Wedi'i lwytho'n awtomatig pryd bynnag y bydd sesiwn yn targedu un o'r cadwrfeydd hyn.", + "pinnedToast": "Wedi'i binio i \"{{repo}}\"", + "pinFailed": "Methu pinio repo", + "unboundToast": "Repo heb ei rwymo", + "unbindFailed": "Ni fu modd dadrwymo repo" + } } } diff --git a/apps/mobile/src/i18n/locales/da.json b/apps/mobile/src/i18n/locales/da.json index 2d4c89ec6ff..df2578f0c24 100644 --- a/apps/mobile/src/i18n/locales/da.json +++ b/apps/mobile/src/i18n/locales/da.json @@ -2191,7 +2191,33 @@ "continueFrom": "Sessionen {{title}} fortsættes.", "importingSession": "Importerer session", "cliCannotContinue": "Denne CLI kan ikke fortsætte en session. Vælg Cloud Agent eller en anden CLI.", - "importFailed": "Kunne ikke importere denne session." + "importFailed": "Kunne ikke importere denne session.", + "pickProfile": "Vælg en profil", + "noProfile": "Ingen profil", + "manageProfiles": "Administrer profiler", + "noProfiles": "Ingen profiler endnu. Opret en for at tilføje miljøvariabler, MCP-servere og færdigheder.", + "advancedConfig": "Avanceret konfiguration", + "advancedConfigDescription": "Vælg en profil for at indlæse gemte konfigurationer, og konfigurer manuelt yderligere miljøvariabler og opsætningskommandoer", + "saveAsProfile": "Gem som profil", + "saveProfileDescription": "Gem de aktuelle miljøvariabler og opsætningskommandoer som en genbrugelig profil.", + "profileSummary": "{{vars}} miljøvariabler · {{commands}} opsætningskommandoer", + "profileSaved": "Profilen \"{{name}}\" blev gemt", + "organizationProfiles": "Organisationsprofiler", + "personalProfiles": "Personlige profiler", + "yourProfiles": "Dine profiler", + "failedToLoadProfiles": "Kunne ikke indlæse profiler", + "nameTooLong": "Profilnavnet er for langt", + "descriptionTooLong": "Beskrivelsen er for lang", + "secretsEncrypted": "Hemmeligheder krypteres før opbevaring.", + "profileActive": "Profil aktiv", + "profilesActive": "Profiler aktive", + "customConfigActive": "Brugerdefineret konfiguration aktiv", + "configNeedsAttention": "Konfiguration kræver opmærksomhed", + "repoProfile": "Repo profil", + "selectedProfile": "Valgt profil", + "manualOverrides": "Manuelle tilsidesættelser", + "openSettingsToReview": "Åbn Indstillinger for at gennemgå", + "profileSelection": "Valg af profil" }, "partRenderer": { "updatedFileCount_one": "{{displayCount}} fil er opdateret.", @@ -3224,5 +3250,180 @@ "multiplierError": "Angiv en multiplikator på 1 eller mere", "permissionDenied": "Du har ikke tilladelse til at administrere forbrugsadvarsler.", "pushOffByCategory": "Fra i Notifikationer" + }, + "profiles": { + "title": "Profiler", + "entrySubtitle": "Genbrug miljøindstillinger på tværs af sessioner", + "newProfile": "Ny profil", + "emptyTitle": "Ingen profiler endnu", + "emptyDescription": "Opret en profil for at genbruge miljøvariabler, opsætningskommandoer og færdigheder på tværs af sessioner.", + "loadFailed": "Kunne ikke indlæse profiler", + "counts": { + "vars": "{{count}} variabler", + "mcp": "{{count}} MCP", + "skills": "{{count}} færdigheder", + "commands": "{{count}} kommandoer", + "varsShort": "{{count}}var", + "skillsShort": "{{count}}fæ", + "commandsShort": "{{count}}kom" + }, + "nameLabel": "Profilnavn", + "namePlaceholder": "f.eks. Backend debugging", + "descriptionLabel": "Profilbeskrivelse", + "descriptionPlaceholder": "Valgfri beskrivelse", + "ownerLabel": "Profilejer", + "nameRequired": "Indtast et profilnavn", + "createAction": "Opret profil", + "createFailed": "Kunne ikke oprette profil", + "createdToast": "Profilen \"{{name}}\" er oprettet", + "settingsSectionTitle": "Konfiguration", + "defaultSectionTitle": "Standard profil", + "personalDefaultDescription": "Automatisk indlæst, når intet lager har en fastgjort profil.", + "organizationDefaultDescription": "Automatisk indlæst for medlemmer, der ikke har nogen personlig standard.", + "setAsDefault": "Indstil som standard", + "removeDefault": "Fjern som standard", + "saveFailed": "Profilen kunne ikke gemmes", + "deleteTitle": "Slet profil", + "deleteMessage": "Dette fjerner permanent profilen og dens indstillinger. Dette kan ikke fortrydes.", + "deleteFailed": "Profilen kunne ikke slettes", + "deleteBlocked": "Denne profil bruges af en webhook-udløser. Fjern det fra disse udløsere først.", + "deletedToast": "Profilen \"{{name}}\" blev slettet", + "variablesTitle": "Miljøvariabler", + "addVariable": "Tilføj variabel", + "keyLabel": "Nøgle", + "keyTooLong": "Nøglen må højst være 256 tegn", + "keyDuplicate": "Denne nøgle findes allerede i profilen", + "valueLabel": "Værdi", + "secretLabel": "Hemmelighed", + "secrets": { + "markSecret": "Markér som hemmelig", + "masked": "Skjult værdi", + "reveal": "Afsløre værdi", + "newValuePlaceholder": "Indtast ny hemmelig værdi", + "valuePlaceholder": "Hemmelig værdi (krypteret)" + }, + "variablesEmpty": "Ingen variabler endnu", + "variablesSaveFailed": "Variablen kunne ikke gemmes", + "commandsTitle": "Opsætningskommandoer", + "addCommand": "Tilføj kommando", + "commandLabel": "Kommando", + "commandPlaceholder": "e.g. pnpm install", + "commandsEmpty": "Ingen opsætningskommandoer endnu", + "commandsSaveFailed": "Opsætningskommandoer kunne ikke gemmes", + "moveUp": "Flyt op", + "moveDown": "Flyt ned", + "skillsTitle": "Færdigheder", + "addSkill": "Tilføj færdigheder", + "skillNameLabel": "Færdighedsnavn", + "skillMarkdownLabel": "Færdighedsindhold", + "skillsEmpty": "Ingen færdigheder endnu", + "skillNameRequired": "Indtast et færdighedsnavn", + "skillNameInvalid": "Færdighedsnavn skal starte med et bogstav eller ciffer og kun bruge små bogstaver, cifre og bindestreger", + "skillContentRequired": "Færdighedsindhold er påkrævet", + "skillSaveFailed": "Kunne ikke gemme færdighed", + "skillsHint": "Indsæt en SKILL.md og dens navn udfyldes; tilføje en færdighed fra din telefon uden træk og slip.", + "skillEditTitle": "Rediger færdighed", + "mcp": { + "title": "MCP-servere", + "add": "Tilføj MCP-server", + "empty": "Ingen MCP-servere endnu", + "name": "Servernavn", + "type": "Server type", + "localType": "Lokal", + "remoteType": "Fjern", + "command": "Kommandolinje", + "url": "URL", + "environment": "Miljø (JSON)", + "headers": "Overskrifter (JSON)", + "timeout": "Timeout", + "secretCount": "Hemmelig optælling", + "namePlaceholder": "my-server", + "commandPlaceholder": "npx @example/mcp-server", + "jsonPlaceholder": "{\n \"API_KEY\": \"sk-...\"\n}", + "nameRequired": "Indtast et servernavn", + "nameInvalid": "Navn kan kun bruge bogstaver, tal, understregninger og bindestreger", + "commandRequired": "Indtast en kommando", + "commandTooLong": "Brug højst 50 argumenter på 500 tegn hver", + "urlRequired": "Indtast en URL", + "urlInvalid": "Indtast en gyldig URL", + "urlTooLong": "URL'en må højst være 2048 tegn", + "jsonInvalid": "Indtast gyldig JSON - et objekt med strengværdier", + "jsonTooLarge": "Brug højst 50 poster, nøgler op til 128 og værdier op til 4096 tegn", + "timeoutInvalid": "Timeout skal være et helt antal millisekunder", + "edit": "Rediger MCP-server", + "saveFailed": "MCP-serveren kunne ikke gemmes" + }, + "agents": { + "title": "Profilagenter", + "add": "Tilføj agent", + "empty": "Ingen agenter endnu", + "name": "Agent navn", + "slug": "Agentsnegl", + "description": "Agent beskrivelse", + "model": "Model tilsidesættelse", + "modelPlaceholder": "Brug sessionsstandard", + "prompt": "Systemprompt", + "steps": "Maks. trin", + "stepsPlaceholder": "f.eks. 50", + "temperature": "Temperatur", + "temperaturePlaceholder": "f.eks. 0.2", + "topP": "top_p", + "topPPlaceholder": "f.eks. 0.95", + "slugPlaceholder": "my-agent", + "namePlaceholder": "Min agent", + "promptPlaceholder": "Du er en...", + "tools": "Værktøjer", + "visibility": "Sigtbarhed", + "visibilityPrimary": "Primær — vist i vælgeren", + "visibilitySubagent": "Subagent — kan kun kaldes fra andre", + "visibilityAll": "Alle - begge", + "saveFailed": "Agenten kunne ikke gemmes", + "nameRequired": "Indtast et agentnavn", + "slugRequired": "Indtast en agentsnegl", + "slugInvalid": "Slug skal starte med et bogstav og kun bruge små bogstaver, cifre og bindestreger", + "slugConflict": "Den snegl er reserveret af en indbygget agent", + "edit": "Rediger agent" + }, + "slashCommands": { + "title": "Slash-kommandoer", + "add": "Tilføj skråstreg kommando", + "empty": "Ingen skråstreg kommandoer endnu", + "name": "Kommandonavn", + "description": "Kommandobeskrivelse", + "template": "Skabelon", + "namePlaceholder": "explain", + "templatePlaceholder": "Forklar $ARGUMENTS", + "agent": "Agent", + "subtask": "Kør som underopgave", + "edit": "Rediger skråstreg kommando", + "saveFailed": "Kommandoen kunne ikke gemmes", + "nameRequired": "Indtast et kommandonavn", + "nameInvalid": "Navnet skal starte med et lille bogstav og kun bruge små bogstaver, cifre og bindestreger", + "templateRequired": "Indtast en kommandoskabelon", + "nameConflict": "Dette navn er reserveret af en indbygget kommando" + }, + "repoBindings": { + "title": "Standardprofiler for Repos", + "add": "Tilføj standard", + "empty": "Ingen standardindstillinger konfigureret", + "emptyDescription": "Tilføj en standard for automatisk at anvende en profil, når du starter sessioner for en repo.", + "repo": "Vælg lager...", + "profile": "Vælg profil", + "remove": "Afbind", + "private": "Privat", + "search": "Søg i lagre...", + "addedToast": "Standardprofilen er indstillet til \"{{repo}}\"", + "addFailed": "Standardprofilen kunne ikke indstilles", + "removedToast": "Standardprofilen blev fjernet for \"{{repo}}\"", + "removeFailed": "Standardprofilen kunne ikke fjernes", + "pinnedTitle": "Fastgjort til lagre", + "pinRepo": "Fastgør en repo", + "pinnedEmpty": "Ikke fastgjort til nogen lagre.", + "pinnedDescription": "Automatisk indlæst, når en session er målrettet mod et af disse lagre.", + "pinnedToast": "Fastgjort til \"{{repo}}\"", + "pinFailed": "Repo kunne ikke fastgøres", + "unboundToast": "Repo ubundet", + "unbindFailed": "Repo kunne ikke ophæves" + } } } diff --git a/apps/mobile/src/i18n/locales/de.json b/apps/mobile/src/i18n/locales/de.json index 7d7273224ae..d54de21a1cf 100644 --- a/apps/mobile/src/i18n/locales/de.json +++ b/apps/mobile/src/i18n/locales/de.json @@ -1687,7 +1687,33 @@ "continueFrom": "Die Sitzung „{{title}}“ wird fortgesetzt.", "importingSession": "Sitzung wird importiert", "cliCannotContinue": "Diese CLI kann keine Sitzung fortsetzen. Wähle Cloud Agent oder eine andere CLI.", - "importFailed": "Diese Sitzung konnte nicht importiert werden." + "importFailed": "Diese Sitzung konnte nicht importiert werden.", + "pickProfile": "Wählen Sie ein Profil", + "noProfile": "Kein Profil", + "manageProfiles": "Profile verwalten", + "noProfiles": "Noch keine Profile. Erstellen Sie eine, um Umgebungsvariablen, MCP-Server und Fähigkeiten hinzuzufügen.", + "advancedConfig": "Erweiterte Konfiguration", + "advancedConfigDescription": "Wählen Sie ein Profil aus, um gespeicherte Konfigurationen zu laden und zusätzliche Umgebungsvariablen und Setup-Befehle manuell zu konfigurieren", + "saveAsProfile": "Als Profil speichern", + "saveProfileDescription": "Speichern Sie die aktuellen Umgebungsvariablen und Setup-Befehle als wiederverwendbares Profil.", + "profileSummary": "{{vars}} Umgebungsvariablen · {{commands}} Setup-Befehle", + "profileSaved": "Profil „{{name}}“ gespeichert", + "organizationProfiles": "Organisationsprofile", + "personalProfiles": "Persönliche Profile", + "yourProfiles": "Ihre Profile", + "failedToLoadProfiles": "Profile konnten nicht geladen werden", + "nameTooLong": "Der Profilname ist zu lang", + "descriptionTooLong": "Beschreibung ist zu lang", + "secretsEncrypted": "Geheimnisse werden vor der Speicherung verschlüsselt.", + "profileActive": "Profil aktiv", + "profilesActive": "Profile aktiv", + "customConfigActive": "Benutzerdefinierte Konfiguration aktiv", + "configNeedsAttention": "Die Konfiguration erfordert Aufmerksamkeit", + "repoProfile": "Repo-Profil", + "selectedProfile": "Ausgewähltes Profil", + "manualOverrides": "Manuelle Überschreibungen", + "openSettingsToReview": "Öffnen Sie die Einstellungen, um sie zu überprüfen", + "profileSelection": "Profilauswahl" }, "partRenderer": { "updatedFileCount_one": "{{displayCount}} Datei wurde aktualisiert.", @@ -3224,5 +3250,180 @@ "multiplierError": "Gib einen Multiplikator von 1 oder mehr ein", "permissionDenied": "Du hast keine Berechtigung, Ausgaben-Warnungen zu verwalten.", "pushOffByCategory": "In Benachrichtigungen aus" + }, + "profiles": { + "title": "Profile", + "entrySubtitle": "Umgebungseinstellungen sitzungsübergreifend wiederverwenden", + "newProfile": "Neues Profil", + "emptyTitle": "Noch keine Profile", + "emptyDescription": "Erstellen Sie ein Profil, um Umgebungsvariablen, Setup-Befehle und Fähigkeiten sitzungsübergreifend wiederzuverwenden.", + "loadFailed": "Profile konnten nicht geladen werden", + "counts": { + "vars": "{{count}} Variablen", + "mcp": "{{count}} MCP", + "skills": "{{count}} Fähigkeiten", + "commands": "{{count}} Befehle", + "varsShort": "{{count}}Var", + "skillsShort": "{{count}}Fäh", + "commandsShort": "{{count}}Bef" + }, + "nameLabel": "Profilname", + "namePlaceholder": "z.B. Backend-Debugging", + "descriptionLabel": "Profilbeschreibung", + "descriptionPlaceholder": "Optionale Beschreibung", + "ownerLabel": "Profilinhaber", + "nameRequired": "Geben Sie einen Profilnamen ein", + "createAction": "Profil erstellen", + "createFailed": "Profil konnte nicht erstellt werden", + "createdToast": "Profil „{{name}}“ erstellt", + "settingsSectionTitle": "Konfiguration", + "defaultSectionTitle": "Standardprofil", + "personalDefaultDescription": "Wird automatisch geladen, wenn kein Repository über ein angeheftetes Profil verfügt.", + "organizationDefaultDescription": "Wird automatisch für Mitglieder geladen, die keinen persönlichen Standard haben.", + "setAsDefault": "Als Standard festlegen", + "removeDefault": "Als Standard entfernen", + "saveFailed": "Profil konnte nicht gespeichert werden", + "deleteTitle": "Profil löschen", + "deleteMessage": "Dadurch werden das Profil und seine Einstellungen dauerhaft entfernt. Dies kann nicht rückgängig gemacht werden.", + "deleteFailed": "Profil konnte nicht gelöscht werden", + "deleteBlocked": "Dieses Profil wird von einem Webhook-Trigger verwendet. Entfernen Sie es zuerst von diesen Auslösern.", + "deletedToast": "Profil „{{name}}“ gelöscht", + "variablesTitle": "Umgebungsvariablen", + "addVariable": "Variable hinzufügen", + "keyLabel": "Schlüssel", + "keyTooLong": "Der Schlüssel darf höchstens 256 Zeichen lang sein", + "keyDuplicate": "Dieser Schlüssel ist bereits im Profil vorhanden", + "valueLabel": "Wert", + "secretLabel": "Geheimnis", + "secrets": { + "markSecret": "Als geheim markieren", + "masked": "Versteckter Wert", + "reveal": "Wert offenbaren", + "newValuePlaceholder": "Neuen geheimen Wert eingeben", + "valuePlaceholder": "Geheimer Wert (verschlüsselt)" + }, + "variablesEmpty": "Noch keine Variablen", + "variablesSaveFailed": "Variable konnte nicht gespeichert werden", + "commandsTitle": "Setup-Befehle", + "addCommand": "Befehl hinzufügen", + "commandLabel": "Befehl", + "commandPlaceholder": "e.g. pnpm install", + "commandsEmpty": "Noch keine Setup-Befehle", + "commandsSaveFailed": "Setup-Befehle konnten nicht gespeichert werden", + "moveUp": "Bewegen Sie sich nach oben", + "moveDown": "Bewegen Sie sich nach unten", + "skillsTitle": "Fähigkeiten", + "addSkill": "Fertigkeit hinzufügen", + "skillNameLabel": "Fähigkeitsname", + "skillMarkdownLabel": "Kompetenzinhalte", + "skillsEmpty": "Noch keine Fähigkeiten", + "skillNameRequired": "Geben Sie einen Skill-Namen ein", + "skillNameInvalid": "Der Fertigkeitsname muss mit einem Buchstaben oder einer Ziffer beginnen und darf nur Kleinbuchstaben, Ziffern und Bindestriche enthalten", + "skillContentRequired": "Kompetenzinhalte sind erforderlich", + "skillSaveFailed": "Fertigkeit konnte nicht gespeichert werden", + "skillsHint": "Fügen Sie eine SKILL.md ein und der Name wird ausgefüllt. Fügen Sie einen Skill von Ihrem Telefon aus ohne Drag & Drop hinzu.", + "skillEditTitle": "Fertigkeit bearbeiten", + "mcp": { + "title": "MCP-Server", + "add": "MCP-Server hinzufügen", + "empty": "Noch keine MCP-Server", + "name": "Servername", + "type": "Servertyp", + "localType": "Lokal", + "remoteType": "Fernbedienung", + "command": "Befehlszeile", + "url": "URL", + "environment": "Umgebung (JSON)", + "headers": "Header (JSON)", + "timeout": "Time-out", + "secretCount": "Geheime Zählung", + "namePlaceholder": "my-server", + "commandPlaceholder": "npx @example/mcp-server", + "jsonPlaceholder": "{\n \"API_KEY\": \"sk-...\"\n}", + "nameRequired": "Geben Sie einen Servernamen ein", + "nameInvalid": "Der Name darf nur Buchstaben, Zahlen, Unterstriche und Bindestriche enthalten", + "commandRequired": "Geben Sie einen Befehl ein", + "commandTooLong": "Verwenden Sie höchstens 50 Argumente mit je 500 Zeichen", + "urlRequired": "Geben Sie eine URL ein", + "urlInvalid": "Geben Sie eine gültige URL ein", + "urlTooLong": "Die URL darf höchstens 2048 Zeichen lang sein", + "jsonInvalid": "Geben Sie gültiges JSON ein – ein Objekt mit Zeichenfolgenwerten", + "jsonTooLarge": "Verwenden Sie höchstens 50 Einträge, Schlüssel bis 128 und Werte bis 4096 Zeichen", + "timeoutInvalid": "Das Zeitlimit muss eine ganze Zahl von Millisekunden betragen", + "edit": "Bearbeiten Sie den MCP-Server", + "saveFailed": "Der MCP-Server konnte nicht gespeichert werden" + }, + "agents": { + "title": "Profilagenten", + "add": "Agent hinzufügen", + "empty": "Noch keine Agenten", + "name": "Name des Agenten", + "slug": "Agent-Schnecke", + "description": "Beschreibung des Agenten", + "model": "Modellüberschreibung", + "modelPlaceholder": "Sitzungsstandard verwenden", + "prompt": "Systemaufforderung", + "steps": "Max. Schritte", + "stepsPlaceholder": "z. B. 50", + "temperature": "Temperatur", + "temperaturePlaceholder": "z. B. 0.2", + "topP": "top_p", + "topPPlaceholder": "z. B. 0.95", + "slugPlaceholder": "my-agent", + "namePlaceholder": "Mein Agent", + "promptPlaceholder": "Du bist ein...", + "tools": "Werkzeuge", + "visibility": "Sichtweite", + "visibilityPrimary": "Primär – wird im Picker angezeigt", + "visibilitySubagent": "Subagent – nur von anderen aufrufbar", + "visibilityAll": "Alle – beides", + "saveFailed": "Der Agent konnte nicht gespeichert werden", + "nameRequired": "Geben Sie einen Agentennamen ein", + "slugRequired": "Geben Sie einen Agenten-Slug ein", + "slugInvalid": "Slug muss mit einem Buchstaben beginnen und darf nur Kleinbuchstaben, Ziffern und Bindestriche verwenden", + "slugConflict": "Dieser Slug wird von einem integrierten Agenten reserviert", + "edit": "Agent bearbeiten" + }, + "slashCommands": { + "title": "Slash-Befehle", + "add": "Slash-Befehl hinzufügen", + "empty": "Noch keine Slash-Befehle", + "name": "Befehlsname", + "description": "Befehlsbeschreibung", + "template": "Vorlage", + "namePlaceholder": "explain", + "templatePlaceholder": "Erklären Sie $ARGUMENTS", + "agent": "Agent", + "subtask": "Als Unteraufgabe ausführen", + "edit": "Slash-Befehl bearbeiten", + "saveFailed": "Der Befehl konnte nicht gespeichert werden", + "nameRequired": "Geben Sie einen Befehlsnamen ein", + "nameInvalid": "Der Name muss mit einem Kleinbuchstaben beginnen und darf nur Kleinbuchstaben, Ziffern und Bindestriche enthalten", + "templateRequired": "Geben Sie eine Befehlsvorlage ein", + "nameConflict": "Dieser Name wird durch einen integrierten Befehl reserviert" + }, + "repoBindings": { + "title": "Standardprofile für Repos", + "add": "Standard hinzufügen", + "empty": "Keine Standardeinstellungen konfiguriert", + "emptyDescription": "Fügen Sie einen Standard hinzu, um beim Starten von Sitzungen für ein Repo automatisch ein Profil anzuwenden.", + "repo": "Repository auswählen…", + "profile": "Profil auswählen", + "remove": "Lösen", + "private": "Privat", + "search": "Repositories durchsuchen…", + "addedToast": "Standardprofil für „{{repo}}“ festgelegt", + "addFailed": "Das Standardprofil konnte nicht festgelegt werden", + "removedToast": "Standardprofil für „{{repo}}“ entfernt", + "removeFailed": "Das Standardprofil konnte nicht entfernt werden", + "pinnedTitle": "An Repositorys angeheftet", + "pinRepo": "Pinne ein Repo", + "pinnedEmpty": "Nicht an irgendwelche Repositorys angeheftet.", + "pinnedDescription": "Wird automatisch geladen, wenn eine Sitzung auf eines dieser Repositorys abzielt.", + "pinnedToast": "Angeheftet an „{{repo}}“", + "pinFailed": "Repo konnte nicht angeheftet werden", + "unboundToast": "Repo ungebunden", + "unbindFailed": "Repo konnte nicht entbunden werden" + } } } diff --git a/apps/mobile/src/i18n/locales/el.json b/apps/mobile/src/i18n/locales/el.json index f9211e8c600..f5c925dd4b5 100644 --- a/apps/mobile/src/i18n/locales/el.json +++ b/apps/mobile/src/i18n/locales/el.json @@ -2191,7 +2191,33 @@ "continueFrom": "Συνεχίζεται η συνεδρία {{title}}.", "importingSession": "Εισαγωγή συνεδρίας…", "cliCannotContinue": "Αυτό το CLI δεν μπορεί να συνεχίσει μια συνεδρία. Επίλεξε τον Cloud Agent ή άλλο CLI.", - "importFailed": "Δεν ήταν δυνατή η εισαγωγή αυτής της συνεδρίας." + "importFailed": "Δεν ήταν δυνατή η εισαγωγή αυτής της συνεδρίας.", + "pickProfile": "Επιλέξτε ένα προφίλ", + "noProfile": "Χωρίς προφίλ", + "manageProfiles": "Διαχείριση προφίλ", + "noProfiles": "Δεν υπάρχουν ακόμα προφίλ. Δημιουργήστε ένα για να προσθέσετε μεταβλητές περιβάλλοντος, διακομιστές MCP και δεξιότητες.", + "advancedConfig": "Προηγμένη διαμόρφωση", + "advancedConfigDescription": "Επιλέξτε ένα προφίλ για να φορτώσετε αποθηκευμένες διαμορφώσεις και διαμορφώστε μη αυτόματα πρόσθετες μεταβλητές περιβάλλοντος και εντολές ρύθμισης", + "saveAsProfile": "Αποθήκευση ως προφίλ", + "saveProfileDescription": "Αποθηκεύστε τις τρέχουσες μεταβλητές περιβάλλοντος και τις εντολές ρύθμισης ως επαναχρησιμοποιήσιμο προφίλ.", + "profileSummary": "{{vars}} μεταβλητές περιβάλλοντος · {{commands}} εντολές ρύθμισης", + "profileSaved": "Το προφίλ \"{{name}}\" αποθηκεύτηκε", + "organizationProfiles": "Προφίλ Οργανισμού", + "personalProfiles": "Προσωπικά Προφίλ", + "yourProfiles": "Τα Προφίλ σας", + "failedToLoadProfiles": "Η φόρτωση των προφίλ απέτυχε", + "nameTooLong": "Το όνομα προφίλ είναι πολύ μεγάλο", + "descriptionTooLong": "Η περιγραφή είναι πολύ μεγάλη", + "secretsEncrypted": "Τα μυστικά κρυπτογραφούνται πριν την αποθήκευση.", + "profileActive": "Προφίλ ενεργό", + "profilesActive": "Προφίλ ενεργά", + "customConfigActive": "Προσαρμοσμένη διαμόρφωση ενεργή", + "configNeedsAttention": "Η διαμόρφωση θέλει προσοχή", + "repoProfile": "Προφίλ repo", + "selectedProfile": "Επιλεγμένο προφίλ", + "manualOverrides": "Χειροκίνητες παρακάμψεις", + "openSettingsToReview": "Ανοίξτε τις Ρυθμίσεις για έλεγχο", + "profileSelection": "Επιλογή προφίλ" }, "partRenderer": { "updatedFileCount_one": "Ενημερώθηκε {{displayCount}} αρχείο.", @@ -3224,5 +3250,180 @@ "multiplierError": "Εισήγαγε πολλαπλασιαστή 1 ή μεγαλύτερο", "permissionDenied": "Δεν έχεις δικαίωμα να διαχειρίζεσαι τις ειδοποιήσεις δαπανών.", "pushOffByCategory": "Ανενεργό στις Ειδοποιήσεις" + }, + "profiles": { + "title": "Προφίλ", + "entrySubtitle": "Επαναχρησιμοποιήστε τις ρυθμίσεις περιβάλλοντος στις συνεδρίες", + "newProfile": "Νέο προφίλ", + "emptyTitle": "Δεν υπάρχουν ακόμα προφίλ", + "emptyDescription": "Δημιουργήστε ένα προφίλ για να επαναχρησιμοποιήσετε μεταβλητές περιβάλλοντος, εντολές ρύθμισης και δεξιότητες στις συνεδρίες.", + "loadFailed": "Δεν ήταν δυνατή η φόρτωση των προφίλ", + "counts": { + "vars": "{{count}} μεταβλητές", + "mcp": "{{count}} MCP", + "skills": "{{count}} δεξιότητες", + "commands": "{{count}} εντολές", + "varsShort": "{{count}}μ", + "skillsShort": "{{count}}δ", + "commandsShort": "{{count}}ε" + }, + "nameLabel": "Όνομα προφίλ", + "namePlaceholder": "π.χ. Αποσφαλμάτωση backend", + "descriptionLabel": "Περιγραφή προφίλ", + "descriptionPlaceholder": "Προαιρετική περιγραφή", + "ownerLabel": "Κάτοχος προφίλ", + "nameRequired": "Εισαγάγετε ένα όνομα προφίλ", + "createAction": "Δημιουργία προφίλ", + "createFailed": "Δεν ήταν δυνατή η δημιουργία προφίλ", + "createdToast": "Δημιουργήθηκε το προφίλ \"{{name}}\".", + "settingsSectionTitle": "Διαμόρφωση", + "defaultSectionTitle": "Προεπιλεγμένο προφίλ", + "personalDefaultDescription": "Αυτόματη φόρτωση όταν κανένα αποθετήριο δεν έχει καρφιτσωμένο προφίλ.", + "organizationDefaultDescription": "Αυτόματη φόρτωση για μέλη που δεν έχουν προσωπική προεπιλογή.", + "setAsDefault": "Ορισμός ως προεπιλογή", + "removeDefault": "Κατάργηση ως προεπιλογή", + "saveFailed": "Δεν ήταν δυνατή η αποθήκευση του προφίλ", + "deleteTitle": "Διαγραφή προφίλ", + "deleteMessage": "Αυτό καταργεί οριστικά το προφίλ και τις ρυθμίσεις του. Αυτό δεν μπορεί να αναιρεθεί.", + "deleteFailed": "Δεν ήταν δυνατή η διαγραφή του προφίλ", + "deleteBlocked": "Αυτό το προφίλ χρησιμοποιείται από μια ενεργοποίηση webhook. Αφαιρέστε το πρώτα από αυτές τις σκανδάλες.", + "deletedToast": "Το προφίλ \"{{name}}\" διαγράφηκε", + "variablesTitle": "Μεταβλητές περιβάλλοντος", + "addVariable": "Προσθήκη μεταβλητής", + "keyLabel": "Κλειδί", + "keyTooLong": "Το κλειδί πρέπει να έχει 256 χαρακτήρες ή λιγότερους", + "keyDuplicate": "Αυτό το κλειδί υπάρχει ήδη στο προφίλ", + "valueLabel": "Αξία", + "secretLabel": "Μυστικό", + "secrets": { + "markSecret": "Επισήμανση ως μυστικό", + "masked": "Κρυφή τιμή", + "reveal": "Αποκαλύψτε την αξία", + "newValuePlaceholder": "Εισαγάγετε νέα μυστική τιμή", + "valuePlaceholder": "Μυστική τιμή (κρυπτογραφημένη)" + }, + "variablesEmpty": "Δεν υπάρχουν ακόμη μεταβλητές", + "variablesSaveFailed": "Δεν ήταν δυνατή η αποθήκευση της μεταβλητής", + "commandsTitle": "Εντολές εγκατάστασης", + "addCommand": "Προσθήκη εντολής", + "commandLabel": "Εντολή", + "commandPlaceholder": "e.g. pnpm install", + "commandsEmpty": "Δεν υπάρχουν ακόμη εντολές ρύθμισης", + "commandsSaveFailed": "Δεν ήταν δυνατή η αποθήκευση εντολών ρύθμισης", + "moveUp": "Μετακινηθείτε προς τα πάνω", + "moveDown": "Μετακίνηση προς τα κάτω", + "skillsTitle": "Δεξιότητες", + "addSkill": "Προσθέστε δεξιότητα", + "skillNameLabel": "Όνομα δεξιότητας", + "skillMarkdownLabel": "Περιεχόμενο δεξιοτήτων", + "skillsEmpty": "Δεν υπάρχουν ακόμη δεξιότητες", + "skillNameRequired": "Εισαγάγετε ένα όνομα δεξιότητας", + "skillNameInvalid": "Το όνομα δεξιοτήτων πρέπει να ξεκινά με γράμμα ή ψηφίο και να χρησιμοποιεί μόνο πεζά γράμματα, ψηφία και παύλες", + "skillContentRequired": "Απαιτείται περιεχόμενο δεξιοτήτων", + "skillSaveFailed": "Δεν ήταν δυνατή η εξοικονόμηση δεξιοτήτων", + "skillsHint": "Επικολλήστε ένα SKILL.md και το όνομά του συμπληρώνεται. προσθέστε μια δεξιότητα από το τηλέφωνό σας χωρίς μεταφορά και απόθεση.", + "skillEditTitle": "Επεξεργασία ικανότητας", + "mcp": { + "title": "Διακομιστές MCP", + "add": "Προσθήκη διακομιστή MCP", + "empty": "Δεν υπάρχουν ακόμη διακομιστές MCP", + "name": "Όνομα διακομιστή", + "type": "Τύπος διακομιστή", + "localType": "Τοπικός", + "remoteType": "Μακρινός", + "command": "Γραμμή εντολών", + "url": "URL", + "environment": "Περιβάλλον (JSON)", + "headers": "Κεφαλίδες (JSON)", + "timeout": "Timeout", + "secretCount": "Μυστική καταμέτρηση", + "namePlaceholder": "my-server", + "commandPlaceholder": "npx @example/mcp-server", + "jsonPlaceholder": "{\n \"API_KEY\": \"sk-...\"\n}", + "nameRequired": "Εισαγάγετε ένα όνομα διακομιστή", + "nameInvalid": "Το όνομα μπορεί να χρησιμοποιεί μόνο γράμματα, αριθμούς, κάτω παύλες και παύλες", + "commandRequired": "Εισαγάγετε μια εντολή", + "commandTooLong": "Χρησιμοποιήστε το πολύ 50 ορίσματα των 500 χαρακτήρων το καθένα", + "urlRequired": "Εισαγάγετε μια διεύθυνση URL", + "urlInvalid": "Εισαγάγετε μια έγκυρη διεύθυνση URL", + "urlTooLong": "Η διεύθυνση URL πρέπει να έχει 2048 χαρακτήρες ή λιγότερους", + "jsonInvalid": "Εισαγάγετε έγκυρο JSON — ένα αντικείμενο τιμών συμβολοσειράς", + "jsonTooLarge": "Χρησιμοποιήστε το πολύ 50 καταχωρίσεις, κλειδιά έως 128 και τιμές έως 4096 χαρακτήρες", + "timeoutInvalid": "Το χρονικό όριο λήξης πρέπει να είναι ακέραιος αριθμός χιλιοστών του δευτερολέπτου", + "edit": "Επεξεργασία διακομιστή MCP", + "saveFailed": "Δεν ήταν δυνατή η αποθήκευση του διακομιστή MCP" + }, + "agents": { + "title": "Πράκτορες προφίλ", + "add": "Προσθήκη πράκτορα", + "empty": "Δεν υπάρχουν ακόμη πράκτορες", + "name": "Όνομα πράκτορα", + "slug": "Πράκτορας γυμνοσάλιαγκας", + "description": "Περιγραφή πράκτορα", + "model": "Παράκαμψη μοντέλου", + "modelPlaceholder": "Χρήση προεπιλογής συνεδρίας", + "prompt": "Προτροπή συστήματος", + "steps": "Μέγιστα βήματα", + "stepsPlaceholder": "π.χ. 50", + "temperature": "Θερμοκρασία", + "temperaturePlaceholder": "π.χ. 0.2", + "topP": "top_p", + "topPPlaceholder": "π.χ. 0.95", + "slugPlaceholder": "my-agent", + "namePlaceholder": "Ο πράκτοράς μου", + "promptPlaceholder": "Είσαι μια...", + "tools": "Εργαλεία", + "visibility": "Ορατότητα", + "visibilityPrimary": "Κύριο — εμφανίζεται στο εργαλείο επιλογής", + "visibilitySubagent": "Υποπράκτορας — καλείται μόνο από άλλους", + "visibilityAll": "Όλα — και τα δύο", + "saveFailed": "Δεν ήταν δυνατή η αποθήκευση του πράκτορα", + "nameRequired": "Εισαγάγετε ένα όνομα πράκτορα", + "slugRequired": "Εισαγάγετε έναν γυμνοσάλιαγκο πράκτορα", + "slugInvalid": "Το Slug πρέπει να ξεκινά με ένα γράμμα και να χρησιμοποιεί μόνο πεζά γράμματα, ψηφία και παύλες", + "slugConflict": "Αυτός ο γυμνοσάλιαγκας διατηρείται από έναν ενσωματωμένο πράκτορα", + "edit": "Πράκτορας επεξεργασίας" + }, + "slashCommands": { + "title": "Εντολές κάθετο", + "add": "Προσθήκη εντολής κάθετο", + "empty": "Δεν υπάρχουν ακόμη εντολές κάθετου", + "name": "Όνομα εντολής", + "description": "Περιγραφή εντολής", + "template": "Περίγραμμα", + "namePlaceholder": "explain", + "templatePlaceholder": "Εξηγήστε τα $ARGUMENTS", + "agent": "Μέσο", + "subtask": "Εκτέλεση ως δευτερεύουσα εργασία", + "edit": "Επεξεργασία εντολής κάθετο", + "saveFailed": "Δεν ήταν δυνατή η αποθήκευση της εντολής", + "nameRequired": "Εισαγάγετε ένα όνομα εντολής", + "nameInvalid": "Το όνομα πρέπει να ξεκινά με πεζό γράμμα και να χρησιμοποιεί μόνο πεζά γράμματα, ψηφία και παύλες", + "templateRequired": "Εισαγάγετε ένα πρότυπο εντολών", + "nameConflict": "Αυτό το όνομα δεσμεύεται από μια ενσωματωμένη εντολή" + }, + "repoBindings": { + "title": "Προεπιλεγμένα προφίλ για Repos", + "add": "Προσθήκη προεπιλογής", + "empty": "Δεν έχουν διαμορφωθεί προεπιλογές", + "emptyDescription": "Προσθέστε μια προεπιλογή για αυτόματη εφαρμογή ενός προφίλ κατά την έναρξη περιόδων σύνδεσης για ένα repo.", + "repo": "Επιλέξτε αποθετήριο…", + "profile": "Επιλέξτε προφίλ", + "remove": "Απολύω", + "private": "Ιδιωτικός", + "search": "Αναζήτηση αποθετηρίων…", + "addedToast": "Ορίστηκε προεπιλεγμένο προφίλ για \"{{repo}}\"", + "addFailed": "Δεν ήταν δυνατός ο ορισμός του προεπιλεγμένου προφίλ", + "removedToast": "Το προεπιλεγμένο προφίλ καταργήθηκε για το \"{{repo}}\"", + "removeFailed": "Δεν ήταν δυνατή η κατάργηση του προεπιλεγμένου προφίλ", + "pinnedTitle": "Καρφιτσωμένο στα αποθετήρια", + "pinRepo": "Καρφιτσώστε ένα ρεπό", + "pinnedEmpty": "Δεν είναι καρφιτσωμένο σε κανένα αποθετήριο.", + "pinnedDescription": "Φορτώνεται αυτόματα κάθε φορά που μια περίοδος σύνδεσης στοχεύει ένα από αυτά τα αποθετήρια.", + "pinnedToast": "Καρφιτσώθηκε στο \"{{repo}}\"", + "pinFailed": "Δεν ήταν δυνατό να καρφιτσωθεί το repo", + "unboundToast": "Το repo χωρίς περιορισμούς", + "unbindFailed": "Δεν ήταν δυνατή η αποδέσμευση του repo" + } } } diff --git a/apps/mobile/src/i18n/locales/en.json b/apps/mobile/src/i18n/locales/en.json index 398ad8a4c6a..f4899709012 100644 --- a/apps/mobile/src/i18n/locales/en.json +++ b/apps/mobile/src/i18n/locales/en.json @@ -2199,7 +2199,33 @@ "continueFrom": "Continuing {{title}}", "importingSession": "Importing session", "cliCannotContinue": "This CLI cannot continue a session. Pick Cloud Agent or another CLI.", - "importFailed": "Couldn't import this session." + "importFailed": "Couldn't import this session.", + "pickProfile": "Pick a profile", + "noProfile": "No profile", + "manageProfiles": "Manage profiles", + "noProfiles": "No profiles yet. Create one to add environment variables, MCP servers, and skills.", + "advancedConfig": "Advanced Configuration", + "advancedConfigDescription": "Select a profile to load saved configurations, and manually configure additional environment variables and setup commands", + "saveAsProfile": "Save as Profile", + "saveProfileDescription": "Save the current environment variables and setup commands as a reusable profile.", + "profileSummary": "{{vars}} environment variables · {{commands}} setup commands", + "profileSaved": "Profile \"{{name}}\" saved", + "organizationProfiles": "Organization Profiles", + "personalProfiles": "Personal Profiles", + "yourProfiles": "Your Profiles", + "failedToLoadProfiles": "Failed to load profiles", + "nameTooLong": "Profile name is too long", + "descriptionTooLong": "Description is too long", + "secretsEncrypted": "Secrets are encrypted before storage.", + "profileActive": "Profile active", + "profilesActive": "Profiles active", + "customConfigActive": "Custom config active", + "configNeedsAttention": "Config needs attention", + "repoProfile": "Repo profile", + "selectedProfile": "Selected profile", + "manualOverrides": "Manual overrides", + "openSettingsToReview": "Open Settings to review", + "profileSelection": "Profile selection" }, "partRenderer": { "updatedFileCount_one": "Updated {{displayCount}} file", @@ -3224,5 +3250,180 @@ "multiplierError": "Enter a multiplier of 1 or more", "permissionDenied": "You don't have permission to manage spend alerts.", "pushOffByCategory": "Off in Notifications" + }, + "profiles": { + "title": "Profiles", + "entrySubtitle": "Reuse environment settings across sessions", + "newProfile": "New profile", + "emptyTitle": "No profiles yet", + "emptyDescription": "Create a profile to reuse environment variables, setup commands, and skills across sessions.", + "loadFailed": "Couldn't load profiles", + "counts": { + "vars": "{{count}} vars", + "mcp": "{{count}} MCP", + "skills": "{{count}} skills", + "commands": "{{count}} cmds", + "varsShort": "{{count}}v", + "skillsShort": "{{count}}s", + "commandsShort": "{{count}}c" + }, + "nameLabel": "Profile name", + "namePlaceholder": "e.g. Backend debugging", + "descriptionLabel": "Profile description", + "descriptionPlaceholder": "Optional description", + "ownerLabel": "Profile owner", + "nameRequired": "Enter a profile name", + "createAction": "Create profile", + "createFailed": "Couldn't create profile", + "createdToast": "Profile \"{{name}}\" created", + "settingsSectionTitle": "Configuration", + "defaultSectionTitle": "Default profile", + "personalDefaultDescription": "Auto-loaded when no repository has a pinned profile.", + "organizationDefaultDescription": "Auto-loaded for members who have no personal default.", + "setAsDefault": "Set as default", + "removeDefault": "Remove as default", + "saveFailed": "Couldn't save profile", + "deleteTitle": "Delete profile", + "deleteMessage": "This permanently removes the profile and its settings. This cannot be undone.", + "deleteFailed": "Couldn't delete profile", + "deleteBlocked": "This profile is used by a webhook trigger. Remove it from those triggers first.", + "deletedToast": "Profile \"{{name}}\" deleted", + "variablesTitle": "Environment variables", + "addVariable": "Add variable", + "keyLabel": "Key", + "keyTooLong": "Key must be 256 characters or fewer", + "keyDuplicate": "This key already exists in the profile", + "valueLabel": "Value", + "secretLabel": "Secret", + "secrets": { + "markSecret": "Mark as secret", + "masked": "Hidden value", + "reveal": "Reveal value", + "newValuePlaceholder": "Enter new secret value", + "valuePlaceholder": "Secret value (encrypted)" + }, + "variablesEmpty": "No variables yet", + "variablesSaveFailed": "Couldn't save variable", + "commandsTitle": "Setup commands", + "addCommand": "Add command", + "commandLabel": "Command", + "commandPlaceholder": "e.g. pnpm install", + "commandsEmpty": "No setup commands yet", + "commandsSaveFailed": "Couldn't save setup commands", + "moveUp": "Move up", + "moveDown": "Move down", + "skillsTitle": "Skills", + "addSkill": "Add skill", + "skillNameLabel": "Skill name", + "skillMarkdownLabel": "Skill content", + "skillsEmpty": "No skills yet", + "skillNameRequired": "Enter a skill name", + "skillNameInvalid": "Skill name must start with a letter or digit and use only lowercase letters, digits, and dashes", + "skillContentRequired": "Skill content is required", + "skillSaveFailed": "Couldn't save skill", + "skillsHint": "Paste a SKILL.md and its name fills in; add a skill from your phone without drag and drop.", + "skillEditTitle": "Edit skill", + "mcp": { + "title": "MCP servers", + "add": "Add MCP server", + "empty": "No MCP servers yet", + "name": "Server name", + "type": "Server type", + "localType": "Local", + "remoteType": "Remote", + "command": "Command line", + "url": "URL", + "environment": "Environment (JSON)", + "headers": "Headers (JSON)", + "timeout": "Timeout", + "secretCount": "Secret count", + "namePlaceholder": "my-server", + "commandPlaceholder": "npx @example/mcp-server", + "jsonPlaceholder": "{\n \"API_KEY\": \"sk-...\"\n}", + "nameRequired": "Enter a server name", + "nameInvalid": "Name can use only letters, numbers, underscores, and dashes", + "commandRequired": "Enter a command", + "commandTooLong": "Use at most 50 arguments of 500 characters each", + "urlRequired": "Enter a URL", + "urlInvalid": "Enter a valid URL", + "urlTooLong": "URL must be 2048 characters or fewer", + "jsonInvalid": "Enter valid JSON — an object of string values", + "jsonTooLarge": "Use at most 50 entries, keys up to 128 and values up to 4096 characters", + "timeoutInvalid": "Timeout must be a whole number of milliseconds", + "edit": "Edit MCP server", + "saveFailed": "Couldn't save MCP server" + }, + "agents": { + "title": "Profile agents", + "add": "Add agent", + "empty": "No agents yet", + "name": "Agent name", + "slug": "Agent slug", + "description": "Agent description", + "model": "Model override", + "modelPlaceholder": "Use session default", + "prompt": "System prompt", + "steps": "Max steps", + "stepsPlaceholder": "e.g. 50", + "temperature": "Temperature", + "temperaturePlaceholder": "e.g. 0.2", + "topP": "top_p", + "topPPlaceholder": "e.g. 0.95", + "slugPlaceholder": "my-agent", + "namePlaceholder": "My Agent", + "promptPlaceholder": "You are a ...", + "tools": "Tools", + "visibility": "Visibility", + "visibilityPrimary": "Primary — shown in the picker", + "visibilitySubagent": "Subagent — only callable from others", + "visibilityAll": "All — both", + "saveFailed": "Couldn't save agent", + "nameRequired": "Enter an agent name", + "slugRequired": "Enter an agent slug", + "slugInvalid": "Slug must start with a letter and use only lowercase letters, digits, and dashes", + "slugConflict": "That slug is reserved by a built-in agent", + "edit": "Edit agent" + }, + "slashCommands": { + "title": "Slash commands", + "add": "Add slash command", + "empty": "No slash commands yet", + "name": "Command name", + "description": "Command description", + "template": "Template", + "namePlaceholder": "explain", + "templatePlaceholder": "Explain $ARGUMENTS", + "agent": "Agent", + "subtask": "Run as subtask", + "edit": "Edit slash command", + "saveFailed": "Couldn't save command", + "nameRequired": "Enter a command name", + "nameInvalid": "Name must start with a lowercase letter and use only lowercase letters, digits, and dashes", + "templateRequired": "Enter a command template", + "nameConflict": "That name is reserved by a built-in command" + }, + "repoBindings": { + "title": "Default Profiles for Repos", + "add": "Add default", + "empty": "No defaults configured", + "emptyDescription": "Add a default to automatically apply a profile when starting sessions for a repo.", + "repo": "Select repository…", + "profile": "Select profile", + "remove": "Unbind", + "private": "Private", + "search": "Search repositories…", + "addedToast": "Default profile set for \"{{repo}}\"", + "addFailed": "Couldn't set default profile", + "removedToast": "Default profile removed for \"{{repo}}\"", + "removeFailed": "Couldn't remove default profile", + "pinnedTitle": "Pinned to repositories", + "pinRepo": "Pin a repo", + "pinnedEmpty": "Not pinned to any repositories.", + "pinnedDescription": "Auto-loaded whenever a session targets one of these repositories.", + "pinnedToast": "Pinned to \"{{repo}}\"", + "pinFailed": "Couldn't pin repo", + "unboundToast": "Repo unbound", + "unbindFailed": "Couldn't unbind repo" + } } } diff --git a/apps/mobile/src/i18n/locales/es.json b/apps/mobile/src/i18n/locales/es.json index 01d8855664e..710910beeb1 100644 --- a/apps/mobile/src/i18n/locales/es.json +++ b/apps/mobile/src/i18n/locales/es.json @@ -1699,7 +1699,33 @@ "continueFrom": "Se está continuando la sesión {{title}}.", "importingSession": "Importando sesión", "cliCannotContinue": "Esta CLI no puede continuar una sesión. Elige Cloud Agent u otra CLI.", - "importFailed": "No se pudo importar esta sesión." + "importFailed": "No se pudo importar esta sesión.", + "pickProfile": "Elige un perfil", + "noProfile": "Sin perfil", + "manageProfiles": "Administrar perfiles", + "noProfiles": "Aún no hay perfiles. Cree uno para agregar variables de entorno, servidores MCP y habilidades.", + "advancedConfig": "Configuración avanzada", + "advancedConfigDescription": "Seleccione un perfil para cargar configuraciones guardadas y configure manualmente variables de entorno adicionales y comandos de configuración", + "saveAsProfile": "Guardar como perfil", + "saveProfileDescription": "Guarde las variables de entorno actuales y los comandos de configuración como un perfil reutilizable.", + "profileSummary": "{{vars}} variables de entorno · {{commands}} comandos de configuración", + "profileSaved": "Perfil \"{{name}}\" guardado", + "organizationProfiles": "Perfiles de organización", + "personalProfiles": "Perfiles personales", + "yourProfiles": "Tus perfiles", + "failedToLoadProfiles": "No se pudieron cargar los perfiles", + "nameTooLong": "El nombre del perfil es demasiado largo.", + "descriptionTooLong": "La descripción es demasiado larga.", + "secretsEncrypted": "Los secretos se cifran antes del almacenamiento.", + "profileActive": "Perfil activo", + "profilesActive": "Perfiles activos", + "customConfigActive": "Configuración personalizada activa", + "configNeedsAttention": "La configuración necesita atención", + "repoProfile": "Perfil de repositorio", + "selectedProfile": "Perfil seleccionado", + "manualOverrides": "Anulaciones manuales", + "openSettingsToReview": "Abra Configuración para revisar", + "profileSelection": "Selección de perfil" }, "partRenderer": { "updatedFileCount_one": "Se ha actualizado {{displayCount}} archivo.", @@ -3267,5 +3293,180 @@ "multiplierError": "Introduce un multiplicador de 1 o más", "permissionDenied": "No tienes permiso para gestionar las alertas de gasto.", "pushOffByCategory": "Desactivado en Notificaciones" + }, + "profiles": { + "title": "Perfiles", + "entrySubtitle": "Reutilizar la configuración del entorno en todas las sesiones", + "newProfile": "Nuevo perfil", + "emptyTitle": "Aún no hay perfiles", + "emptyDescription": "Cree un perfil para reutilizar variables de entorno, comandos de configuración y habilidades en todas las sesiones.", + "loadFailed": "No se pudieron cargar perfiles", + "counts": { + "vars": "{{count}} variables", + "mcp": "{{count}} MCP", + "skills": "{{count}} habilidades", + "commands": "{{count}} comandos", + "varsShort": "{{count}}v", + "skillsShort": "{{count}}h", + "commandsShort": "{{count}}c" + }, + "nameLabel": "Nombre del perfil", + "namePlaceholder": "p.ej. Depuración de backend", + "descriptionLabel": "Descripción del perfil", + "descriptionPlaceholder": "Descripción opcional", + "ownerLabel": "Propietario del perfil", + "nameRequired": "Introduce un nombre de perfil", + "createAction": "Crear perfil", + "createFailed": "No se pudo crear el perfil", + "createdToast": "Perfil \"{{name}}\" creado", + "settingsSectionTitle": "Configuración", + "defaultSectionTitle": "Perfil predeterminado", + "personalDefaultDescription": "Se carga automáticamente cuando ningún repositorio tiene un perfil fijado.", + "organizationDefaultDescription": "Cargado automáticamente para miembros que no tienen valores predeterminados personales.", + "setAsDefault": "Establecer como predeterminado", + "removeDefault": "Eliminar por defecto", + "saveFailed": "No se pudo guardar el perfil", + "deleteTitle": "Eliminar perfil", + "deleteMessage": "Esto elimina permanentemente el perfil y su configuración. Esto no se puede deshacer.", + "deleteFailed": "No se pudo eliminar el perfil", + "deleteBlocked": "Este perfil lo utiliza un activador de webhook. Quítelo primero de esos desencadenantes.", + "deletedToast": "Perfil \"{{name}}\" eliminado", + "variablesTitle": "Variables ambientales", + "addVariable": "Agregar variable", + "keyLabel": "Llave", + "keyTooLong": "La clave debe tener 256 caracteres o menos", + "keyDuplicate": "Esta clave ya existe en el perfil", + "valueLabel": "Valor", + "secretLabel": "Secreto", + "secrets": { + "markSecret": "Marcar como secreto", + "masked": "Valor oculto", + "reveal": "Revelar valor", + "newValuePlaceholder": "Introduce un nuevo valor secreto", + "valuePlaceholder": "Valor secreto (cifrado)" + }, + "variablesEmpty": "Aún no hay variables", + "variablesSaveFailed": "No se pudo guardar la variable", + "commandsTitle": "Comandos de configuración", + "addCommand": "Agregar comando", + "commandLabel": "Dominio", + "commandPlaceholder": "e.g. pnpm install", + "commandsEmpty": "Aún no hay comandos de configuración", + "commandsSaveFailed": "No se pudieron guardar los comandos de configuración", + "moveUp": "Subir", + "moveDown": "Bajar", + "skillsTitle": "Habilidades", + "addSkill": "Agregar habilidad", + "skillNameLabel": "Nombre de la habilidad", + "skillMarkdownLabel": "Contenido de habilidad", + "skillsEmpty": "Aún no hay habilidades", + "skillNameRequired": "Ingrese un nombre de habilidad", + "skillNameInvalid": "El nombre de la habilidad debe comenzar con una letra o un dígito y usar solo letras minúsculas, dígitos y guiones.", + "skillContentRequired": "Se requiere contenido de habilidad", + "skillSaveFailed": "No se pudo guardar la habilidad", + "skillsHint": "Pegue un SKILL.md y su nombre se completará; agregue una habilidad desde su teléfono sin arrastrar y soltar.", + "skillEditTitle": "Editar habilidad", + "mcp": { + "title": "Servidores MCP", + "add": "Agregar servidor MCP", + "empty": "Aún no hay servidores MCP", + "name": "Nombre del servidor", + "type": "Tipo de servidor", + "localType": "Local", + "remoteType": "Remoto", + "command": "línea de comando", + "url": "URL", + "environment": "Entorno (JSON)", + "headers": "Encabezados (JSON)", + "timeout": "Se acabó el tiempo", + "secretCount": "recuento secreto", + "namePlaceholder": "my-server", + "commandPlaceholder": "npx @example/mcp-server", + "jsonPlaceholder": "{\n \"API_KEY\": \"sk-...\"\n}", + "nameRequired": "Introduzca un nombre de servidor", + "nameInvalid": "El nombre solo puede usar letras, números, guiones bajos y guiones.", + "commandRequired": "Introduzca un comando", + "commandTooLong": "Usa como máximo 50 argumentos de 500 caracteres cada uno", + "urlRequired": "Introduce una URL", + "urlInvalid": "Introduce una URL válida", + "urlTooLong": "La URL debe tener 2048 caracteres o menos", + "jsonInvalid": "Ingrese JSON válido: un objeto de valores de cadena", + "jsonTooLarge": "Usa como máximo 50 entradas, claves de hasta 128 y valores de hasta 4096 caracteres", + "timeoutInvalid": "El tiempo de espera debe ser un número entero de milisegundos.", + "edit": "Editar servidor MCP", + "saveFailed": "No se pudo guardar el servidor MCP" + }, + "agents": { + "title": "Agentes de perfil", + "add": "Agregar agente", + "empty": "Aún no hay agentes", + "name": "Nombre del agente", + "slug": "babosa agente", + "description": "Descripción del agente", + "model": "Anulación de modelo", + "modelPlaceholder": "Usar sesión predeterminada", + "prompt": "Aviso del sistema", + "steps": "Pasos máximos", + "stepsPlaceholder": "p. ej. 50", + "temperature": "Temperatura", + "temperaturePlaceholder": "p. ej. 0.2", + "topP": "top_p", + "topPPlaceholder": "p. ej. 0.95", + "slugPlaceholder": "my-agent", + "namePlaceholder": "mi agente", + "promptPlaceholder": "Eres un...", + "tools": "Herramientas", + "visibility": "Visibilidad", + "visibilityPrimary": "Primario: se muestra en el selector", + "visibilitySubagent": "Subagente: solo puede ser invocado por otros", + "visibilityAll": "Todos - ambos", + "saveFailed": "No se pudo guardar al agente", + "nameRequired": "Ingrese el nombre de un agente", + "slugRequired": "Introduzca una babosa de agente", + "slugInvalid": "Slug debe comenzar con una letra y usar solo letras minúsculas, dígitos y guiones.", + "slugConflict": "Esa babosa está reservada por un agente incorporado.", + "edit": "Editar agente" + }, + "slashCommands": { + "title": "Comandos de barra diagonal", + "add": "Agregar comando de barra diagonal", + "empty": "Aún no hay comandos de barra diagonal", + "name": "Nombre del comando", + "description": "Descripción del comando", + "template": "Plantilla", + "namePlaceholder": "explain", + "templatePlaceholder": "Explique $ARGUMENTS", + "agent": "Agente", + "subtask": "Ejecutar como subtarea", + "edit": "Editar comando de barra diagonal", + "saveFailed": "No se pudo guardar el comando", + "nameRequired": "Introduzca un nombre de comando", + "nameInvalid": "El nombre debe comenzar con una letra minúscula y usar solo letras minúsculas, dígitos y guiones.", + "templateRequired": "Ingrese una plantilla de comando", + "nameConflict": "Ese nombre está reservado mediante un comando integrado." + }, + "repoBindings": { + "title": "Perfiles predeterminados para repositorios", + "add": "Agregar valor predeterminado", + "empty": "No hay valores predeterminados configurados", + "emptyDescription": "Agregue un valor predeterminado para aplicar automáticamente un perfil al iniciar sesiones para un repositorio.", + "repo": "Seleccionar repositorio…", + "profile": "Seleccionar perfil", + "remove": "Desatar", + "private": "Privado", + "search": "Buscar repositorios…", + "addedToast": "Perfil predeterminado establecido para \"{{repo}}\"", + "addFailed": "No se pudo establecer el perfil predeterminado", + "removedToast": "Perfil predeterminado eliminado para \"{{repo}}\"", + "removeFailed": "No se pudo eliminar el perfil predeterminado", + "pinnedTitle": "Anclado a repositorios", + "pinRepo": "Anclar un repositorio", + "pinnedEmpty": "No anclado a ningún repositorio.", + "pinnedDescription": "Se carga automáticamente cada vez que una sesión se dirige a uno de estos repositorios.", + "pinnedToast": "Fijado en \"{{repo}}\"", + "pinFailed": "No se pudo fijar el repositorio", + "unboundToast": "Repo sin consolidar", + "unbindFailed": "No se pudo desvincular el repositorio" + } } } diff --git a/apps/mobile/src/i18n/locales/et.json b/apps/mobile/src/i18n/locales/et.json index e029bc0d54a..44a886c491d 100644 --- a/apps/mobile/src/i18n/locales/et.json +++ b/apps/mobile/src/i18n/locales/et.json @@ -2191,7 +2191,33 @@ "continueFrom": "Jätkub seanss „{{title}}“.", "importingSession": "Imporditakse seanssi", "cliCannotContinue": "See CLI ei saa seanssi jätkata. Vali Cloud Agent või mõni muu CLI.", - "importFailed": "Seda seanssi ei õnnestunud importida." + "importFailed": "Seda seanssi ei õnnestunud importida.", + "pickProfile": "Valige profiil", + "noProfile": "Profiili pole", + "manageProfiles": "Profiilide haldamine", + "noProfiles": "Profiilid veel puuduvad. Looge see keskkonnamuutujate, MCP-serverite ja oskuste lisamiseks.", + "advancedConfig": "Täpsem konfiguratsioon", + "advancedConfigDescription": "Valige salvestatud konfiguratsioonide laadimiseks profiil ja konfigureerige käsitsi täiendavaid keskkonnamuutujaid ja häälestuskäske", + "saveAsProfile": "Salvesta profiilina", + "saveProfileDescription": "Salvestage praegused keskkonnamuutujad ja häälestuskäsud korduvkasutatava profiilina.", + "profileSummary": "{{vars}} keskkonnamuutujad · {{commands}} häälestuskäsud", + "profileSaved": "Profiil \"{{name}}\" on salvestatud", + "organizationProfiles": "Organisatsiooni profiilid", + "personalProfiles": "Isiklikud profiilid", + "yourProfiles": "Teie profiilid", + "failedToLoadProfiles": "Profiilide laadimine ebaõnnestus", + "nameTooLong": "Profiili nimi on liiga pikk", + "descriptionTooLong": "Kirjeldus on liiga pikk", + "secretsEncrypted": "Saladused krüpteeritakse enne salvestamist.", + "profileActive": "Profiil aktiivne", + "profilesActive": "Profiilid on aktiivsed", + "customConfigActive": "Kohandatud konfiguratsioon aktiivne", + "configNeedsAttention": "Konfiguratsioon vajab tähelepanu", + "repoProfile": "Repo profiil", + "selectedProfile": "Valitud profiil", + "manualOverrides": "Käsitsi alistamised", + "openSettingsToReview": "Avage ülevaatamiseks Seaded", + "profileSelection": "Profiili valik" }, "partRenderer": { "updatedFileCount_one": "Uuendati {{displayCount}} fail.", @@ -3224,5 +3250,180 @@ "multiplierError": "Sisesta kordaja 1 või suurem", "permissionDenied": "Sul puudub õigus kulutushoiatusi hallata.", "pushOffByCategory": "Teavitustes väljas" + }, + "profiles": { + "title": "Profiilid", + "entrySubtitle": "Keskkonnaseadete taaskasutamine seansside jooksul", + "newProfile": "Uus profiil", + "emptyTitle": "Profiilid veel puuduvad", + "emptyDescription": "Looge profiil keskkonnamuutujate, häälestuskäskude ja oskuste taaskasutamiseks seansside jooksul.", + "loadFailed": "Profiilide laadimine nurjus", + "counts": { + "vars": "{{count}} muutujat", + "mcp": "{{count}} MCP", + "skills": "{{count}} oskust", + "commands": "{{count}} käsku", + "varsShort": "{{count}}m", + "skillsShort": "{{count}}o", + "commandsShort": "{{count}}k" + }, + "nameLabel": "Profiili nimi", + "namePlaceholder": "nt. Taustasüsteemi silumine", + "descriptionLabel": "Profiili kirjeldus", + "descriptionPlaceholder": "Valikuline kirjeldus", + "ownerLabel": "Profiili omanik", + "nameRequired": "Sisestage profiili nimi", + "createAction": "Loo profiil", + "createFailed": "Profiili ei saanud luua", + "createdToast": "Profiili \"{{name}}\" loodud", + "settingsSectionTitle": "Seadistamine", + "defaultSectionTitle": "Vaikeprofiil", + "personalDefaultDescription": "Laaditakse automaatselt, kui ühelgi hoidlal pole kinnitatud profiili.", + "organizationDefaultDescription": "Automaatselt laaditud liikmetele, kellel pole isiklikku vaikeseadet.", + "setAsDefault": "Määra vaikeväärtuseks", + "removeDefault": "Eemalda vaikimisi", + "saveFailed": "Profiili ei saanud salvestada", + "deleteTitle": "Kustuta profiil", + "deleteMessage": "See eemaldab jäädavalt profiili ja selle seaded. Seda ei saa tagasi võtta.", + "deleteFailed": "Profiili ei saanud kustutada", + "deleteBlocked": "Seda profiili kasutab veebihaagi päästik. Esmalt eemaldage see nendelt päästikutelt.", + "deletedToast": "Profiil \"{{name}}\" on kustutatud", + "variablesTitle": "Keskkonnamuutujad", + "addVariable": "Lisa muutuja", + "keyLabel": "Võti", + "keyTooLong": "Võti võib olla kuni 256 tähemärki", + "keyDuplicate": "See võti on profiilis juba olemas", + "valueLabel": "Väärtus", + "secretLabel": "Saladus", + "secrets": { + "markSecret": "Märgi salajaseks", + "masked": "Varjatud väärtus", + "reveal": "Avalda väärtust", + "newValuePlaceholder": "Sisesta uus salajane väärtus", + "valuePlaceholder": "Salajane väärtus (krüpteeritud)" + }, + "variablesEmpty": "Muutujaid veel pole", + "variablesSaveFailed": "Muutujat ei saanud salvestada", + "commandsTitle": "Seadistuskäsud", + "addCommand": "Lisa käsk", + "commandLabel": "Käsk", + "commandPlaceholder": "e.g. pnpm install", + "commandsEmpty": "Seadistuskäske pole veel", + "commandsSaveFailed": "Seadistuskäske ei saanud salvestada", + "moveUp": "Liigu üles", + "moveDown": "Liigu alla", + "skillsTitle": "Oskused", + "addSkill": "Lisa oskusi", + "skillNameLabel": "Oskuste nimi", + "skillMarkdownLabel": "Oskuste sisu", + "skillsEmpty": "Oskusi veel pole", + "skillNameRequired": "Sisestage oskuse nimi", + "skillNameInvalid": "Oskuste nimi peab algama tähe või numbriga ning kasutama ainult väiketähti, numbreid ja sidekriipse", + "skillContentRequired": "Vajalik on oskuste sisu", + "skillSaveFailed": "Oskust ei õnnestunud salvestada", + "skillsHint": "Kleepige fail SKILL.md ja selle nimi täidetakse; lisage oma telefonist oskusi ilma lohistamata.", + "skillEditTitle": "Muuda oskusi", + "mcp": { + "title": "MCP-serverid", + "add": "Lisage MCP-server", + "empty": "MCP-servereid veel pole", + "name": "Serveri nimi", + "type": "Serveri tüüp", + "localType": "Kohalik", + "remoteType": "Kaugjuhtimispult", + "command": "Käsurida", + "url": "URL", + "environment": "Keskkond (JSON)", + "headers": "Päised (JSON)", + "timeout": "Aegumine", + "secretCount": "Salajane arv", + "namePlaceholder": "my-server", + "commandPlaceholder": "npx @example/mcp-server", + "jsonPlaceholder": "{\n \"API_KEY\": \"sk-...\"\n}", + "nameRequired": "Sisestage serveri nimi", + "nameInvalid": "Nimes võib kasutada ainult tähti, numbreid, allkriipse ja sidekriipse", + "commandRequired": "Sisestage käsk", + "commandTooLong": "Kasuta kõige rohkem 50 argumenti, igaüks kuni 500 tähemärki", + "urlRequired": "Sisestage URL", + "urlInvalid": "Sisestage kehtiv URL", + "urlTooLong": "URL võib olla kuni 2048 tähemärki", + "jsonInvalid": "Sisestage kehtiv JSON – stringiväärtuste objekt", + "jsonTooLarge": "Kasuta kõige rohkem 50 kirjet, võtmed kuni 128 ja väärtused kuni 4096 tähemärki", + "timeoutInvalid": "Aegumine peab olema täisarv millisekundeid", + "edit": "Redigeeri MCP-serverit", + "saveFailed": "MCP-serverit ei saanud salvestada" + }, + "agents": { + "title": "Profiiliagendid", + "add": "Lisa agent", + "empty": "Agente veel pole", + "name": "Agendi nimi", + "slug": "Agent nälkjas", + "description": "Agendi kirjeldus", + "model": "Mudeli alistamine", + "modelPlaceholder": "Kasuta seansi vaikeseadet", + "prompt": "Süsteemi viip", + "steps": "Maks. sammu", + "stepsPlaceholder": "nt 50", + "temperature": "Temperatuur", + "temperaturePlaceholder": "nt 0.2", + "topP": "top_p", + "topPPlaceholder": "nt 0.95", + "slugPlaceholder": "my-agent", + "namePlaceholder": "Minu agent", + "promptPlaceholder": "Sa oled...", + "tools": "Tööriistad", + "visibility": "Nähtavus", + "visibilityPrimary": "Esmane – kuvatakse valijas", + "visibilitySubagent": "Subagent — ainult teistelt helistatav", + "visibilityAll": "Kõik - mõlemad", + "saveFailed": "Agenti ei õnnestunud salvestada", + "nameRequired": "Sisestage agendi nimi", + "slugRequired": "Sisestage agent nälkjas", + "slugInvalid": "Slug peab algama tähega ja kasutama ainult väiketähti, numbreid ja sidekriipse", + "slugConflict": "See nälkjas on reserveeritud sisseehitatud agenti poolt", + "edit": "Redigeeri agenti" + }, + "slashCommands": { + "title": "Kaldkriipsuga käsud", + "add": "Lisa kaldkriipsu käsk", + "empty": "Kaldkriipsu käske pole veel", + "name": "Käsu nimi", + "description": "Käsu kirjeldus", + "template": "Mall", + "namePlaceholder": "explain", + "templatePlaceholder": "Selgitage $ARGUMENTS", + "agent": "Agent", + "subtask": "Käivita alamülesandena", + "edit": "Muuda kaldkriipsu käsku", + "saveFailed": "Käsku ei saanud salvestada", + "nameRequired": "Sisestage käsu nimi", + "nameInvalid": "Nimi peab algama väiketähtedega ja kasutama ainult väiketähti, numbreid ja sidekriipse", + "templateRequired": "Sisestage käsumall", + "nameConflict": "See nimi on reserveeritud sisseehitatud käsuga" + }, + "repoBindings": { + "title": "Repode vaikeprofiilid", + "add": "Lisa vaikeseade", + "empty": "Vaikimisi pole konfigureeritud", + "emptyDescription": "Lisage vaikeseade profiili automaatseks rakendamiseks repo seansside alustamisel.", + "repo": "Valige hoidla…", + "profile": "Valige profiil", + "remove": "Vabasta sidumine", + "private": "Privaatne", + "search": "Otsi hoidlaid…", + "addedToast": "Vaikeprofiil on seatud \"{{repo}}\" jaoks", + "addFailed": "Vaikeprofiili ei saanud määrata", + "removedToast": "Vaikeprofiil eemaldati \"{{repo}}\" jaoks", + "removeFailed": "Vaikeprofiili ei saanud eemaldada", + "pinnedTitle": "Kinnitatud hoidlatesse", + "pinRepo": "Kinnitage repo", + "pinnedEmpty": "Pole kinnitatud ühegi hoidla külge.", + "pinnedDescription": "Laaditakse automaatselt, kui seanss sihib mõnda neist hoidlatest.", + "pinnedToast": "Kinnitatud \"{{repo}}\"", + "pinFailed": "Repot ei saanud kinnitada", + "unboundToast": "Repo sidumata", + "unbindFailed": "Repot ei saanud lahti siduda" + } } } diff --git a/apps/mobile/src/i18n/locales/eu.json b/apps/mobile/src/i18n/locales/eu.json index 4b47ea4341a..6ca1eaed752 100644 --- a/apps/mobile/src/i18n/locales/eu.json +++ b/apps/mobile/src/i18n/locales/eu.json @@ -2191,7 +2191,33 @@ "continueFrom": "Saio honekin jarraitzen ari zara: {{title}}.", "importingSession": "Saioa inportatzen", "cliCannotContinue": "CLI honek ezin dio saio bati jarraitu. Aukeratu Cloud Agent edo beste CLI bat.", - "importFailed": "Ezin izan da saio hau inportatu." + "importFailed": "Ezin izan da saio hau inportatu.", + "pickProfile": "Aukeratu profil bat", + "noProfile": "Ez dago profilik", + "manageProfiles": "Kudeatu profilak", + "noProfiles": "Oraindik ez dago profilik. Sortu bat ingurune-aldagaiak, MCP zerbitzariak eta trebetasunak gehitzeko.", + "advancedConfig": "Konfigurazio aurreratua", + "advancedConfigDescription": "Hautatu profil bat gordetako konfigurazioak kargatzeko eta eskuz konfiguratu ingurune-aldagai gehigarriak eta konfigurazio komandoak", + "saveAsProfile": "Gorde profil gisa", + "saveProfileDescription": "Gorde uneko ingurune-aldagaiak eta konfiguratu komandoak profil berrerabilgarri gisa.", + "profileSummary": "{{vars}} ingurune-aldagaiak · {{commands}} konfigurazio komandoak", + "profileSaved": "\"{{name}}\" profila gorde da", + "organizationProfiles": "Erakundeen profilak", + "personalProfiles": "Profil pertsonalak", + "yourProfiles": "Zure profilak", + "failedToLoadProfiles": "Ezin izan dira kargatu profilak", + "nameTooLong": "Profilaren izena luzeegia da", + "descriptionTooLong": "Deskribapena luzeegia da", + "secretsEncrypted": "Sekretuak biltegiratu aurretik zifratzen dira.", + "profileActive": "Profila aktiboa", + "profilesActive": "Profilak aktibo", + "customConfigActive": "Konfigurazio pertsonalizatua aktibo", + "configNeedsAttention": "Konfigurazioak arreta behar du", + "repoProfile": "Repo profila", + "selectedProfile": "Hautatutako profila", + "manualOverrides": "Eskuzko baliogabetzeak", + "openSettingsToReview": "Ireki Ezarpenak berrikusteko", + "profileSelection": "Profila hautatzea" }, "partRenderer": { "updatedFileCount_one": "{{displayCount}} fitxategi eguneratu da.", @@ -3224,5 +3250,180 @@ "multiplierError": "Sartu 1 edo handiagoa den biderkatzaile bat", "permissionDenied": "Ez duzu gastu-alertak kudeatzeko baimenik.", "pushOffByCategory": "Jakinarazpenetan itzalita" + }, + "profiles": { + "title": "Profilak", + "entrySubtitle": "Berrerabili ingurunearen ezarpenak saio guztietan", + "newProfile": "Profil berria", + "emptyTitle": "Oraindik ez dago profilik", + "emptyDescription": "Sortu profil bat ingurune-aldagaiak, konfigurazio komandoak eta trebetasunak saioetan berrerabiltzeko.", + "loadFailed": "Ezin izan dira kargatu profilak", + "counts": { + "vars": "{{count}} aldagai", + "mcp": "{{count}} MCP", + "skills": "{{count}} trebetasun", + "commands": "{{count}} komando", + "varsShort": "{{count}}a", + "skillsShort": "{{count}}t", + "commandsShort": "{{count}}k" + }, + "nameLabel": "Profilaren izena", + "namePlaceholder": "adib. Backend arazketa", + "descriptionLabel": "Profilaren deskribapena", + "descriptionPlaceholder": "Aukerako deskribapena", + "ownerLabel": "Profilaren jabea", + "nameRequired": "Sartu profilaren izena", + "createAction": "Sortu profila", + "createFailed": "Ezin izan da profila sortu", + "createdToast": "\"{{name}}\" profila sortu da", + "settingsSectionTitle": "Konfigurazioa", + "defaultSectionTitle": "Profil lehenetsia", + "personalDefaultDescription": "Automatikoki kargatzen da biltegirik ez dagoenean ainguratuta dagoen profilik.", + "organizationDefaultDescription": "Lehenetsi pertsonalik ez duten kideentzat automatikoki kargatu da.", + "setAsDefault": "Ezarri lehenetsi gisa", + "removeDefault": "Kendu lehenespen gisa", + "saveFailed": "Ezin izan da profila gorde", + "deleteTitle": "Ezabatu profila", + "deleteMessage": "Honek profila eta bere ezarpenak betiko kentzen ditu. Hau ezin da desegin.", + "deleteFailed": "Ezin izan da profila ezabatu", + "deleteBlocked": "Profil hau webhook abiarazle batek erabiltzen du. Kendu abiarazle horietatik lehenik.", + "deletedToast": "\"{{name}}\" profila ezabatu da", + "variablesTitle": "Inguruko aldagaiak", + "addVariable": "Gehitu aldagaia", + "keyLabel": "Gakoa", + "keyTooLong": "Gakoak 256 karaktere edo gutxiago izan behar ditu", + "keyDuplicate": "Gako hau jada badago profilean", + "valueLabel": "Balioa", + "secretLabel": "Sekretua", + "secrets": { + "markSecret": "Markatu sekretu gisa", + "masked": "Ezkutuko balioa", + "reveal": "Erakutsi balioa", + "newValuePlaceholder": "Sartu balio sekretu berria", + "valuePlaceholder": "Balio sekretua (zifratuta)" + }, + "variablesEmpty": "Oraindik ez dago aldagairik", + "variablesSaveFailed": "Ezin izan da aldagaia gorde", + "commandsTitle": "Konfiguratu komandoak", + "addCommand": "Gehitu komandoa", + "commandLabel": "Agindua", + "commandPlaceholder": "e.g. pnpm install", + "commandsEmpty": "Oraindik ez dago konfigurazio komandorik", + "commandsSaveFailed": "Ezin izan dira gorde konfigurazio komandoak", + "moveUp": "Gora", + "moveDown": "Mugitu behera", + "skillsTitle": "Trebetasunak", + "addSkill": "Gehitu trebetasuna", + "skillNameLabel": "Trebetasunaren izena", + "skillMarkdownLabel": "Trebetasun edukia", + "skillsEmpty": "Oraindik ez dago gaitasunik", + "skillNameRequired": "Sartu trebetasun-izen bat", + "skillNameInvalid": "Trebetasunaren izenak letra edo zifra batekin hasi behar du eta letra xeheak, zifrak eta marratxoak soilik erabili behar ditu", + "skillContentRequired": "Trebetasun edukia beharrezkoa da", + "skillSaveFailed": "Ezin izan da trebetasuna gorde", + "skillsHint": "Itsatsi SKILL.md bat eta bere izena beteko da; gehitu trebetasun bat telefonotik arrastatu eta jaregin gabe.", + "skillEditTitle": "Editatu trebetasuna", + "mcp": { + "title": "MCP zerbitzariak", + "add": "Gehitu MCP zerbitzaria", + "empty": "Oraindik ez dago MCP zerbitzaririk", + "name": "Zerbitzariaren izena", + "type": "Zerbitzari mota", + "localType": "Tokikoa", + "remoteType": "Urrunekoa", + "command": "Komando-lerroa", + "url": "URLa", + "environment": "Ingurumena (JSON)", + "headers": "Goiburuak (JSON)", + "timeout": "Denbora-muga", + "secretCount": "Zenbaketa sekretua", + "namePlaceholder": "my-server", + "commandPlaceholder": "npx @example/mcp-server", + "jsonPlaceholder": "{\n \"API_KEY\": \"sk-...\"\n}", + "nameRequired": "Sartu zerbitzariaren izena", + "nameInvalid": "Izenak hizkiak, zenbakiak, azpimarra eta marra soilik erabil ditzake", + "commandRequired": "Sartu komando bat", + "commandTooLong": "Erabili gehienez 50 argumentu, bakoitza 500 karakterekoa", + "urlRequired": "Sartu URL bat", + "urlInvalid": "Sartu baliozko URL bat", + "urlTooLong": "URLak 2048 karaktere edo gutxiago izan behar ditu", + "jsonInvalid": "Sartu baliozko JSON - kate-balioen objektua", + "jsonTooLarge": "Erabili gehienez 50 sarrera, gakoak 128 arte eta balioak 4096 karaktere arte", + "timeoutInvalid": "Denbora-mugak milisegundo kopuru osoa izan behar du", + "edit": "Editatu MCP zerbitzaria", + "saveFailed": "Ezin izan da MCP zerbitzaria gorde" + }, + "agents": { + "title": "Profileko agenteak", + "add": "Gehitu agentea", + "empty": "Agenterik ez oraindik", + "name": "Agentearen izena", + "slug": "Agente barea", + "description": "Agentearen deskribapena", + "model": "Eredua gainidaztea", + "modelPlaceholder": "Erabili saioa lehenetsia", + "prompt": "Sistemaren gonbita", + "steps": "Gehieneko urratsak", + "stepsPlaceholder": "adib. 50", + "temperature": "Tenperatura", + "temperaturePlaceholder": "adib. 0.2", + "topP": "top_p", + "topPPlaceholder": "adib. 0.95", + "slugPlaceholder": "my-agent", + "namePlaceholder": "Nire agentea", + "promptPlaceholder": "bat zara...", + "tools": "Tresnak", + "visibility": "Ikusgarritasuna", + "visibilityPrimary": "Lehen mailakoa: hautatzailean agertzen da", + "visibilitySubagent": "Azpi-agentea - besteengandik bakarrik dei daiteke", + "visibilityAll": "Guztiak - biak", + "saveFailed": "Ezin izan da agentea gorde", + "nameRequired": "Sartu agentearen izena", + "slugRequired": "Sartu agente bare bat", + "slugInvalid": "Slug-ek letra batekin hasi behar du eta letra xeheak, zifrak eta marratxoak soilik erabili behar ditu", + "slugConflict": "Bare hori integratutako agente batek erreserbatzen du", + "edit": "Editatu agentea" + }, + "slashCommands": { + "title": "Barra komandoak", + "add": "Gehitu barra komandoa", + "empty": "Oraindik ez dago barra-komandorik", + "name": "Komandoaren izena", + "description": "Komandoaren deskribapena", + "template": "Txantiloia", + "namePlaceholder": "explain", + "templatePlaceholder": "Azaldu $ARGUMENTS", + "agent": "Agentea", + "subtask": "Exekutatu azpizeregin gisa", + "edit": "Editatu barra komandoa", + "saveFailed": "Ezin izan da gorde komandoa", + "nameRequired": "Sartu komando-izen bat", + "nameInvalid": "Izenak letra xehe batekin hasi behar du eta letra xeheak, zifrak eta marra baino ez ditu erabili", + "templateRequired": "Sartu komando txantiloia", + "nameConflict": "Izen hori barneko komando batek erreserbatzen du" + }, + "repoBindings": { + "title": "Repos-en profil lehenetsiak", + "add": "Gehitu lehenetsia", + "empty": "Ez dago lehenetsirik konfiguratuta", + "emptyDescription": "Gehitu lehenetsitako profil bat automatikoki aplikatzeko repositegi baterako saioak hastean.", + "repo": "Hautatu biltegia…", + "profile": "Hautatu profila", + "remove": "Askatu", + "private": "Pribatua", + "search": "Bilatu biltegiak...", + "addedToast": "\"{{repo}}\"-ren profil lehenetsia ezarri da", + "addFailed": "Ezin izan da profil lehenetsia ezarri", + "removedToast": "\"{{repo}}\"-ren profil lehenetsia kendu da", + "removeFailed": "Ezin izan da profil lehenetsia kendu", + "pinnedTitle": "Biltegietan ainguratuta", + "pinRepo": "Ainguratu repo bat", + "pinnedEmpty": "Ez dago ainguratuta inongo biltegietan.", + "pinnedDescription": "Automatikoki kargatzen da saio bat biltegi horietako bat helburu duen bakoitzean.", + "pinnedToast": "\"{{repo}}\"-n ainguratuta", + "pinFailed": "Ezin izan da repo ainguratu", + "unboundToast": "Repo lotu gabe", + "unbindFailed": "Ezin izan da biltegia deslotu" + } } } diff --git a/apps/mobile/src/i18n/locales/fa.json b/apps/mobile/src/i18n/locales/fa.json index b3dd388c02b..73bfec7b4c1 100644 --- a/apps/mobile/src/i18n/locales/fa.json +++ b/apps/mobile/src/i18n/locales/fa.json @@ -2191,7 +2191,33 @@ "continueFrom": "نشست {{title}} را ادامه می‌دهید.", "importingSession": "در حال وارد کردن نشست", "cliCannotContinue": "این CLI نمی‌تواند یک نشست را ادامه دهد. Cloud Agent یا CLI دیگری را انتخاب کنید.", - "importFailed": "وارد کردن این نشست ممکن نشد." + "importFailed": "وارد کردن این نشست ممکن نشد.", + "pickProfile": "یک نمایه انتخاب کنید", + "noProfile": "بدون پروفایل", + "manageProfiles": "مدیریت پروفایل ها", + "noProfiles": "هنوز پروفایلی وجود ندارد یکی برای اضافه کردن متغیرهای محیطی، سرورهای MCP و مهارت‌ها ایجاد کنید.", + "advancedConfig": "پیکربندی پیشرفته", + "advancedConfigDescription": "یک نمایه را برای بارگیری تنظیمات ذخیره شده انتخاب کنید و متغیرهای محیط اضافی و دستورات راه اندازی را به صورت دستی پیکربندی کنید.", + "saveAsProfile": "ذخیره به عنوان نمایه", + "saveProfileDescription": "متغیرهای محیط فعلی و دستورات راه اندازی را به عنوان یک نمایه قابل استفاده مجدد ذخیره کنید.", + "profileSummary": "{{vars}} متغیرهای محیطی · دستورات تنظیم {{commands}}", + "profileSaved": "نمایه \"{{name}}\" ذخیره شد", + "organizationProfiles": "نمایه های سازمان", + "personalProfiles": "پروفایل های شخصی", + "yourProfiles": "پروفایل های شما", + "failedToLoadProfiles": "نمایه ها بارگیری نشد", + "nameTooLong": "نام نمایه خیلی طولانی است", + "descriptionTooLong": "توضیحات خیلی طولانی است", + "secretsEncrypted": "اسرار قبل از ذخیره سازی رمزگذاری می شوند.", + "profileActive": "نمایه فعال", + "profilesActive": "پروفایل های فعال", + "customConfigActive": "پیکربندی سفارشی فعال است", + "configNeedsAttention": "پیکربندی نیاز به توجه دارد", + "repoProfile": "نمایه رپو", + "selectedProfile": "نمایه انتخاب شده", + "manualOverrides": "لغو دستی", + "openSettingsToReview": "برای بازبینی تنظیمات را باز کنید", + "profileSelection": "انتخاب نمایه" }, "partRenderer": { "updatedFileCount_one": "{{displayCount}} فایل به‌روزرسانی شد.", @@ -3224,5 +3250,180 @@ "multiplierError": "ضریبی برابر ۱ یا بیشتر وارد کن", "permissionDenied": "شما اجازهٔ مدیریت هشدارهای هزینه را ندارید.", "pushOffByCategory": "در اعلان‌ها خاموش" + }, + "profiles": { + "title": "پروفایل ها", + "entrySubtitle": "استفاده مجدد از تنظیمات محیط در جلسات", + "newProfile": "پروفایل جدید", + "emptyTitle": "هنوز پروفایلی وجود ندارد", + "emptyDescription": "یک نمایه برای استفاده مجدد از متغیرهای محیطی، دستورات راه‌اندازی و مهارت‌ها در جلسات ایجاد کنید.", + "loadFailed": "نمایه ها بارگیری نشد", + "counts": { + "vars": "{{count}} متغیر", + "mcp": "{{count}} MCP", + "skills": "{{count}} مهارت", + "commands": "{{count}} فرمان", + "varsShort": "{{count}}متغ", + "skillsShort": "{{count}}مهار", + "commandsShort": "{{count}}ف" + }, + "nameLabel": "نام پروفایل", + "namePlaceholder": "به عنوان مثال اشکال زدایی باطن", + "descriptionLabel": "توضیحات پروفایل", + "descriptionPlaceholder": "توضیحات اختیاری", + "ownerLabel": "مالک نمایه", + "nameRequired": "یک نام نمایه وارد کنید", + "createAction": "ایجاد نمایه", + "createFailed": "نمایه ایجاد نشد", + "createdToast": "نمایه \"{{name}}\" ایجاد شد", + "settingsSectionTitle": "پیکربندی", + "defaultSectionTitle": "نمایه پیش فرض", + "personalDefaultDescription": "زمانی که هیچ مخزنی نمایه پین شده نداشته باشد، به صورت خودکار بارگذاری می شود.", + "organizationDefaultDescription": "بارگیری خودکار برای اعضایی که هیچ پیش فرض شخصی ندارند.", + "setAsDefault": "به عنوان پیش فرض تنظیم کنید", + "removeDefault": "به عنوان پیش فرض حذف کنید", + "saveFailed": "نمایه ذخیره نشد", + "deleteTitle": "حذف نمایه", + "deleteMessage": "با این کار نمایه و تنظیمات آن برای همیشه حذف می شود. این قابل واگرد نیست.", + "deleteFailed": "نمایه حذف نشد", + "deleteBlocked": "این نمایه توسط یک ماشه وب هوک استفاده می شود. ابتدا آن را از آن محرک ها حذف کنید.", + "deletedToast": "نمایه \"{{name}}\" حذف شد", + "variablesTitle": "متغیرهای محیطی", + "addVariable": "متغیر اضافه کنید", + "keyLabel": "کلید", + "keyTooLong": "کلید باید 256 نویسه یا کمتر باشد", + "keyDuplicate": "این کلید از قبل در نمایه وجود دارد", + "valueLabel": "ارزش", + "secretLabel": "راز", + "secrets": { + "markSecret": "علامت گذاری به عنوان راز", + "masked": "ارزش پنهان", + "reveal": "ارزش را آشکار کند", + "newValuePlaceholder": "مقدار مخفی جدید را وارد کنید", + "valuePlaceholder": "مقدار مخفی (رمزگذاری شده)" + }, + "variablesEmpty": "هنوز هیچ متغیری وجود ندارد", + "variablesSaveFailed": "متغیر ذخیره نشد", + "commandsTitle": "دستورات راه اندازی", + "addCommand": "دستور اضافه کنید", + "commandLabel": "فرمان", + "commandPlaceholder": "e.g. pnpm install", + "commandsEmpty": "هنوز دستور راه اندازی وجود ندارد", + "commandsSaveFailed": "دستورات راه اندازی ذخیره نشد", + "moveUp": "حرکت به بالا", + "moveDown": "به پایین حرکت کنید", + "skillsTitle": "مهارت ها", + "addSkill": "مهارت را اضافه کنید", + "skillNameLabel": "نام مهارت", + "skillMarkdownLabel": "محتوای مهارتی", + "skillsEmpty": "هنوز هیچ مهارتی وجود ندارد", + "skillNameRequired": "یک نام مهارت وارد کنید", + "skillNameInvalid": "نام مهارت باید با یک حرف یا رقم شروع شود و فقط از حروف کوچک، ارقام و خط تیره استفاده کند.", + "skillContentRequired": "محتوای مهارتی مورد نیاز است", + "skillSaveFailed": "مهارت ذخیره نشد", + "skillsHint": "یک SKILL.md قرار دهید و نام آن پر می شود. یک مهارت از تلفن خود بدون کشیدن و رها کردن اضافه کنید.", + "skillEditTitle": "مهارت ویرایش", + "mcp": { + "title": "سرورهای MCP", + "add": "سرور MCP را اضافه کنید", + "empty": "هنوز هیچ سرور MCP وجود ندارد", + "name": "نام سرور", + "type": "نوع سرور", + "localType": "محلی", + "remoteType": "از راه دور", + "command": "خط فرمان", + "url": "URL", + "environment": "محیط زیست (JSON)", + "headers": "سرصفحه ها (JSON)", + "timeout": "تایم اوت", + "secretCount": "شمارش مخفی", + "namePlaceholder": "my-server", + "commandPlaceholder": "npx @example/mcp-server", + "jsonPlaceholder": "{\n \"API_KEY\": \"sk-...\"\n}", + "nameRequired": "نام سرور را وارد کنید", + "nameInvalid": "نام فقط می تواند از حروف، اعداد، زیرخط و خط تیره استفاده کند", + "commandRequired": "دستوری را وارد کنید", + "commandTooLong": "حداکثر 50 آرگومان با 500 نویسه استفاده کنید", + "urlRequired": "یک URL وارد کنید", + "urlInvalid": "یک URL معتبر وارد کنید", + "urlTooLong": "نشانی URL باید 2048 نویسه یا کمتر باشد", + "jsonInvalid": "JSON معتبر وارد کنید - یک شی از مقادیر رشته", + "jsonTooLarge": "حداکثر 50 ورودی، کلیدها تا 128 و مقادیر تا 4096 نویسه استفاده کنید", + "timeoutInvalid": "تایم اوت باید چندین میلی ثانیه باشد", + "edit": "ویرایش سرور MCP", + "saveFailed": "سرور MCP ذخیره نشد" + }, + "agents": { + "title": "نمایندگان پروفایل", + "add": "عامل اضافه کنید", + "empty": "هنوز هیچ نماینده ای وجود ندارد", + "name": "نام نماینده", + "slug": "راب عامل", + "description": "توضیحات نماینده", + "model": "لغو مدل", + "modelPlaceholder": "از جلسه پیش فرض استفاده کنید", + "prompt": "اعلان سیستم", + "steps": "حداکثر گام‌ها", + "stepsPlaceholder": "مثلاً 50", + "temperature": "دما", + "temperaturePlaceholder": "مثلاً 0.2", + "topP": "top_p", + "topPPlaceholder": "مثلاً 0.95", + "slugPlaceholder": "my-agent", + "namePlaceholder": "نماینده من", + "promptPlaceholder": "شما یک ...", + "tools": "ابزار", + "visibility": "دید", + "visibilityPrimary": "اولیه - در انتخابگر نشان داده شده است", + "visibilitySubagent": "Subagent - فقط از دیگران قابل تماس است", + "visibilityAll": "همه - هر دو", + "saveFailed": "عامل ذخیره نشد", + "nameRequired": "یک نام نماینده وارد کنید", + "slugRequired": "یک اسلاگ عامل را وارد کنید", + "slugInvalid": "Slug باید با یک حرف شروع شود و فقط از حروف کوچک، اعداد و خط تیره استفاده کند", + "slugConflict": "آن راب توسط یک عامل داخلی رزرو شده است", + "edit": "عامل ویرایش" + }, + "slashCommands": { + "title": "دستورات اسلش", + "add": "دستور اسلش را اضافه کنید", + "empty": "هنوز دستور اسلش وجود ندارد", + "name": "نام فرمان", + "description": "توضیحات فرمان", + "template": "الگو", + "namePlaceholder": "explain", + "templatePlaceholder": "$ARGUMENTS را توضیح دهید", + "agent": "عامل", + "subtask": "به عنوان وظیفه فرعی اجرا شود", + "edit": "ویرایش دستور اسلش", + "saveFailed": "فرمان ذخیره نشد", + "nameRequired": "یک نام فرمان وارد کنید", + "nameInvalid": "نام باید با یک حرف کوچک شروع شود و فقط از حروف کوچک، ارقام و خط تیره استفاده کند", + "templateRequired": "یک قالب فرمان را وارد کنید", + "nameConflict": "این نام توسط یک دستور داخلی رزرو شده است" + }, + "repoBindings": { + "title": "نمایه های پیش فرض برای مخازن", + "add": "پیش فرض را اضافه کنید", + "empty": "هیچ پیش فرضی پیکربندی نشده است", + "emptyDescription": "یک پیش‌فرض اضافه کنید تا به‌طور خودکار یک نمایه هنگام شروع جلسات برای مخزن اعمال شود.", + "repo": "انتخاب مخزن…", + "profile": "پروفایل را انتخاب کنید", + "remove": "باز کردن", + "private": "خصوصی", + "search": "جستجو در مخازن…", + "addedToast": "نمایه پیش فرض برای \"{{repo}}\" تنظیم شد", + "addFailed": "نمایه پیش‌فرض تنظیم نشد", + "removedToast": "نمایه پیش فرض برای \"{{repo}}\" حذف شد", + "removeFailed": "نمایه پیش فرض حذف نشد", + "pinnedTitle": "به مخازن پین شده است", + "pinRepo": "یک مخزن سنجاق کنید", + "pinnedEmpty": "به هیچ مخزنی پین نشده است.", + "pinnedDescription": "هر زمان که جلسه ای یکی از این مخازن را هدف قرار دهد، به طور خودکار بارگذاری می شود.", + "pinnedToast": "به \"{{repo}}\" پین شد", + "pinFailed": "Repo پین نشد", + "unboundToast": "Repo بدون محدودیت", + "unbindFailed": "Repo لغو پیوند نشد" + } } } diff --git a/apps/mobile/src/i18n/locales/fi.json b/apps/mobile/src/i18n/locales/fi.json index dc26bfb2e87..dbbf95600c6 100644 --- a/apps/mobile/src/i18n/locales/fi.json +++ b/apps/mobile/src/i18n/locales/fi.json @@ -2191,7 +2191,33 @@ "continueFrom": "Jatketaan istuntoa: {{title}}.", "importingSession": "Tuodaan istuntoa", "cliCannotContinue": "Tämä CLI ei voi jatkaa istuntoa. Valitse Cloud Agent tai toinen CLI.", - "importFailed": "Tätä istuntoa ei voitu tuoda." + "importFailed": "Tätä istuntoa ei voitu tuoda.", + "pickProfile": "Valitse profiili", + "noProfile": "Ei profiilia", + "manageProfiles": "Hallitse profiileja", + "noProfiles": "Ei vielä profiileja. Luo sellainen, jonka avulla voit lisätä ympäristömuuttujia, MCP-palvelimia ja taitoja.", + "advancedConfig": "Lisäasetukset", + "advancedConfigDescription": "Valitse profiili ladataksesi tallennetut kokoonpanot ja määritä manuaalisesti lisäympäristömuuttujat ja asennuskomennot", + "saveAsProfile": "Tallenna profiilina", + "saveProfileDescription": "Tallenna nykyiset ympäristömuuttujat ja asetuskomennot uudelleen käytettäväksi profiiliksi.", + "profileSummary": "{{vars}} ympäristömuuttujat · {{commands}} asennuskomennot", + "profileSaved": "Profiili \"{{name}}\" tallennettu", + "organizationProfiles": "Organisaatioprofiilit", + "personalProfiles": "Henkilökohtaiset profiilit", + "yourProfiles": "Omat profiilisi", + "failedToLoadProfiles": "Profiilien lataaminen epäonnistui", + "nameTooLong": "Profiilin nimi on liian pitkä", + "descriptionTooLong": "Kuvaus on liian pitkä", + "secretsEncrypted": "Salaisuudet salataan ennen tallennusta.", + "profileActive": "Profiili aktiivinen", + "profilesActive": "Profiilit käytössä", + "customConfigActive": "Mukautettu konfiguraatio aktiivinen", + "configNeedsAttention": "Konfiguraatio vaatii huomiota", + "repoProfile": "Repo-profiili", + "selectedProfile": "Valittu profiili", + "manualOverrides": "Manuaaliset ohitukset", + "openSettingsToReview": "Avaa Asetukset tarkistaaksesi", + "profileSelection": "Profiilin valinta" }, "partRenderer": { "updatedFileCount_one": "{{displayCount}} tiedosto päivitettiin.", @@ -3224,5 +3250,180 @@ "multiplierError": "Anna kerroin, joka on 1 tai suurempi", "permissionDenied": "Sinulla ei ole oikeutta hallita kulutusvaroituksia.", "pushOffByCategory": "Pois päältä Ilmoituksissa" + }, + "profiles": { + "title": "Profiilit", + "entrySubtitle": "Käytä ympäristöasetuksia uudelleen istuntojen aikana", + "newProfile": "Uusi profiili", + "emptyTitle": "Ei vielä profiileja", + "emptyDescription": "Luo profiili käyttääksesi uudelleen ympäristömuuttujia, asetuskomentoja ja taitoja istuntojen aikana.", + "loadFailed": "Profiilien lataaminen epäonnistui", + "counts": { + "vars": "{{count}} muuttujaa", + "mcp": "{{count}} MCP", + "skills": "{{count}} taitoa", + "commands": "{{count}} komentoa", + "varsShort": "{{count}}m", + "skillsShort": "{{count}}t", + "commandsShort": "{{count}}k" + }, + "nameLabel": "Profiilin nimi", + "namePlaceholder": "esim. Taustavirheenkorjaus", + "descriptionLabel": "Profiilin kuvaus", + "descriptionPlaceholder": "Valinnainen kuvaus", + "ownerLabel": "Profiilin omistaja", + "nameRequired": "Anna profiilin nimi", + "createAction": "Luo profiili", + "createFailed": "Profiilia ei voitu luoda", + "createdToast": "Profiili \"{{name}}\" luotu", + "settingsSectionTitle": "Kokoonpano", + "defaultSectionTitle": "Oletusprofiili", + "personalDefaultDescription": "Ladataan automaattisesti, kun missään arkistossa ei ole kiinnitettyä profiilia.", + "organizationDefaultDescription": "Ladataan automaattisesti jäsenille, joilla ei ole henkilökohtaista oletusarvoa.", + "setAsDefault": "Aseta oletukseksi", + "removeDefault": "Poista oletuksena", + "saveFailed": "Profiilia ei voitu tallentaa", + "deleteTitle": "Poista profiili", + "deleteMessage": "Tämä poistaa profiilin ja sen asetukset pysyvästi. Tätä ei voi kumota.", + "deleteFailed": "Profiilia ei voitu poistaa", + "deleteBlocked": "Tätä profiilia käyttää webhook-laukaisin. Poista se ensin noista laukaisimista.", + "deletedToast": "Profiili \"{{name}}\" poistettu", + "variablesTitle": "Ympäristömuuttujat", + "addVariable": "Lisää muuttuja", + "keyLabel": "Avain", + "keyTooLong": "Avaimessa saa olla enintään 256 merkkiä", + "keyDuplicate": "Tämä avain on jo profiilissa", + "valueLabel": "Arvo", + "secretLabel": "Salaisuus", + "secrets": { + "markSecret": "Merkitse salaiseksi", + "masked": "Piilotettu arvo", + "reveal": "Paljasta arvo", + "newValuePlaceholder": "Syötä uusi salainen arvo", + "valuePlaceholder": "Salainen arvo (salattu)" + }, + "variablesEmpty": "Ei vielä muuttujia", + "variablesSaveFailed": "Muuttujan tallentaminen epäonnistui", + "commandsTitle": "Asennuskomennot", + "addCommand": "Lisää komento", + "commandLabel": "Komento", + "commandPlaceholder": "e.g. pnpm install", + "commandsEmpty": "Ei vielä asennuskomentoja", + "commandsSaveFailed": "Asennuskomentoja ei voitu tallentaa", + "moveUp": "Siirrä ylös", + "moveDown": "Siirry alas", + "skillsTitle": "Taidot", + "addSkill": "Lisää taitoa", + "skillNameLabel": "Taidon nimi", + "skillMarkdownLabel": "Ammattitaitoinen sisältö", + "skillsEmpty": "Ei taitoja vielä", + "skillNameRequired": "Anna taidon nimi", + "skillNameInvalid": "Taidon nimen tulee alkaa kirjaimella tai numerolla ja käyttää vain pieniä kirjaimia, numeroita ja väliviivoja", + "skillContentRequired": "Ammattitaitoinen sisältö vaaditaan", + "skillSaveFailed": "Taitoa ei voitu säästää", + "skillsHint": "Liitä SKILL.md ja sen nimi täyttyy; lisää taito puhelimestasi ilman vetämistä ja pudotusta.", + "skillEditTitle": "Muokkaa taitoa", + "mcp": { + "title": "MCP-palvelimet", + "add": "Lisää MCP-palvelin", + "empty": "Ei vielä MCP-palvelimia", + "name": "Palvelimen nimi", + "type": "Palvelimen tyyppi", + "localType": "Paikallinen", + "remoteType": "Kauko", + "command": "Komentorivi", + "url": "URL-osoite", + "environment": "Ympäristö (JSON)", + "headers": "Otsikot (JSON)", + "timeout": "Aikakatkaisu", + "secretCount": "Salainen laskelma", + "namePlaceholder": "my-server", + "commandPlaceholder": "npx @example/mcp-server", + "jsonPlaceholder": "{\n \"API_KEY\": \"sk-...\"\n}", + "nameRequired": "Anna palvelimen nimi", + "nameInvalid": "Nimessä voi käyttää vain kirjaimia, numeroita, alaviivoja ja väliviivoja", + "commandRequired": "Anna komento", + "commandTooLong": "Käytä enintään 50 argumenttia, kukin 500 merkkiä", + "urlRequired": "Anna URL-osoite", + "urlInvalid": "Anna kelvollinen URL-osoite", + "urlTooLong": "URL-osoitteessa saa olla enintään 2048 merkkiä", + "jsonInvalid": "Anna kelvollinen JSON — merkkijonoarvojen objekti", + "jsonTooLarge": "Käytä enintään 50 merkintää, avaimet enintään 128 ja arvot enintään 4096 merkkiä", + "timeoutInvalid": "Aikakatkaisun on oltava millisekuntien kokonaisluku", + "edit": "Muokkaa MCP-palvelinta", + "saveFailed": "MCP-palvelinta ei voitu tallentaa" + }, + "agents": { + "title": "Profiiliagentit", + "add": "Lisää agentti", + "empty": "Ei agentteja vielä", + "name": "Agentin nimi", + "slug": "Agentti etana", + "description": "Agentin kuvaus", + "model": "Mallin ohitus", + "modelPlaceholder": "Käytä istunnon oletusarvoa", + "prompt": "Järjestelmäkehote", + "steps": "Enimmäisvaiheet", + "stepsPlaceholder": "esim. 50", + "temperature": "Lämpötila", + "temperaturePlaceholder": "esim. 0.2", + "topP": "top_p", + "topPPlaceholder": "esim. 0.95", + "slugPlaceholder": "my-agent", + "namePlaceholder": "Agenttini", + "promptPlaceholder": "Olet...", + "tools": "Työkalut", + "visibility": "Näkyvyys", + "visibilityPrimary": "Ensisijainen – näkyy poimijassa", + "visibilitySubagent": "Subagent - vain soitettavissa muilta", + "visibilityAll": "Kaikki - molemmat", + "saveFailed": "Agenttia ei voitu tallentaa", + "nameRequired": "Anna agentin nimi", + "slugRequired": "Syötä agentti etana", + "slugInvalid": "Slugin tulee alkaa kirjaimella ja käyttää vain pieniä kirjaimia, numeroita ja väliviivoja", + "slugConflict": "Sisäänrakennettu agentti on varannut tuon etanan", + "edit": "Muokkaa agenttia" + }, + "slashCommands": { + "title": "Slash komennot", + "add": "Lisää vinoviiva komento", + "empty": "Ei vinoviivakomentoja vielä", + "name": "Komennon nimi", + "description": "Komennon kuvaus", + "template": "Malli", + "namePlaceholder": "explain", + "templatePlaceholder": "Selitä $ARGUMENTS", + "agent": "Agentti", + "subtask": "Suorita alitehtävänä", + "edit": "Muokkaa vinoviivakomentoa", + "saveFailed": "Komentoa ei voitu tallentaa", + "nameRequired": "Anna komennon nimi", + "nameInvalid": "Nimen tulee alkaa pienellä kirjaimella ja käyttää vain pieniä kirjaimia, numeroita ja väliviivoja", + "templateRequired": "Anna komentomalli", + "nameConflict": "Tämä nimi on varattu sisäänrakennetulla komennolla" + }, + "repoBindings": { + "title": "Repojen oletusprofiilit", + "add": "Lisää oletusarvo", + "empty": "Ei oletusasetuksia määritetty", + "emptyDescription": "Lisää oletusasetus, joka ottaa profiilin käyttöön automaattisesti, kun repo-istunnot aloitetaan.", + "repo": "Valitse arkisto…", + "profile": "Valitse profiili", + "remove": "Pura sidonta", + "private": "Yksityinen", + "search": "Hae arkistot…", + "addedToast": "Oletusprofiili asetettu kohteelle \"{{repo}}\"", + "addFailed": "Oletusprofiilia ei voitu asettaa", + "removedToast": "Oletusprofiili poistettu kohteelta \"{{repo}}\"", + "removeFailed": "Oletusprofiilia ei voitu poistaa", + "pinnedTitle": "Kiinnitetty arkistoihin", + "pinRepo": "Kiinnitä repo", + "pinnedEmpty": "Ei kiinnitetty mihinkään arkistoon.", + "pinnedDescription": "Ladataan automaattisesti aina, kun istunto kohdistuu johonkin näistä arkistoista.", + "pinnedToast": "Kiinnitetty kohtaan \"{{repo}}\"", + "pinFailed": "Repoa ei voitu kiinnittää", + "unboundToast": "Repo sitomaton", + "unbindFailed": "Repoa ei voitu purkaa" + } } } diff --git a/apps/mobile/src/i18n/locales/fil.json b/apps/mobile/src/i18n/locales/fil.json index c87d4090495..18d51989107 100644 --- a/apps/mobile/src/i18n/locales/fil.json +++ b/apps/mobile/src/i18n/locales/fil.json @@ -2191,7 +2191,33 @@ "continueFrom": "Ipinagpapatuloy ang {{title}}.", "importingSession": "Ini-import ang sesyon", "cliCannotContinue": "Hindi kayang magpatuloy ng sesyon ang CLI na ito. Pumili ng Cloud Agent o ibang CLI.", - "importFailed": "Hindi ma-import ang sesyong ito." + "importFailed": "Hindi ma-import ang sesyong ito.", + "pickProfile": "Pumili ng profile", + "noProfile": "Walang profile", + "manageProfiles": "Pamahalaan ang mga profile", + "noProfiles": "Wala pang mga profile. Gumawa ng isa upang magdagdag ng mga variable ng kapaligiran, mga server ng MCP, at mga kasanayan.", + "advancedConfig": "Advanced na kompigurasyon", + "advancedConfigDescription": "Pumili ng profile upang i-load ang mga naka-save na configuration, at manu-manong i-configure ang mga karagdagang environment variable at setup command", + "saveAsProfile": "I-save bilang Profile", + "saveProfileDescription": "I-save ang kasalukuyang mga variable ng kapaligiran at mga command sa pag-setup bilang isang reusable na profile.", + "profileSummary": "{{vars}} variable ng kapaligiran · {{commands}} utos sa pag-setup", + "profileSaved": "Na-save ang profile na \"{{name}}\".", + "organizationProfiles": "Mga Profile ng Organisasyon", + "personalProfiles": "Mga Personal na Profile", + "yourProfiles": "Iyong mga Profile", + "failedToLoadProfiles": "Nabigong i-load ang mga profile", + "nameTooLong": "Masyadong mahaba ang pangalan ng profile", + "descriptionTooLong": "Masyadong mahaba ang paglalarawan", + "secretsEncrypted": "Ang mga lihim ay naka-encrypt bago imbakan.", + "profileActive": "Aktibo ang profile", + "profilesActive": "Aktibo ang mga profile", + "customConfigActive": "Aktibo ang custom na config", + "configNeedsAttention": "Ang configuration ay nangangailangan ng pansin", + "repoProfile": "Profile ng repo", + "selectedProfile": "Napiling profile", + "manualOverrides": "Mga manu-manong override", + "openSettingsToReview": "Buksan ang Mga Setting para suriin", + "profileSelection": "Pagpili ng profile" }, "partRenderer": { "updatedFileCount_one": "Na-update ang {{displayCount}} na file.", @@ -3224,5 +3250,180 @@ "multiplierError": "Maglagay ng multiplier na 1 o higit pa", "permissionDenied": "Wala kang pahintulot na pamahalaan ang mga babala sa gastos.", "pushOffByCategory": "Naka-off sa Mga abiso" + }, + "profiles": { + "title": "Mga profile", + "entrySubtitle": "Muling gamitin ang mga setting ng kapaligiran sa mga session", + "newProfile": "Bagong profile", + "emptyTitle": "Wala pang mga profile", + "emptyDescription": "Gumawa ng profile para magamit muli ang mga variable ng kapaligiran, mga command sa pag-setup, at mga kasanayan sa mga session.", + "loadFailed": "Hindi ma-load ang mga profile", + "counts": { + "vars": "{{count}} variable", + "mcp": "{{count}} MCP", + "skills": "{{count}} kasanayan", + "commands": "{{count}} utos", + "varsShort": "{{count}}v", + "skillsShort": "{{count}}k", + "commandsShort": "{{count}}u" + }, + "nameLabel": "Pangalan ng profile", + "namePlaceholder": "hal. Backend debugging", + "descriptionLabel": "Paglalarawan ng profile", + "descriptionPlaceholder": "Opsyonal na paglalarawan", + "ownerLabel": "May-ari ng profile", + "nameRequired": "Maglagay ng pangalan ng profile", + "createAction": "Lumikha ng profile", + "createFailed": "Hindi makalikha ng profile", + "createdToast": "Nagawa ang profile na \"{{name}}\".", + "settingsSectionTitle": "Kompigurasyon", + "defaultSectionTitle": "Default na profile", + "personalDefaultDescription": "Awtomatikong na-load kapag walang repository na may naka-pin na profile.", + "organizationDefaultDescription": "Awtomatikong na-load para sa mga miyembrong walang personal na default.", + "setAsDefault": "Itakda bilang default", + "removeDefault": "Alisin bilang default", + "saveFailed": "Hindi ma-save ang profile", + "deleteTitle": "Tanggalin ang profile", + "deleteMessage": "Permanente nitong inaalis ang profile at mga setting nito. Hindi na ito maaaring bawiin.", + "deleteFailed": "Hindi matanggal ang profile", + "deleteBlocked": "Ang profile na ito ay ginagamit ng isang webhook trigger. Alisin muna ito sa mga trigger na iyon.", + "deletedToast": "Ang profile na \"{{name}}\" ay tinanggal", + "variablesTitle": "Mga variable ng kapaligiran", + "addVariable": "Magdagdag ng variable", + "keyLabel": "Susi", + "keyTooLong": "Ang susi ay dapat 256 na character o mas kaunti", + "keyDuplicate": "Umiiral na ang key na ito sa profile", + "valueLabel": "Halaga", + "secretLabel": "Lihim", + "secrets": { + "markSecret": "Markahan bilang sikreto", + "masked": "Nakatagong halaga", + "reveal": "Ipakita ang halaga", + "newValuePlaceholder": "Maglagay ng bagong lihim na halaga", + "valuePlaceholder": "Lihim na halaga (naka-encrypt)" + }, + "variablesEmpty": "Wala pang variable", + "variablesSaveFailed": "Hindi ma-save ang variable", + "commandsTitle": "Mga utos sa pag-setup", + "addCommand": "Magdagdag ng utos", + "commandLabel": "Utos", + "commandPlaceholder": "e.g. pnpm install", + "commandsEmpty": "Wala pang setup command", + "commandsSaveFailed": "Hindi ma-save ang mga command sa pag-setup", + "moveUp": "Umakyat", + "moveDown": "Ilipat pababa", + "skillsTitle": "Mga kasanayan", + "addSkill": "Magdagdag ng kasanayan", + "skillNameLabel": "Pangalan ng kasanayan", + "skillMarkdownLabel": "Nilalaman ng kasanayan", + "skillsEmpty": "Wala pang skills", + "skillNameRequired": "Maglagay ng pangalan ng kasanayan", + "skillNameInvalid": "Ang pangalan ng kasanayan ay dapat magsimula sa isang titik o digit at gumamit lamang ng maliliit na titik, digit, at gitling", + "skillContentRequired": "Kinakailangan ang nilalaman ng kasanayan", + "skillSaveFailed": "Hindi makapag-save ng kasanayan", + "skillsHint": "Mag-paste ng SKILL.md at mapunan ang pangalan nito; magdagdag ng kasanayan mula sa iyong telepono nang walang drag at drop.", + "skillEditTitle": "Mag-edit ng kasanayan", + "mcp": { + "title": "Mga MCP server", + "add": "Magdagdag ng MCP server", + "empty": "Wala pang MCP server", + "name": "Pangalan ng server", + "type": "Uri ng server", + "localType": "Lokal", + "remoteType": "Malayo", + "command": "Linya ng utos", + "url": "URL", + "environment": "Kapaligiran (JSON)", + "headers": "Mga Header (JSON)", + "timeout": "Oras ng paghihintay", + "secretCount": "Lihim na bilang", + "namePlaceholder": "my-server", + "commandPlaceholder": "npx @example/mcp-server", + "jsonPlaceholder": "{\n \"API_KEY\": \"sk-...\"\n}", + "nameRequired": "Maglagay ng pangalan ng server", + "nameInvalid": "Ang pangalan ay maaari lamang gumamit ng mga titik, numero, underscore, at gitling", + "commandRequired": "Maglagay ng command", + "commandTooLong": "Gumamit ng hindi hihigit sa 50 argumento na 500 character bawat isa", + "urlRequired": "Maglagay ng URL", + "urlInvalid": "Maglagay ng wastong URL", + "urlTooLong": "Dapat 2048 character o mas kaunti ang URL", + "jsonInvalid": "Maglagay ng wastong JSON — isang bagay ng mga halaga ng string", + "jsonTooLarge": "Gumamit ng hindi hihigit sa 50 entry, mga key hanggang 128 at mga value hanggang 4096 character", + "timeoutInvalid": "Ang timeout ay dapat na isang buong bilang ng mga millisecond", + "edit": "I-edit ang MCP server", + "saveFailed": "Hindi ma-save ang MCP server" + }, + "agents": { + "title": "Mga ahente ng profile", + "add": "Magdagdag ng ahente", + "empty": "Wala pang ahente", + "name": "Pangalan ng ahente", + "slug": "Slug ng ahente", + "description": "Paglalarawan ng ahente", + "model": "Override ng modelo", + "modelPlaceholder": "Gamitin ang default ng session", + "prompt": "Prompt ng sistema", + "steps": "Pinakamataas na hakbang", + "stepsPlaceholder": "hal. 50", + "temperature": "Temperatura", + "temperaturePlaceholder": "hal. 0.2", + "topP": "top_p", + "topPPlaceholder": "hal. 0.95", + "slugPlaceholder": "my-agent", + "namePlaceholder": "Aking Ahente", + "promptPlaceholder": "ikaw ay isang...", + "tools": "Mga gamit", + "visibility": "Bisibilidad", + "visibilityPrimary": "Pangunahin — ipinapakita sa picker", + "visibilitySubagent": "Subagent — matatawag lang mula sa iba", + "visibilityAll": "Lahat - pareho", + "saveFailed": "Hindi ma-save ang ahente", + "nameRequired": "Maglagay ng pangalan ng ahente", + "slugRequired": "Maglagay ng agent slug", + "slugInvalid": "Ang slug ay dapat magsimula sa isang titik at gumamit lamang ng maliliit na titik, digit, at gitling", + "slugConflict": "Ang slug na iyon ay nakalaan ng isang built-in na ahente", + "edit": "I-edit ang ahente" + }, + "slashCommands": { + "title": "Mga utos ng slash", + "add": "Magdagdag ng slash command", + "empty": "Wala pang slash command", + "name": "Pangalan ng command", + "description": "Paglalarawan ng utos", + "template": "Padron", + "namePlaceholder": "explain", + "templatePlaceholder": "Ipaliwanag ang $ARGUMENTS", + "agent": "Ahente", + "subtask": "Patakbuhin bilang subtask", + "edit": "I-edit ang slash command", + "saveFailed": "Hindi ma-save ang command", + "nameRequired": "Maglagay ng command name", + "nameInvalid": "Ang pangalan ay dapat magsimula sa maliit na titik at gumamit lamang ng maliliit na titik, digit, at gitling", + "templateRequired": "Maglagay ng command template", + "nameConflict": "Ang pangalang iyon ay nakalaan sa pamamagitan ng isang built-in na command" + }, + "repoBindings": { + "title": "Mga Default na Profile para sa Repos", + "add": "Magdagdag ng default", + "empty": "Walang mga default na na-configure", + "emptyDescription": "Magdagdag ng default para awtomatikong maglapat ng profile kapag nagsisimula ng mga session para sa isang repo.", + "repo": "Pumili ng repositoryo...", + "profile": "Pumili ng profile", + "remove": "Alisin ang pagkakatali", + "private": "Pribado", + "search": "Maghanap ng mga repositoryo...", + "addedToast": "Itinakda ang default na profile para sa \"{{repo}}\"", + "addFailed": "Hindi maitakda ang default na profile", + "removedToast": "Inalis ang default na profile para sa \"{{repo}}\"", + "removeFailed": "Hindi maalis ang default na profile", + "pinnedTitle": "Naka-pin sa mga repository", + "pinRepo": "I-pin ang isang repo", + "pinnedEmpty": "Hindi naka-pin sa anumang mga repository.", + "pinnedDescription": "Awtomatikong na-load sa tuwing tina-target ng isang session ang isa sa mga repositoryo na ito.", + "pinnedToast": "Naka-pin sa \"{{repo}}\"", + "pinFailed": "Hindi ma-pin ang repo", + "unboundToast": "Nakalag ang repo", + "unbindFailed": "Hindi ma-unbind ang repo" + } } } diff --git a/apps/mobile/src/i18n/locales/fr.json b/apps/mobile/src/i18n/locales/fr.json index 420cd83f363..0d90a21284e 100644 --- a/apps/mobile/src/i18n/locales/fr.json +++ b/apps/mobile/src/i18n/locales/fr.json @@ -1603,7 +1603,33 @@ "continueFrom": "La session {{title}} reprend.", "importingSession": "Importation de la session…", "cliCannotContinue": "Cette CLI ne permet pas de reprendre une session. Choisissez Cloud Agent ou une autre CLI.", - "importFailed": "Impossible d'importer cette session." + "importFailed": "Impossible d'importer cette session.", + "pickProfile": "Choisissez un profil", + "noProfile": "Aucun profil", + "manageProfiles": "Gérer les profils", + "noProfiles": "Aucun profil pour l'instant. Créez-en un pour ajouter des variables d'environnement, des serveurs MCP et des compétences.", + "advancedConfig": "Configuration avancée", + "advancedConfigDescription": "Sélectionnez un profil pour charger les configurations enregistrées et configurez manuellement des variables d'environnement et des commandes de configuration supplémentaires.", + "saveAsProfile": "Enregistrer en tant que profil", + "saveProfileDescription": "Enregistrez les variables d'environnement et les commandes de configuration actuelles en tant que profil réutilisable.", + "profileSummary": "{{vars}} variables d'environnement · {{commands}} commandes de configuration", + "profileSaved": "Profil \"{{name}}\" enregistré", + "organizationProfiles": "Profils d'organisation", + "personalProfiles": "Profils personnels", + "yourProfiles": "Vos profils", + "failedToLoadProfiles": "Échec du chargement des profils", + "nameTooLong": "Le nom du profil est trop long", + "descriptionTooLong": "La description est trop longue", + "secretsEncrypted": "Les secrets sont cryptés avant stockage.", + "profileActive": "Profil actif", + "profilesActive": "Profils actifs", + "customConfigActive": "Configuration personnalisée active", + "configNeedsAttention": "La configuration a besoin d'attention", + "repoProfile": "Profil de pension", + "selectedProfile": "Profil sélectionné", + "manualOverrides": "Remplacements manuels", + "openSettingsToReview": "Ouvrez les paramètres pour vérifier", + "profileSelection": "Sélection de profil" }, "partRenderer": { "updatedFileCount_one": "{{displayCount}} fichier mis à jour.", @@ -3267,5 +3293,180 @@ "multiplierError": "Saisissez un multiplicateur de 1 ou plus", "permissionDenied": "Vous n'avez pas l'autorisation de gérer les alertes de dépenses.", "pushOffByCategory": "Désactivé dans les notifications" + }, + "profiles": { + "title": "Profils", + "entrySubtitle": "Réutiliser les paramètres d'environnement d'une session à l'autre", + "newProfile": "Nouveau profil", + "emptyTitle": "Aucun profil pour l'instant", + "emptyDescription": "Créez un profil pour réutiliser les variables d'environnement, les commandes de configuration et les compétences au fil des sessions.", + "loadFailed": "Impossible de charger les profils", + "counts": { + "vars": "{{count}} variables", + "mcp": "{{count}} MCP", + "skills": "{{count}} compétences", + "commands": "{{count}} commandes", + "varsShort": "{{count}}var", + "skillsShort": "{{count}}comp", + "commandsShort": "{{count}}cmd" + }, + "nameLabel": "Nom du profil", + "namePlaceholder": "par ex. Débogage back-end", + "descriptionLabel": "Description du profil", + "descriptionPlaceholder": "Description facultative", + "ownerLabel": "Propriétaire du profil", + "nameRequired": "Entrez un nom de profil", + "createAction": "Créer un profil", + "createFailed": "Impossible de créer un profil", + "createdToast": "Profil \"{{name}}\" créé", + "settingsSectionTitle": "Configuration", + "defaultSectionTitle": "Profil par défaut", + "personalDefaultDescription": "Chargé automatiquement lorsqu'aucun référentiel n'a de profil épinglé.", + "organizationDefaultDescription": "Chargé automatiquement pour les membres qui n’ont aucun défaut personnel.", + "setAsDefault": "Définir par défaut", + "removeDefault": "Supprimer par défaut", + "saveFailed": "Impossible d'enregistrer le profil", + "deleteTitle": "Supprimer le profil", + "deleteMessage": "Cela supprime définitivement le profil et ses paramètres. Cela ne peut pas être annulé.", + "deleteFailed": "Impossible de supprimer le profil", + "deleteBlocked": "Ce profil est utilisé par un déclencheur webhook. Supprimez-le d'abord de ces déclencheurs.", + "deletedToast": "Profil \"{{name}}\" supprimé", + "variablesTitle": "Variables d'environnement", + "addVariable": "Ajouter une variable", + "keyLabel": "Clé", + "keyTooLong": "La clé doit contenir 256 caractères ou moins", + "keyDuplicate": "Cette clé existe déjà dans le profil", + "valueLabel": "Valeur", + "secretLabel": "Secrète", + "secrets": { + "markSecret": "Marquer comme secret", + "masked": "Valeur cachée", + "reveal": "Révéler la valeur", + "newValuePlaceholder": "Saisir une nouvelle valeur secrète", + "valuePlaceholder": "Valeur secrète (chiffrée)" + }, + "variablesEmpty": "Aucune variable pour l'instant", + "variablesSaveFailed": "Impossible d'enregistrer la variable", + "commandsTitle": "Commandes de configuration", + "addCommand": "Ajouter une commande", + "commandLabel": "Commande", + "commandPlaceholder": "e.g. pnpm install", + "commandsEmpty": "Aucune commande de configuration pour l'instant", + "commandsSaveFailed": "Impossible d'enregistrer les commandes de configuration", + "moveUp": "Monter", + "moveDown": "Descendre", + "skillsTitle": "Compétences", + "addSkill": "Ajouter une compétence", + "skillNameLabel": "Nom de la compétence", + "skillMarkdownLabel": "Contenu des compétences", + "skillsEmpty": "Aucune compétence pour l'instant", + "skillNameRequired": "Entrez un nom de compétence", + "skillNameInvalid": "Le nom de la compétence doit commencer par une lettre ou un chiffre et utiliser uniquement des lettres minuscules, des chiffres et des tirets.", + "skillContentRequired": "Un contenu de compétence est requis", + "skillSaveFailed": "Impossible de sauvegarder la compétence", + "skillsHint": "Collez un SKILL.md et son nom est renseigné ; ajoutez une compétence depuis votre téléphone sans glisser-déposer.", + "skillEditTitle": "Modifier la compétence", + "mcp": { + "title": "Serveurs MCP", + "add": "Ajouter un serveur MCP", + "empty": "Pas encore de serveurs MCP", + "name": "Nom du serveur", + "type": "Type de serveur", + "localType": "Locale", + "remoteType": "Télécommande", + "command": "Ligne de commande", + "url": "URL", + "environment": "Environnement (JSON)", + "headers": "En-têtes (JSON)", + "timeout": "Temps mort", + "secretCount": "Compte secret", + "namePlaceholder": "my-server", + "commandPlaceholder": "npx @example/mcp-server", + "jsonPlaceholder": "{\n \"API_KEY\": \"sk-...\"\n}", + "nameRequired": "Entrez un nom de serveur", + "nameInvalid": "Le nom ne peut utiliser que des lettres, des chiffres, des traits de soulignement et des tirets.", + "commandRequired": "Entrez une commande", + "commandTooLong": "Utilisez au maximum 50 arguments de 500 caractères chacun", + "urlRequired": "Entrez une URL", + "urlInvalid": "Entrez une URL valide", + "urlTooLong": "L'URL doit contenir 2048 caractères ou moins", + "jsonInvalid": "Entrez un JSON valide - un objet de valeurs de chaîne", + "jsonTooLarge": "Utilisez au maximum 50 entrées, des clés jusqu'à 128 et des valeurs jusqu'à 4096 caractères", + "timeoutInvalid": "Le délai d'attente doit être un nombre entier de millisecondes", + "edit": "Modifier le serveur MCP", + "saveFailed": "Impossible de sauvegarder le serveur MCP" + }, + "agents": { + "title": "Agents de profil", + "add": "Ajouter un agent", + "empty": "Aucun agent pour l'instant", + "name": "Nom de l'agent", + "slug": "Limace d'agent", + "description": "Description de l'agent", + "model": "Remplacement du modèle", + "modelPlaceholder": "Utiliser la session par défaut", + "prompt": "Invite système", + "steps": "Étapes max.", + "stepsPlaceholder": "p. ex. 50", + "temperature": "Température", + "temperaturePlaceholder": "p. ex. 0.2", + "topP": "top_p", + "topPPlaceholder": "p. ex. 0.95", + "slugPlaceholder": "my-agent", + "namePlaceholder": "Mon agent", + "promptPlaceholder": "Vous êtes un...", + "tools": "Outils", + "visibility": "Visibilité", + "visibilityPrimary": "Primaire : affiché dans le sélecteur", + "visibilitySubagent": "Sous-agent – appelable uniquement par d'autres", + "visibilityAll": "Tous — les deux", + "saveFailed": "Impossible de sauvegarder l'agent", + "nameRequired": "Entrez un nom d'agent", + "slugRequired": "Entrez un slug d'agent", + "slugInvalid": "Slug doit commencer par une lettre et utiliser uniquement des lettres minuscules, des chiffres et des tirets.", + "slugConflict": "Ce slug est réservé par un agent intégré", + "edit": "Modifier l'agent" + }, + "slashCommands": { + "title": "Commandes barre oblique", + "add": "Ajouter une commande slash", + "empty": "Pas encore de commandes slash", + "name": "Nom de la commande", + "description": "Description de la commande", + "template": "Modèle", + "namePlaceholder": "explain", + "templatePlaceholder": "Expliquez $ARGUMENTS", + "agent": "Agent", + "subtask": "Exécuter en tant que sous-tâche", + "edit": "Modifier la commande barre oblique", + "saveFailed": "Impossible d'enregistrer la commande", + "nameRequired": "Entrez un nom de commande", + "nameInvalid": "Le nom doit commencer par une lettre minuscule et utiliser uniquement des lettres minuscules, des chiffres et des tirets.", + "templateRequired": "Entrez un modèle de commande", + "nameConflict": "Ce nom est réservé par une commande intégrée" + }, + "repoBindings": { + "title": "Profils par défaut pour les dépôts", + "add": "Ajouter une valeur par défaut", + "empty": "Aucune valeur par défaut configurée", + "emptyDescription": "Ajoutez une valeur par défaut pour appliquer automatiquement un profil lors du démarrage des sessions pour un dépôt.", + "repo": "Sélectionnez le référentiel…", + "profile": "Sélectionnez un profil", + "remove": "Délier", + "private": "Privé", + "search": "Rechercher des référentiels…", + "addedToast": "Profil par défaut défini pour \"{{repo}}\"", + "addFailed": "Impossible de définir le profil par défaut", + "removedToast": "Profil par défaut supprimé pour \"{{repo}}\"", + "removeFailed": "Impossible de supprimer le profil par défaut", + "pinnedTitle": "Épinglé aux référentiels", + "pinRepo": "Épingler un dépôt", + "pinnedEmpty": "Non épinglé à aucun référentiel.", + "pinnedDescription": "Chargé automatiquement chaque fois qu'une session cible l'un de ces référentiels.", + "pinnedToast": "Épinglé sur \"{{repo}}\"", + "pinFailed": "Impossible d'épingler le dépôt", + "unboundToast": "Dépôt non lié", + "unbindFailed": "Impossible de dissocier le dépôt" + } } } diff --git a/apps/mobile/src/i18n/locales/ga.json b/apps/mobile/src/i18n/locales/ga.json index 6593ee238f0..70ff31006d0 100644 --- a/apps/mobile/src/i18n/locales/ga.json +++ b/apps/mobile/src/i18n/locales/ga.json @@ -2275,7 +2275,33 @@ "continueFrom": "Táthar ag leanúint ar aghaidh le {{title}}.", "importingSession": "Ag iompórtáil an tseisiúin", "cliCannotContinue": "Ní féidir leis an CLI seo leanúint ar aghaidh le seisiún. Roghnaigh Cloud Agent nó CLI eile.", - "importFailed": "Níorbh fhéidir an seisiún seo a iompórtáil." + "importFailed": "Níorbh fhéidir an seisiún seo a iompórtáil.", + "pickProfile": "Roghnaigh próifíl", + "noProfile": "Gan próifíl", + "manageProfiles": "Bainistigh próifílí", + "noProfiles": "Níl próifílí ann fós. Cruthaigh ceann chun athróga timpeallachta, freastalaithe MCP, agus scileanna a chur leis.", + "advancedConfig": "Cumraíocht Casta", + "advancedConfigDescription": "Roghnaigh próifíl chun cumraíochtaí sábháilte a luchtú, agus de láimh athróga timpeallachta breise agus orduithe socraithe a chumrú", + "saveAsProfile": "Sábháil mar Phróifíl", + "saveProfileDescription": "Sábháil na hathróga timpeallachta reatha agus na horduithe socraithe mar phróifíl ath-inúsáidte.", + "profileSummary": "{{vars}} athróga timpeallachta · {{commands}} orduithe socraithe", + "profileSaved": "Próifíl \"{{name}}\" sábháilte", + "organizationProfiles": "Próifílí Eagraíochta", + "personalProfiles": "Próifílí Pearsanta", + "yourProfiles": "Do Phróifílí", + "failedToLoadProfiles": "Theip ar lódáil próifílí", + "nameTooLong": "Tá an t-ainm próifíl ró-fhada", + "descriptionTooLong": "Tá an cur síos ró-fhada", + "secretsEncrypted": "Déantar rúin a chriptiú roimh stóráil.", + "profileActive": "Próifíl gníomhach", + "profilesActive": "Próifílí gníomhach", + "customConfigActive": "Cumraíocht saincheaptha gníomhach", + "configNeedsAttention": "Ní mór aird a thabhairt ar chumraíocht", + "repoProfile": "Próifíl repo", + "selectedProfile": "Próifíl roghnaithe", + "manualOverrides": "Sáruithe láimhe", + "openSettingsToReview": "Oscail Socruithe chun athbhreithniú a dhéanamh", + "profileSelection": "Roghnú próifíle" }, "partRenderer": { "updatedFileCount_one": "Nuashonraíodh {{displayCount}} chomhad.", @@ -3353,5 +3379,180 @@ "multiplierError": "Iontráil iolraitheoir 1 nó níos mó", "permissionDenied": "Níl cead agat foláirimh chaiteachais a bhainistiú.", "pushOffByCategory": "As i bhFógraí" + }, + "profiles": { + "title": "Próifílí", + "entrySubtitle": "Athúsáid socruithe timpeallachta ar fud na seisiún", + "newProfile": "Próifíl nua", + "emptyTitle": "Níl próifílí ann fós", + "emptyDescription": "Cruthaigh próifíl chun athróga timpeallachta, orduithe socraithe, agus scileanna a athúsáid thar seisiúin.", + "loadFailed": "Níorbh fhéidir próifílí a lódáil", + "counts": { + "vars": "{{count}} athróg", + "mcp": "{{count}} MCP", + "skills": "{{count}} scil", + "commands": "{{count}} ordú", + "varsShort": "{{count}}a", + "skillsShort": "{{count}}s", + "commandsShort": "{{count}}o" + }, + "nameLabel": "Ainm próifíle", + "namePlaceholder": "e.g. Debugging backend", + "descriptionLabel": "Cur síos ar phróifíl", + "descriptionPlaceholder": "Cur síos roghnach", + "ownerLabel": "Úinéir próifíle", + "nameRequired": "Cuir isteach ainm próifíle", + "createAction": "Cruthaigh próifíl", + "createFailed": "Níorbh fhéidir próifíl a chruthú", + "createdToast": "Próifíl \"{{name}}\" cruthaithe", + "settingsSectionTitle": "Cumraíocht", + "defaultSectionTitle": "Próifíl réamhshocraithe", + "personalDefaultDescription": "Uathlódáilte nuair nach bhfuil próifíl phionnáilte ag stór ar bith.", + "organizationDefaultDescription": "Uathluchtaithe do bhaill nach bhfuil réamhshocrú pearsanta acu.", + "setAsDefault": "Socraigh mar réamhshocrú", + "removeDefault": "Bain mar réamhshocrú", + "saveFailed": "Níorbh fhéidir an phróifíl a shábháil", + "deleteTitle": "Scrios próifíl", + "deleteMessage": "Baineann sé seo an phróifíl agus a socruithe amach go buan. Ní féidir é seo a chealú.", + "deleteFailed": "Níorbh fhéidir an phróifíl a scriosadh", + "deleteBlocked": "Úsáideann truicear crúca gréasáin an phróifíl seo. Bain de na truicir ar dtús é.", + "deletedToast": "Próifíl \"{{name}}\" scriosta", + "variablesTitle": "Athróga timpeallachta", + "addVariable": "Cuir athróg leis", + "keyLabel": "eochair", + "keyTooLong": "Caithfidh an eochair a bheith 256 carachtar nó níos lú", + "keyDuplicate": "Tá an eochair seo ann cheana sa phróifíl", + "valueLabel": "Luach", + "secretLabel": "Rúnda", + "secrets": { + "markSecret": "Marcáil mar rún", + "masked": "Luach i bhfolach", + "reveal": "Nocht luach", + "newValuePlaceholder": "Cuir isteach luach rúnda nua", + "valuePlaceholder": "Luach rúnda (criptithe)" + }, + "variablesEmpty": "Níl aon athróg ann fós", + "variablesSaveFailed": "Níorbh fhéidir an athróg a shábháil", + "commandsTitle": "Orduithe socraithe", + "addCommand": "Cuir ordú leis", + "commandLabel": "Ordú", + "commandPlaceholder": "e.g. pnpm install", + "commandsEmpty": "Níl aon orduithe socraithe fós", + "commandsSaveFailed": "Níorbh fhéidir na horduithe socraithe a shábháil", + "moveUp": "Bog suas", + "moveDown": "Bog síos", + "skillsTitle": "Scileanna", + "addSkill": "Cuir scil leis", + "skillNameLabel": "Ainm scile", + "skillMarkdownLabel": "Ábhar scile", + "skillsEmpty": "Gan scileanna fós", + "skillNameRequired": "Cuir isteach ainm scile", + "skillNameInvalid": "Ní mór tús a chur le hainm scile le litir nó le digit agus ní mór ach litreacha beaga, digití agus daiseanna a úsáid", + "skillContentRequired": "Tá ábhar scile ag teastáil", + "skillSaveFailed": "Níorbh fhéidir an scil a shábháil", + "skillsHint": "Greamaigh SKILL.md agus líonann a ainm isteach; cuir scil ó do ghuthán gan tarraing agus scaoil.", + "skillEditTitle": "Cuir scil in eagar", + "mcp": { + "title": "Freastalaithe MCP", + "add": "Cuir freastalaí MCP leis", + "empty": "Níl aon fhreastalaí MCP ann fós", + "name": "Ainm freastalaí", + "type": "Cineál freastalaí", + "localType": "Áitiúil", + "remoteType": "cianda", + "command": "Líne ordaithe", + "url": "URL", + "environment": "Comhshaol (JSON)", + "headers": "Ceanntásca (JSON)", + "timeout": "Teorainn Ama", + "secretCount": "Comhaireamh rúnda", + "namePlaceholder": "my-server", + "commandPlaceholder": "npx @example/mcp-server", + "jsonPlaceholder": "{\n \"API_KEY\": \"sk-...\"\n}", + "nameRequired": "Cuir isteach ainm freastalaí", + "nameInvalid": "Ní féidir le hainm ach litreacha, uimhreacha, béimeanna agus daiseanna a úsáid", + "commandRequired": "Cuir isteach ordú", + "commandTooLong": "Úsáid 50 argóint ar a mhéad, 500 carachtar sa cheann", + "urlRequired": "Cuir isteach URL", + "urlInvalid": "Cuir isteach URL bailí", + "urlTooLong": "Caithfidh an URL a bheith 2048 carachtar nó níos lú", + "jsonInvalid": "Iontráil JSON bailí — réad teaghluachanna", + "jsonTooLarge": "Úsáid 50 iontráil ar a mhéad, eochracha suas le 128 agus luachanna suas le 4096 carachtar", + "timeoutInvalid": "Caithfidh teorainn ama a bheith ina iomlán de na milleasoicindí", + "edit": "Cuir freastalaí MCP in eagar", + "saveFailed": "Níorbh fhéidir an freastalaí MCP a shábháil" + }, + "agents": { + "title": "Gníomhairí próifíl", + "add": "Cuir gníomhaire leis", + "empty": "Níl gníomhairí fós", + "name": "Ainm gníomhaire", + "slug": "Seilide gníomhaire", + "description": "Cur síos ar an ngníomhaire", + "model": "Sárú múnla", + "modelPlaceholder": "Úsáid réamhshocrú seisiúin", + "prompt": "Leid córais", + "steps": "Uaschéimeanna", + "stepsPlaceholder": "m.sh. 50", + "temperature": "Teocht", + "temperaturePlaceholder": "m.sh. 0.2", + "topP": "top_p", + "topPPlaceholder": "m.sh. 0.95", + "slugPlaceholder": "my-agent", + "namePlaceholder": "Mo Ghníomhaire", + "promptPlaceholder": "Is duine thú...", + "tools": "Uirlisí", + "visibility": "Infheictheacht", + "visibilityPrimary": "Bunscoile - a thaispeántar sa roghnóir", + "visibilitySubagent": "Fo-ghníomhaire — inghlaoite ó dhaoine eile amháin", + "visibilityAll": "Gach - araon", + "saveFailed": "Níorbh fhéidir an gníomhaire a shábháil", + "nameRequired": "Cuir isteach ainm gníomhaire", + "slugRequired": "Cuir isteach seilide gníomhaire", + "slugInvalid": "Caithfidh sluga tosú le litir agus gan ach litreacha beaga, digití agus daiseanna a úsáid", + "slugConflict": "Tá an seilide sin curtha in áirithe ag gníomhaire ionsuite", + "edit": "Cuir gníomhaire in eagar" + }, + "slashCommands": { + "title": "Orduithe Slais", + "add": "Cuir ordú slais leis", + "empty": "Níl aon orduithe slais fós", + "name": "Ainm ordú", + "description": "Cur síos ar ordú", + "template": "Teimpléad", + "namePlaceholder": "explain", + "templatePlaceholder": "Mínigh $ARGUMENTS", + "agent": "Gníomhaire", + "subtask": "Rith mar fhothasc", + "edit": "Cuir ordú slais in eagar", + "saveFailed": "Níorbh fhéidir an t-ordú a shábháil", + "nameRequired": "Cuir isteach ainm ordaithe", + "nameInvalid": "Caithfidh an t-ainm tosú le litir chás íochtair agus gan ach litreacha beaga, digití agus daiseanna a úsáid", + "templateRequired": "Cuir isteach teimpléad ordaithe", + "nameConflict": "Tá an t-ainm sin forchoimeádta ag ordú ionsuite" + }, + "repoBindings": { + "title": "Próifílí Réamhshocraithe le haghaidh Repos", + "add": "Cuir réamhshocraithe leis", + "empty": "Níl aon réamhshocruithe cumraithe", + "emptyDescription": "Cuir réamhshocrú leis chun próifíl a chur i bhfeidhm go huathoibríoch agus seisiúin á gcur ar bun le haghaidh repo.", + "repo": "Roghnaigh stór…", + "profile": "Roghnaigh próifíl", + "remove": "Dícheangail", + "private": "Príobháideach", + "search": "Cuardaigh stórtha…", + "addedToast": "Socraíodh próifíl réamhshocraithe do \"{{repo}}\"", + "addFailed": "Níorbh fhéidir an phróifíl réamhshocraithe a shocrú", + "removedToast": "Baineadh an phróifíl réamhshocraithe le haghaidh \"{{repo}}\"", + "removeFailed": "Níorbh fhéidir an phróifíl réamhshocraithe a bhaint", + "pinnedTitle": "Pionáilte chuig stórtha", + "pinRepo": "Pin repo", + "pinnedEmpty": "Gan pinn chuig aon stórtha.", + "pinnedDescription": "Uathluchtaithe aon uair a bheidh seisiún dírithe ar cheann de na stórtha sin.", + "pinnedToast": "Pionnáilte go \"{{repo}}\"", + "pinFailed": "Níorbh fhéidir repo a phionáil", + "unboundToast": "Repo gan cheangal", + "unbindFailed": "Níorbh fhéidir repo a dhícheangal" + } } } diff --git a/apps/mobile/src/i18n/locales/gl.json b/apps/mobile/src/i18n/locales/gl.json index 1608694c926..7dd681062d8 100644 --- a/apps/mobile/src/i18n/locales/gl.json +++ b/apps/mobile/src/i18n/locales/gl.json @@ -2191,7 +2191,33 @@ "continueFrom": "Continúas a sesión {{title}}.", "importingSession": "Importando a sesión", "cliCannotContinue": "Este CLI non pode continuar unha sesión. Escolle Cloud Agent ou outro CLI.", - "importFailed": "Non se puido importar esta sesión." + "importFailed": "Non se puido importar esta sesión.", + "pickProfile": "Escolle un perfil", + "noProfile": "Sen perfil", + "manageProfiles": "Xestionar perfís", + "noProfiles": "Aínda non hai perfís. Crea un para engadir variables de ambiente, servidores MCP e habilidades.", + "advancedConfig": "Configuración avanzada", + "advancedConfigDescription": "Seleccione un perfil para cargar configuracións gardadas e configure manualmente variables de ambiente adicionais e comandos de configuración", + "saveAsProfile": "Gardar como perfil", + "saveProfileDescription": "Garda as variables de ambiente actuais e os comandos de configuración como perfil reutilizable.", + "profileSummary": "{{vars}} variables de ambiente · {{commands}} comandos de configuración", + "profileSaved": "Gardouse o perfil \"{{name}}\".", + "organizationProfiles": "Perfís da organización", + "personalProfiles": "Perfís persoais", + "yourProfiles": "Os teus perfís", + "failedToLoadProfiles": "Produciuse un erro ao cargar os perfís", + "nameTooLong": "O nome do perfil é demasiado longo", + "descriptionTooLong": "A descrición é demasiado longa", + "secretsEncrypted": "Os segredos cífranse antes do almacenamento.", + "profileActive": "Perfil activo", + "profilesActive": "Perfís activos", + "customConfigActive": "Configuración personalizada activa", + "configNeedsAttention": "A configuración necesita atención", + "repoProfile": "Perfil de repositorio", + "selectedProfile": "Perfil seleccionado", + "manualOverrides": "Anulacións manuais", + "openSettingsToReview": "Abre Configuración para revisar", + "profileSelection": "Selección de perfil" }, "partRenderer": { "updatedFileCount_one": "Actualizouse {{displayCount}} ficheiro.", @@ -3224,5 +3250,180 @@ "multiplierError": "Introduce un multiplicador de 1 ou máis", "permissionDenied": "Non tes permiso para xestionar as alertas de gasto.", "pushOffByCategory": "Desactivado en Notificacións" + }, + "profiles": { + "title": "Perfís", + "entrySubtitle": "Reutiliza a configuración do contorno en todas as sesións", + "newProfile": "Novo perfil", + "emptyTitle": "Aínda non hai perfís", + "emptyDescription": "Crea un perfil para reutilizar as variables de ambiente, os comandos de configuración e as habilidades nas sesións.", + "loadFailed": "Non se puideron cargar os perfís", + "counts": { + "vars": "{{count}} variables", + "mcp": "{{count}} MCP", + "skills": "{{count}} habilidades", + "commands": "{{count}} comandos", + "varsShort": "{{count}}v", + "skillsShort": "{{count}}h", + "commandsShort": "{{count}}c" + }, + "nameLabel": "Nome do perfil", + "namePlaceholder": "p.ex. Depuración do backend", + "descriptionLabel": "Descrición do perfil", + "descriptionPlaceholder": "Descrición opcional", + "ownerLabel": "Propietario do perfil", + "nameRequired": "Introduza un nome de perfil", + "createAction": "Crear perfil", + "createFailed": "Non se puido crear o perfil", + "createdToast": "Creouse o perfil \"{{name}}\".", + "settingsSectionTitle": "Configuración", + "defaultSectionTitle": "Perfil predeterminado", + "personalDefaultDescription": "Carga automaticamente cando ningún repositorio ten un perfil fixado.", + "organizationDefaultDescription": "Cargado automático para membros que non teñen ningún valor predeterminado persoal.", + "setAsDefault": "Establecer como predeterminado", + "removeDefault": "Eliminar por defecto", + "saveFailed": "Non se puido gardar o perfil", + "deleteTitle": "Eliminar perfil", + "deleteMessage": "Isto elimina permanentemente o perfil e a súa configuración. Isto non se pode desfacer.", + "deleteFailed": "Non se puido eliminar o perfil", + "deleteBlocked": "Este perfil é usado por un activador de webhook. Elimínao deses disparadores primeiro.", + "deletedToast": "Eliminouse o perfil \"{{name}}\".", + "variablesTitle": "Variables de ambiente", + "addVariable": "Engadir variable", + "keyLabel": "Chave", + "keyTooLong": "A chave debe ter 256 caracteres ou menos", + "keyDuplicate": "Esta chave xa existe no perfil", + "valueLabel": "Valor", + "secretLabel": "Segredo", + "secrets": { + "markSecret": "Marcar como segredo", + "masked": "Valor oculto", + "reveal": "Revela valor", + "newValuePlaceholder": "Introduce un novo valor secreto", + "valuePlaceholder": "Valor secreto (cifrado)" + }, + "variablesEmpty": "Aínda non hai variables", + "variablesSaveFailed": "Non se puido gardar a variable", + "commandsTitle": "Comandos de configuración", + "addCommand": "Engadir comando", + "commandLabel": "Comando", + "commandPlaceholder": "e.g. pnpm install", + "commandsEmpty": "Aínda non hai comandos de configuración", + "commandsSaveFailed": "Non se puideron gardar os comandos de configuración", + "moveUp": "Moverse cara arriba", + "moveDown": "Mover cara abaixo", + "skillsTitle": "Habilidades", + "addSkill": "Engade habilidade", + "skillNameLabel": "Nome da habilidade", + "skillMarkdownLabel": "Contido de habilidades", + "skillsEmpty": "Aínda non hai habilidades", + "skillNameRequired": "Introduza un nome de habilidade", + "skillNameInvalid": "O nome da habilidade debe comezar cunha letra ou un díxitos e usar só letras minúsculas, díxitos e guións", + "skillContentRequired": "Requírese contido de habilidade", + "skillSaveFailed": "Non se puido gardar a habilidade", + "skillsHint": "Pega un SKILL.md e o seu nome énchese; engade unha habilidade desde o teu teléfono sen arrastrar e soltar.", + "skillEditTitle": "Editar habilidade", + "mcp": { + "title": "Servidores MCP", + "add": "Engadir servidor MCP", + "empty": "Aínda non hai servidores MCP", + "name": "Nome do servidor", + "type": "Tipo de servidor", + "localType": "Local", + "remoteType": "Mando a distancia", + "command": "Liña de comandos", + "url": "URL", + "environment": "Ambiente (JSON)", + "headers": "Cabeceiras (JSON)", + "timeout": "Tempo de espera", + "secretCount": "Conta secreta", + "namePlaceholder": "my-server", + "commandPlaceholder": "npx @example/mcp-server", + "jsonPlaceholder": "{\n \"API_KEY\": \"sk-...\"\n}", + "nameRequired": "Introduza un nome de servidor", + "nameInvalid": "O nome só pode usar letras, números, guións baixos e guións", + "commandRequired": "Introduza un comando", + "commandTooLong": "Usa como máximo 50 argumentos de 500 caracteres cada un", + "urlRequired": "Introduza un URL", + "urlInvalid": "Introduce un URL válido", + "urlTooLong": "O URL debe ter 2048 caracteres ou menos", + "jsonInvalid": "Introduce un JSON válido: un obxecto de valores de cadea", + "jsonTooLarge": "Usa como máximo 50 entradas, chaves de ata 128 e valores de ata 4096 caracteres", + "timeoutInvalid": "O tempo de espera debe ser dun número enteiro de milisegundos", + "edit": "Editar servidor MCP", + "saveFailed": "Non se puido gardar o servidor MCP" + }, + "agents": { + "title": "Axentes de perfil", + "add": "Engadir axente", + "empty": "Aínda non hai axentes", + "name": "Nome do axente", + "slug": "Axente babosa", + "description": "Descrición do axente", + "model": "Anulación do modelo", + "modelPlaceholder": "Usar sesión predeterminada", + "prompt": "Aviso do sistema", + "steps": "Pasos máximos", + "stepsPlaceholder": "p. ex. 50", + "temperature": "Temperatura", + "temperaturePlaceholder": "p. ex. 0.2", + "topP": "top_p", + "topPPlaceholder": "p. ex. 0.95", + "slugPlaceholder": "my-agent", + "namePlaceholder": "O meu axente", + "promptPlaceholder": "Vostede é un...", + "tools": "Ferramentas", + "visibility": "Visibilidade", + "visibilityPrimary": "Primario: móstrase no selector", + "visibilitySubagent": "Subaxente: só se pode chamar desde outros", + "visibilityAll": "Todos - ambos", + "saveFailed": "Non se puido gardar o axente", + "nameRequired": "Introduza un nome de axente", + "slugRequired": "Introduza unha babosa de axente", + "slugInvalid": "Slug debe comezar cunha letra e usar só letras minúsculas, díxitos e guións", + "slugConflict": "Esa babosa está reservada por un axente incorporado", + "edit": "Editar axente" + }, + "slashCommands": { + "title": "Comandos de barra", + "add": "Engadir comando de barra", + "empty": "Aínda non hai comandos de barra", + "name": "Nome do comando", + "description": "Descrición do comando", + "template": "Modelo", + "namePlaceholder": "explain", + "templatePlaceholder": "Explica $ARGUMENTS", + "agent": "Axente", + "subtask": "Executar como subtarefa", + "edit": "Comando de edición de barra", + "saveFailed": "Non se puido gardar o comando", + "nameRequired": "Introduza un nome de comando", + "nameInvalid": "O nome debe comezar cunha letra minúscula e usar só letras minúsculas, díxitos e guións", + "templateRequired": "Introduza un modelo de comando", + "nameConflict": "Ese nome está reservado por un comando incorporado" + }, + "repoBindings": { + "title": "Perfís predeterminados para Repos", + "add": "Engadir predeterminado", + "empty": "Non se configuraron valores predeterminados", + "emptyDescription": "Engade un predeterminado para aplicar automaticamente un perfil ao iniciar sesións para un repositorio.", + "repo": "Seleccionar repositorio...", + "profile": "Seleccione o perfil", + "remove": "Desvincular", + "private": "Privado", + "search": "Buscar repositorios...", + "addedToast": "Perfil predeterminado definido para \"{{repo}}\"", + "addFailed": "Non se puido establecer o perfil predeterminado", + "removedToast": "Eliminouse o perfil predeterminado para \"{{repo}}\"", + "removeFailed": "Non se puido eliminar o perfil predeterminado", + "pinnedTitle": "Fixado en repositorios", + "pinRepo": "Fixar un repo", + "pinnedEmpty": "Non fixado a ningún repositorio.", + "pinnedDescription": "Carga automaticamente sempre que unha sesión se dirixa a un destes repositorios.", + "pinnedToast": "Fixado en \"{{repo}}\"", + "pinFailed": "Non se puido fixar o repositorio", + "unboundToast": "Repo desvinculado", + "unbindFailed": "Non se puido desvincular o repositorio" + } } } diff --git a/apps/mobile/src/i18n/locales/gu.json b/apps/mobile/src/i18n/locales/gu.json index c51b82f0487..af5ea68ebb6 100644 --- a/apps/mobile/src/i18n/locales/gu.json +++ b/apps/mobile/src/i18n/locales/gu.json @@ -2191,7 +2191,33 @@ "continueFrom": "{{title}} સત્ર આગળ ચાલુ થઈ રહ્યું છે.", "importingSession": "સત્ર આયાત થઈ રહ્યું છે", "cliCannotContinue": "આ CLI સત્ર ચાલુ રાખી શકતું નથી. Cloud Agent અથવા બીજું CLI પસંદ કરો.", - "importFailed": "આ સત્ર આયાત કરી શકાયું નહીં." + "importFailed": "આ સત્ર આયાત કરી શકાયું નહીં.", + "pickProfile": "પ્રોફાઇલ ચૂંટો", + "noProfile": "કોઈ પ્રોફાઇલ નથી", + "manageProfiles": "પ્રોફાઇલ મેનેજ કરો", + "noProfiles": "હજુ સુધી કોઈ પ્રોફાઇલ નથી. પર્યાવરણ ચલો, MCP સર્વર અને કુશળતા ઉમેરવા માટે એક બનાવો.", + "advancedConfig": "અદ્યતન રૂપરેખાંકન", + "advancedConfigDescription": "સાચવેલ રૂપરેખાંકનો લોડ કરવા માટે પ્રોફાઇલ પસંદ કરો, અને વધારાના પર્યાવરણ ચલો અને સેટઅપ આદેશોને મેન્યુઅલી ગોઠવો", + "saveAsProfile": "પ્રોફાઇલ તરીકે સાચવો", + "saveProfileDescription": "વર્તમાન પર્યાવરણ ચલો અને સેટઅપ આદેશોને ફરીથી વાપરી શકાય તેવી પ્રોફાઇલ તરીકે સાચવો.", + "profileSummary": "{{vars}} પર્યાવરણ ચલો · {{commands}} સેટઅપ આદેશો", + "profileSaved": "પ્રોફાઇલ \"{{name}}\" સાચવી", + "organizationProfiles": "સંસ્થા પ્રોફાઇલ્સ", + "personalProfiles": "વ્યક્તિગત પ્રોફાઇલ્સ", + "yourProfiles": "તમારી પ્રોફાઇલ્સ", + "failedToLoadProfiles": "પ્રોફાઇલ લોડ કરવામાં નિષ્ફળ", + "nameTooLong": "પ્રોફાઇલ નામ ખૂબ લાંબુ છે", + "descriptionTooLong": "વર્ણન ખૂબ લાંબુ છે", + "secretsEncrypted": "સંગ્રહ પહેલાં રહસ્યો એનક્રિપ્ટ થયેલ છે.", + "profileActive": "પ્રોફાઇલ સક્રિય", + "profilesActive": "પ્રોફાઇલ સક્રિય છે", + "customConfigActive": "કસ્ટમ રૂપરેખા સક્રિય", + "configNeedsAttention": "રૂપરેખાને ધ્યાન આપવાની જરૂર છે", + "repoProfile": "રેપો પ્રોફાઇલ", + "selectedProfile": "પસંદ કરેલ પ્રોફાઇલ", + "manualOverrides": "મેન્યુઅલ ઓવરરાઇડ", + "openSettingsToReview": "સમીક્ષા કરવા માટે સેટિંગ્સ ખોલો", + "profileSelection": "પ્રોફાઇલ પસંદગી" }, "partRenderer": { "updatedFileCount_one": "{{displayCount}} ફાઇલ અપડેટ થઈ.", @@ -3224,5 +3250,180 @@ "multiplierError": "1 કે તેથી વધુ ગુણક દાખલ કરો", "permissionDenied": "તમને ખર્ચની ચેતવણીઓનું સંચાલન કરવાની પરવાનગી નથી.", "pushOffByCategory": "સૂચનાઓમાં બંધ" + }, + "profiles": { + "title": "પ્રોફાઇલ્સ", + "entrySubtitle": "સમગ્ર સત્રોમાં પર્યાવરણ સેટિંગ્સનો ફરીથી ઉપયોગ કરો", + "newProfile": "નવી પ્રોફાઇલ", + "emptyTitle": "હજુ સુધી કોઈ પ્રોફાઇલ નથી", + "emptyDescription": "સમગ્ર સત્રોમાં પર્યાવરણ ચલો, સેટઅપ કમાન્ડ અને કૌશલ્યોનો ફરીથી ઉપયોગ કરવા માટે પ્રોફાઇલ બનાવો.", + "loadFailed": "પ્રોફાઇલ લોડ કરી શકાઈ નથી", + "counts": { + "vars": "{{count}} ચલો", + "mcp": "{{count}} MCP", + "skills": "{{count}} કૌશલ્ય", + "commands": "{{count}} આદેશો", + "varsShort": "{{count}}ચ", + "skillsShort": "{{count}}ક", + "commandsShort": "{{count}}આ" + }, + "nameLabel": "પ્રોફાઇલ નામ", + "namePlaceholder": "દા.ત. બેકએન્ડ ડીબગીંગ", + "descriptionLabel": "પ્રોફાઇલ વર્ણન", + "descriptionPlaceholder": "વૈકલ્પિક વર્ણન", + "ownerLabel": "પ્રોફાઇલ માલિક", + "nameRequired": "પ્રોફાઇલ નામ દાખલ કરો", + "createAction": "પ્રોફાઇલ બનાવો", + "createFailed": "પ્રોફાઇલ બનાવી શકાઈ નથી", + "createdToast": "પ્રોફાઇલ \"{{name}}\" બનાવી", + "settingsSectionTitle": "રૂપરેખાંકન", + "defaultSectionTitle": "ડિફૉલ્ટ પ્રોફાઇલ", + "personalDefaultDescription": "જ્યારે કોઈ રીપોઝીટરીમાં પિન કરેલ પ્રોફાઇલ ન હોય ત્યારે સ્વતઃ લોડ થાય છે.", + "organizationDefaultDescription": "કોઈ વ્યક્તિગત ડિફોલ્ટ ન હોય તેવા સભ્યો માટે સ્વતઃ-લોડ.", + "setAsDefault": "ડિફૉલ્ટ તરીકે સેટ કરો", + "removeDefault": "ડિફૉલ્ટ તરીકે દૂર કરો", + "saveFailed": "પ્રોફાઇલ સાચવી શકાઈ નથી", + "deleteTitle": "પ્રોફાઇલ કાઢી નાખો", + "deleteMessage": "આ પ્રોફાઇલ અને તેની સેટિંગ્સને કાયમ માટે દૂર કરે છે. આ પૂર્વવત્ કરી શકાતું નથી.", + "deleteFailed": "પ્રોફાઇલ ડિલીટ કરી શકાઈ નથી", + "deleteBlocked": "આ પ્રોફાઇલનો ઉપયોગ વેબહૂક ટ્રિગર દ્વારા થાય છે. તેને પહેલા તે ટ્રિગર્સમાંથી દૂર કરો.", + "deletedToast": "પ્રોફાઇલ \"{{name}}\" કાઢી નાખી", + "variablesTitle": "પર્યાવરણ ચલો", + "addVariable": "ચલ ઉમેરો", + "keyLabel": "કી", + "keyTooLong": "કી 256 અક્ષરો કે તેથી ઓછા હોવી જોઈએ", + "keyDuplicate": "આ કી પ્રોફાઇલમાં પહેલેથી અસ્તિત્વમાં છે", + "valueLabel": "મૂલ્ય", + "secretLabel": "ગુપ્ત", + "secrets": { + "markSecret": "ગુપ્ત તરીકે ચિહ્નિત કરો", + "masked": "છુપાયેલ મૂલ્ય", + "reveal": "મૂલ્ય જણાવો", + "newValuePlaceholder": "નવું ગુપ્ત મૂલ્ય દાખલ કરો", + "valuePlaceholder": "ગુપ્ત મૂલ્ય (એન્ક્રિપ્ટ કરેલ)" + }, + "variablesEmpty": "હજુ સુધી કોઈ ચલ નથી", + "variablesSaveFailed": "ચલ સાચવી શકાયું નથી", + "commandsTitle": "સેટઅપ આદેશો", + "addCommand": "આદેશ ઉમેરો", + "commandLabel": "આદેશ", + "commandPlaceholder": "e.g. pnpm install", + "commandsEmpty": "હજુ સુધી કોઈ સેટઅપ આદેશો નથી", + "commandsSaveFailed": "સેટઅપ આદેશો સાચવી શકાયા નથી", + "moveUp": "ઉપર ખસેડો", + "moveDown": "નીચે ખસેડો", + "skillsTitle": "કૌશલ્ય", + "addSkill": "કૌશલ્ય ઉમેરો", + "skillNameLabel": "કૌશલ્યનું નામ", + "skillMarkdownLabel": "કૌશલ્ય સામગ્રી", + "skillsEmpty": "હજુ સુધી કોઈ આવડત નથી", + "skillNameRequired": "કૌશલ્યનું નામ દાખલ કરો", + "skillNameInvalid": "કૌશલ્યનું નામ અક્ષર અથવા અંકથી શરૂ થવું જોઈએ અને માત્ર નાના અક્ષરો, અંકો અને ડેશનો ઉપયોગ કરવો જોઈએ", + "skillContentRequired": "કૌશલ્ય સામગ્રી જરૂરી છે", + "skillSaveFailed": "કૌશલ્ય બચાવી શક્યા નથી", + "skillsHint": "એક SKILL.md પેસ્ટ કરો અને તેનું નામ ભરે છે; ખેંચો અને છોડો વગર તમારા ફોનમાંથી કુશળતા ઉમેરો.", + "skillEditTitle": "કૌશલ્ય સંપાદિત કરો", + "mcp": { + "title": "MCP સર્વર્સ", + "add": "MCP સર્વર ઉમેરો", + "empty": "હજુ સુધી કોઈ MCP સર્વર નથી", + "name": "સર્વર નામ", + "type": "સર્વર પ્રકાર", + "localType": "સ્થાનિક", + "remoteType": "દૂરસ્થ", + "command": "આદેશ વાક્ય", + "url": "URL", + "environment": "પર્યાવરણ (JSON)", + "headers": "હેડર્સ (JSON)", + "timeout": "સમયસમાપ્ત", + "secretCount": "ગુપ્ત ગણતરી", + "namePlaceholder": "my-server", + "commandPlaceholder": "npx @example/mcp-server", + "jsonPlaceholder": "{\n \"API_KEY\": \"sk-...\"\n}", + "nameRequired": "સર્વર નામ દાખલ કરો", + "nameInvalid": "નામ માત્ર અક્ષરો, સંખ્યાઓ, અન્ડરસ્કોર અને ડેશનો ઉપયોગ કરી શકે છે", + "commandRequired": "આદેશ દાખલ કરો", + "commandTooLong": "વધુમાં વધુ 50 આર્ગ્યુમેન્ટ વાપરો, દરેક 500 અક્ષરોના", + "urlRequired": "એક URL દાખલ કરો", + "urlInvalid": "માન્ય URL દાખલ કરો", + "urlTooLong": "URL 2048 અક્ષરો કે તેથી ઓછું હોવું જોઈએ", + "jsonInvalid": "માન્ય JSON દાખલ કરો — સ્ટ્રિંગ મૂલ્યોનો ઑબ્જેક્ટ", + "jsonTooLarge": "વધુમાં વધુ 50 એન્ટ્રી વાપરો, કી 128 સુધી અને મૂલ્યો 4096 અક્ષરો સુધી", + "timeoutInvalid": "સમયસમાપ્તિ મિલીસેકન્ડની સંપૂર્ણ સંખ્યા હોવી આવશ્યક છે", + "edit": "MCP સર્વર સંપાદિત કરો", + "saveFailed": "MCP સર્વર સાચવી શકાયું નથી" + }, + "agents": { + "title": "પ્રોફાઇલ એજન્ટો", + "add": "એજન્ટ ઉમેરો", + "empty": "હજુ સુધી કોઈ એજન્ટ નથી", + "name": "એજન્ટનું નામ", + "slug": "એજન્ટ ગોકળગાય", + "description": "એજન્ટનું વર્ણન", + "model": "મોડલ ઓવરરાઇડ", + "modelPlaceholder": "સત્ર ડિફૉલ્ટનો ઉપયોગ કરો", + "prompt": "સિસ્ટમ પ્રોમ્પ્ટ", + "steps": "મહત્તમ પગલાં", + "stepsPlaceholder": "દા.ત. 50", + "temperature": "તાપમાન", + "temperaturePlaceholder": "દા.ત. 0.2", + "topP": "top_p", + "topPPlaceholder": "દા.ત. 0.95", + "slugPlaceholder": "my-agent", + "namePlaceholder": "મારા એજન્ટ", + "promptPlaceholder": "તમે એક...", + "tools": "સાધનો", + "visibility": "દૃશ્યતા", + "visibilityPrimary": "પ્રાથમિક — પીકરમાં બતાવેલ છે", + "visibilitySubagent": "સબએજન્ટ - ફક્ત અન્ય લોકો પાસેથી કૉલ કરી શકાય છે", + "visibilityAll": "બધા - બંને", + "saveFailed": "એજન્ટને સાચવી શકાયો નથી", + "nameRequired": "એજન્ટનું નામ દાખલ કરો", + "slugRequired": "એજન્ટ ગોકળગાય દાખલ કરો", + "slugInvalid": "સ્લગ એક અક્ષરથી શરૂ થવો જોઈએ અને માત્ર નાના અક્ષરો, અંકો અને ડેશનો ઉપયોગ કરવો જોઈએ", + "slugConflict": "તે ગોકળગાય બિલ્ટ-ઇન એજન્ટ દ્વારા આરક્ષિત છે", + "edit": "એજન્ટ સંપાદિત કરો" + }, + "slashCommands": { + "title": "સ્લેશ આદેશો", + "add": "સ્લેશ આદેશ ઉમેરો", + "empty": "હજુ સુધી કોઈ સ્લેશ આદેશો નથી", + "name": "આદેશનું નામ", + "description": "આદેશનું વર્ણન", + "template": "ઢાંચો", + "namePlaceholder": "explain", + "templatePlaceholder": "$ARGUMENTS સમજાવો", + "agent": "એજન્ટ", + "subtask": "સબટાસ્ક તરીકે ચલાવો", + "edit": "સ્લેશ આદેશ સંપાદિત કરો", + "saveFailed": "આદેશ સાચવી શકાયો નથી", + "nameRequired": "આદેશનું નામ દાખલ કરો", + "nameInvalid": "નામ લોઅરકેસ અક્ષરથી શરૂ થવું જોઈએ અને માત્ર લોઅરકેસ અક્ષરો, અંકો અને ડેશનો ઉપયોગ કરવો જોઈએ", + "templateRequired": "આદેશ ટેમ્પલેટ દાખલ કરો", + "nameConflict": "તે નામ બિલ્ટ-ઇન કમાન્ડ દ્વારા આરક્ષિત છે" + }, + "repoBindings": { + "title": "Repos માટે ડિફોલ્ટ પ્રોફાઇલ્સ", + "add": "ડિફૉલ્ટ ઉમેરો", + "empty": "કોઈ ડિફૉલ્ટ ગોઠવેલ નથી", + "emptyDescription": "રેપો માટે સત્રો શરૂ કરતી વખતે આપમેળે પ્રોફાઇલ લાગુ કરવા માટે ડિફોલ્ટ ઉમેરો.", + "repo": "રીપોઝીટરી પસંદ કરો...", + "profile": "પ્રોફાઇલ પસંદ કરો", + "remove": "અનબાઇન્ડ", + "private": "ખાનગી", + "search": "ભંડાર શોધો...", + "addedToast": "\"{{repo}}\" માટે ડિફૉલ્ટ પ્રોફાઇલ સેટ કરો", + "addFailed": "ડિફૉલ્ટ પ્રોફાઇલ સેટ કરી શકાઈ નથી", + "removedToast": "\"{{repo}}\" માટે ડિફૉલ્ટ પ્રોફાઇલ દૂર કરી", + "removeFailed": "ડિફૉલ્ટ પ્રોફાઇલ દૂર કરી શકાઈ નથી", + "pinnedTitle": "રિપોઝીટરીઝમાં પિન કરેલ", + "pinRepo": "રેપો પિન કરો", + "pinnedEmpty": "કોઈપણ રીપોઝીટરીઝમાં પિન કરેલ નથી.", + "pinnedDescription": "જ્યારે પણ સત્ર આ રીપોઝીટરીઝમાંથી એકને લક્ષ્ય બનાવે છે ત્યારે સ્વતઃ લોડ થાય છે.", + "pinnedToast": "\"{{repo}}\" પર પિન કરેલ", + "pinFailed": "રેપો પિન કરી શકાયું નથી", + "unboundToast": "રેપો અનબાઉન્ડ", + "unbindFailed": "રેપોને અનબાઇન્ડ કરી શકાયું નથી" + } } } diff --git a/apps/mobile/src/i18n/locales/ha.json b/apps/mobile/src/i18n/locales/ha.json index b717d1e963c..4145a7c31be 100644 --- a/apps/mobile/src/i18n/locales/ha.json +++ b/apps/mobile/src/i18n/locales/ha.json @@ -2191,7 +2191,33 @@ "continueFrom": "Ana ci gaba da {{title}}.", "importingSession": "Ana shigo da zaman", "cliCannotContinue": "Wannan CLI ba zai iya ci gaba da zaman ba. Zaɓi Cloud Agent ko wani CLI.", - "importFailed": "An kasa shigo da wannan zaman." + "importFailed": "An kasa shigo da wannan zaman.", + "pickProfile": "Zaɓi bayanin martaba", + "noProfile": "Babu bayanin martaba", + "manageProfiles": "Sarrafa bayanan martaba", + "noProfiles": "Babu bayanan martaba tukuna. Ƙirƙiri ɗaya don ƙara masu canjin yanayi, sabar MCP, da ƙwarewa.", + "advancedConfig": "Babban Kanfigareshan", + "advancedConfigDescription": "Zaɓi bayanin martaba don loda ajiyayyun saituna, kuma da hannu saita ƙarin masu canjin yanayi da umarnin saitin", + "saveAsProfile": "Ajiye azaman Bayanan martaba", + "saveProfileDescription": "Ajiye masu canjin yanayi na yanzu da saitin umarni azaman bayanin martaba mai sake amfani da shi.", + "profileSummary": "{{vars}} masu canjin yanayi · {{commands}} umarnin saitin", + "profileSaved": "An adana bayanan martaba \"{{name}}\".", + "organizationProfiles": "Bayanan Ƙungiya", + "personalProfiles": "Bayanan sirri", + "yourProfiles": "Bayanan martaba", + "failedToLoadProfiles": "An kasa loda bayanan martaba", + "nameTooLong": "Sunan bayanin martaba ya yi tsayi da yawa", + "descriptionTooLong": "Bayanin ya yi tsayi da yawa", + "secretsEncrypted": "Ana rufaffen sirri kafin ajiya.", + "profileActive": "Bayanan martaba yana aiki", + "profilesActive": "Bayanan martaba suna aiki", + "customConfigActive": "Daidaitaccen tsari yana aiki", + "configNeedsAttention": "Config yana buƙatar kulawa", + "repoProfile": "Bayanan martaba na repo", + "selectedProfile": "Bayanan martaba da aka zaɓa", + "manualOverrides": "Hannu ya soke", + "openSettingsToReview": "Bude Saituna don dubawa", + "profileSelection": "Zaɓin bayanin martaba" }, "partRenderer": { "updatedFileCount_one": "An sabunta fayil {{displayCount}}.", @@ -3224,5 +3250,180 @@ "multiplierError": "Shigar da mai ninka 1 ko fiye", "permissionDenied": "Ba ka da izinin sarrafa faɗakarwar kashe kuɗi.", "pushOffByCategory": "A kashe a cikin Sanarwa" + }, + "profiles": { + "title": "Bayanan martaba", + "entrySubtitle": "Sake amfani da saitunan yanayi a duk zaman", + "newProfile": "Sabuwar bayanin martaba", + "emptyTitle": "Babu bayanan martaba tukuna", + "emptyDescription": "Ƙirƙirar bayanin martaba don sake amfani da masu canjin yanayi, umarnin saitin, da ƙwarewa a cikin zaman.", + "loadFailed": "An kasa loda bayanan martaba", + "counts": { + "vars": "{{count}} masu canji", + "mcp": "{{count}} MCP", + "skills": "{{count}} ƙwarewa", + "commands": "{{count}} umarni", + "varsShort": "{{count}}m", + "skillsShort": "{{count}}ƙ", + "commandsShort": "{{count}}u" + }, + "nameLabel": "Sunan bayanin martaba", + "namePlaceholder": "misali Gyaran baya", + "descriptionLabel": "Bayanin bayanin martaba", + "descriptionPlaceholder": "Bayanin zaɓi", + "ownerLabel": "Ma'abucin bayanin martaba", + "nameRequired": "Shigar da sunan bayanin martaba", + "createAction": "Ƙirƙiri bayanin martaba", + "createFailed": "An kasa ƙirƙirar bayanin martaba", + "createdToast": "An ƙirƙira bayanin martaba \"{{name}}\".", + "settingsSectionTitle": "Kanfigareshan", + "defaultSectionTitle": "Bayanan martaba na asali", + "personalDefaultDescription": "An ɗora ta atomatik lokacin da babu wurin ajiya da ke da bayanin martaba.", + "organizationDefaultDescription": "An ɗora ta atomatik don membobi waɗanda ba su da tsoho na sirri.", + "setAsDefault": "Saita azaman tsoho", + "removeDefault": "Cire azaman tsoho", + "saveFailed": "An kasa ajiye bayanan martaba", + "deleteTitle": "Share bayanin martaba", + "deleteMessage": "Wannan yana cire bayanan martaba da saitunan sa har abada. Ba za a iya soke wannan ba.", + "deleteFailed": "An kasa share bayanan martaba", + "deleteBlocked": "Ana amfani da wannan bayanin martaba ta hanyar faɗakarwa ta yanar gizo. Cire shi daga waɗancan abubuwan tunzura.", + "deletedToast": "An goge bayanin martaba \"{{name}}\".", + "variablesTitle": "Matsalolin muhalli", + "addVariable": "Ƙara m", + "keyLabel": "Maɓalli", + "keyTooLong": "Maɓalli dole ya zama haruffa 256 ko ƙasa da haka", + "keyDuplicate": "Wannan maɓalli ya riga ya kasance a cikin bayanin martaba", + "valueLabel": "Daraja", + "secretLabel": "Sirrin", + "secrets": { + "markSecret": "Yi alama a matsayin sirri", + "masked": "Ƙimar ɓoye", + "reveal": "Bayyana darajar", + "newValuePlaceholder": "Shigar da sabuwar ƙimar sirri", + "valuePlaceholder": "Ƙimar sirri (an ɓoye)" + }, + "variablesEmpty": "Babu masu canji tukuna", + "variablesSaveFailed": "An kasa ajiye canji", + "commandsTitle": "Saita umarni", + "addCommand": "Ƙara umarni", + "commandLabel": "Umurni", + "commandPlaceholder": "e.g. pnpm install", + "commandsEmpty": "Babu umarnin saitin tukuna", + "commandsSaveFailed": "An kasa ajiye umarnin saitin", + "moveUp": "Matsa sama", + "moveDown": "Matsa ƙasa", + "skillsTitle": "Ƙwarewa", + "addSkill": "Ƙara fasaha", + "skillNameLabel": "Sunan gwaninta", + "skillMarkdownLabel": "Abubuwan gwaninta", + "skillsEmpty": "Babu fasaha tukuna", + "skillNameRequired": "Shigar da sunan fasaha", + "skillNameInvalid": "Dole ne sunan gwani ya fara da harafi ko lambobi kuma yayi amfani da ƙananan haruffa kawai, lambobi, da dashes", + "skillContentRequired": "Ana buƙatar abun ciki gwaninta", + "skillSaveFailed": "An kasa ajiye fasaha", + "skillsHint": "Manna SKILL.md kuma sunansa ya cika; ƙara fasaha daga wayarka ba tare da ja da sauke ba.", + "skillEditTitle": "Gyara fasaha", + "mcp": { + "title": "Sabar MCP", + "add": "Ƙara uwar garken MCP", + "empty": "Babu sabar MCP tukuna", + "name": "Sunan uwar garken", + "type": "Nau'in uwar garken", + "localType": "Na gida", + "remoteType": "Nisa", + "command": "Layin umarni", + "url": "URL", + "environment": "Muhalli (JSON)", + "headers": "Shugabanni (JSON)", + "timeout": "Lokaci ya ƙare", + "secretCount": "Ƙididdigar sirri", + "namePlaceholder": "my-server", + "commandPlaceholder": "npx @example/mcp-server", + "jsonPlaceholder": "{\n \"API_KEY\": \"sk-...\"\n}", + "nameRequired": "Shigar da sunan uwar garke", + "nameInvalid": "Sunan zai iya amfani da haruffa kawai, lambobi, ƙararraki, da dashes", + "commandRequired": "Shigar da umarni", + "commandTooLong": "Yi amfani da bai fi hujjoji 50 ba, kowanne haruffa 500", + "urlRequired": "Shigar da URL", + "urlInvalid": "Shigar da ingantaccen URL", + "urlTooLong": "Dole URL ya zama haruffa 2048 ko ƙasa da haka", + "jsonInvalid": "Shigar da ingantacciyar JSON - abu na ƙimar kirtani", + "jsonTooLarge": "Yi amfani da bai fi shigarwa 50 ba, maɓallai har 128 da ƙimomi har haruffa 4096", + "timeoutInvalid": "Ƙayyadaddun lokaci dole ne ya zama cikakken adadin millise seconds", + "edit": "Shirya uwar garken MCP", + "saveFailed": "An kasa ajiye uwar garken MCP" + }, + "agents": { + "title": "Wakilan bayanan martaba", + "add": "Ƙara wakili", + "empty": "Babu wakilai tukuna", + "name": "Sunan wakili", + "slug": "Slug wakili", + "description": "Bayanin wakili", + "model": "Ƙarfafa samfurin", + "modelPlaceholder": "Yi amfani da tsohowar zama", + "prompt": "Tsarin tsari", + "steps": "Matsakaicin matakai", + "stepsPlaceholder": "misali 50", + "temperature": "Zafin jiki", + "temperaturePlaceholder": "misali 0.2", + "topP": "top_p", + "topPPlaceholder": "misali 0.95", + "slugPlaceholder": "my-agent", + "namePlaceholder": "Wakilina", + "promptPlaceholder": "ka a...", + "tools": "Kayan aiki", + "visibility": "Ganuwa", + "visibilityPrimary": "Na farko - wanda aka nuna a cikin mai ɗaukar hoto", + "visibilitySubagent": "Subagent - kawai kira daga wasu", + "visibilityAll": "Duk - duka", + "saveFailed": "An kasa ajiye wakili", + "nameRequired": "Shigar da sunan wakili", + "slugRequired": "Shigar da slug wakili", + "slugInvalid": "Slug dole ne ya fara da harafi kuma yayi amfani da ƙananan haruffa kawai, lambobi, da dashes", + "slugConflict": "Wannan slug an tanada shi ta wani ginannen wakili", + "edit": "Wakilin gyara" + }, + "slashCommands": { + "title": "Slash umarni", + "add": "Ƙara umarnin slash", + "empty": "Babu umarnin yanke hukunci tukuna", + "name": "Sunan umarni", + "description": "Bayanin umarni", + "template": "Samfura", + "namePlaceholder": "explain", + "templatePlaceholder": "Yi bayani $ARGUMENTS", + "agent": "Wakili", + "subtask": "Gudu azaman ƙaramin aiki", + "edit": "Gyara umarnin slash", + "saveFailed": "An kasa ajiye umarni", + "nameRequired": "Shigar da sunan umarni", + "nameInvalid": "Dole ne suna farawa da ƙananan haruffa kuma amfani da ƙananan haruffa kawai, lambobi, da dashes", + "templateRequired": "Shigar da samfurin umarni", + "nameConflict": "An adana wannan sunan ta hanyar ginanniyar umarni" + }, + "repoBindings": { + "title": "Tsoffin Bayanan martaba don Repos", + "add": "Ƙara tsoho", + "empty": "Ba a saita abubuwan da suka dace ba", + "emptyDescription": "Ƙara tsoho don amfani da bayanin martaba ta atomatik lokacin fara zama don repo.", + "repo": "Zaɓi wurin ajiya…", + "profile": "Zaɓi bayanin martaba", + "remove": "Cire daurin", + "private": "Na sirri", + "search": "Bincika wuraren ajiya…", + "addedToast": "An saita tsoffin bayanan martaba don \"{{repo}}\"", + "addFailed": "An kasa saita bayanan martaba na asali", + "removedToast": "An cire tsoffin bayanan martaba don \"{{repo}}\"", + "removeFailed": "An kasa cire tsohon bayanin martaba", + "pinnedTitle": "An liƙa zuwa wuraren ajiya", + "pinRepo": "Shigar da repo", + "pinnedEmpty": "Ba a lika wa kowane ma'ajiyar ajiya ba.", + "pinnedDescription": "Ana lodawa ta atomatik a duk lokacin da wani zama ya yi niyya ga ɗayan waɗannan ma'ajin.", + "pinnedToast": "An liƙa zuwa \"{{repo}}\"", + "pinFailed": "An kasa tantance repo", + "unboundToast": "Repo kwance", + "unbindFailed": "An kasa kwance repo" + } } } diff --git a/apps/mobile/src/i18n/locales/he.json b/apps/mobile/src/i18n/locales/he.json index 95f2316549e..d997684ee37 100644 --- a/apps/mobile/src/i18n/locales/he.json +++ b/apps/mobile/src/i18n/locales/he.json @@ -1699,7 +1699,33 @@ "continueFrom": "ממשיך את הסשן {{title}}.", "importingSession": "מייבא סשן…", "cliCannotContinue": "ה-CLI הזה אינו יכול להמשיך סשן. בחר ב-Cloud Agent או ב-CLI אחר.", - "importFailed": "לא ניתן היה לייבא את הסשן הזה." + "importFailed": "לא ניתן היה לייבא את הסשן הזה.", + "pickProfile": "בחר פרופיל", + "noProfile": "אין פרופיל", + "manageProfiles": "נהל פרופילים", + "noProfiles": "עדיין אין פרופילים. צור אחד כדי להוסיף משתני סביבה, שרתי MCP ומיומנויות.", + "advancedConfig": "תצורה מתקדמת", + "advancedConfigDescription": "בחר פרופיל כדי לטעון תצורות שמורות, ולהגדיר באופן ידני משתני סביבה ופקודות הגדרה נוספות", + "saveAsProfile": "שמור כפרופיל", + "saveProfileDescription": "שמור את משתני הסביבה הנוכחיים ואת פקודות ההגדרה כפרופיל לשימוש חוזר.", + "profileSummary": "{{vars}} משתני סביבה · {{commands}} פקודות הגדרה", + "profileSaved": "הפרופיל \"{{name}}\" נשמר", + "organizationProfiles": "פרופילי ארגון", + "personalProfiles": "פרופילים אישיים", + "yourProfiles": "הפרופילים שלך", + "failedToLoadProfiles": "טעינת הפרופילים נכשלה", + "nameTooLong": "שם הפרופיל ארוך מדי", + "descriptionTooLong": "התיאור ארוך מדי", + "secretsEncrypted": "הסודות מוצפנים לפני האחסון.", + "profileActive": "הפרופיל פעיל", + "profilesActive": "פרופילים פעילים", + "customConfigActive": "תצורה מותאמת אישית פעילה", + "configNeedsAttention": "תצורה זקוקה לתשומת לב", + "repoProfile": "פרופיל Repo", + "selectedProfile": "פרופיל נבחר", + "manualOverrides": "עקיפות ידניות", + "openSettingsToReview": "פתח את ההגדרות כדי לסקור", + "profileSelection": "בחירת פרופיל" }, "partRenderer": { "updatedFileCount_one": "מספר הקבצים שעודכנו הוא {{displayCount}}.", @@ -3267,5 +3293,180 @@ "multiplierError": "הזן מכפיל של 1 או יותר", "permissionDenied": "אין לך הרשאה לנהל התראות הוצאות.", "pushOffByCategory": "כבוי בהתראות" + }, + "profiles": { + "title": "פרופילים", + "entrySubtitle": "שימוש חוזר בהגדרות הסביבה בין הפעלות", + "newProfile": "פרופיל חדש", + "emptyTitle": "עדיין אין פרופילים", + "emptyDescription": "צור פרופיל לשימוש חוזר במשתני סביבה, פקודות הגדרה ומיומנויות בפעילויות שונות.", + "loadFailed": "לא ניתן היה לטעון פרופילים", + "counts": { + "vars": "{{count}} משתנים", + "mcp": "{{count}} MCP", + "skills": "{{count}} מיומנויות", + "commands": "{{count}} פקודות", + "varsShort": "{{count}}מ", + "skillsShort": "{{count}}מי", + "commandsShort": "{{count}}פ" + }, + "nameLabel": "שם פרופיל", + "namePlaceholder": "לְמָשָׁל ניפוי באגים בקצה האחורי", + "descriptionLabel": "תיאור הפרופיל", + "descriptionPlaceholder": "תיאור אופציונלי", + "ownerLabel": "בעל פרופיל", + "nameRequired": "הזן שם פרופיל", + "createAction": "צור פרופיל", + "createFailed": "לא ניתן ליצור פרופיל", + "createdToast": "הפרופיל \"{{name}}\" נוצר", + "settingsSectionTitle": "תְצוּרָה", + "defaultSectionTitle": "פרופיל ברירת מחדל", + "personalDefaultDescription": "נטען אוטומטית כאשר לאף מאגר אין פרופיל מוצמד.", + "organizationDefaultDescription": "נטען אוטומטית עבור חברים שאין להם ברירת מחדל אישית.", + "setAsDefault": "הגדר כברירת מחדל", + "removeDefault": "הסר כברירת מחדל", + "saveFailed": "לא ניתן לשמור את הפרופיל", + "deleteTitle": "מחק את הפרופיל", + "deleteMessage": "פעולה זו מסירה לצמיתות את הפרופיל ואת ההגדרות שלו. לא ניתן לבטל זאת.", + "deleteFailed": "לא ניתן היה למחוק את הפרופיל", + "deleteBlocked": "פרופיל זה נמצא בשימוש על ידי מפעיל webhook. תחילה הסר אותו מהטריגרים האלה.", + "deletedToast": "הפרופיל \"{{name}}\" נמחק", + "variablesTitle": "משתני סביבה", + "addVariable": "הוסף משתנה", + "keyLabel": "מַפְתֵחַ", + "keyTooLong": "המפתח חייב להכיל 256 תווים או פחות", + "keyDuplicate": "המפתח הזה כבר קיים בפרופיל", + "valueLabel": "עֵרֶך", + "secretLabel": "סוֹד", + "secrets": { + "markSecret": "סמן כסוד", + "masked": "ערך נסתר", + "reveal": "לחשוף ערך", + "newValuePlaceholder": "הזן ערך סודי חדש", + "valuePlaceholder": "ערך סודי (מוצפן)" + }, + "variablesEmpty": "עדיין אין משתנים", + "variablesSaveFailed": "לא ניתן לשמור משתנה", + "commandsTitle": "פקודות הגדרה", + "addCommand": "הוסף פקודה", + "commandLabel": "פְּקוּדָה", + "commandPlaceholder": "e.g. pnpm install", + "commandsEmpty": "אין עדיין פקודות הגדרה", + "commandsSaveFailed": "לא ניתן לשמור פקודות הגדרה", + "moveUp": "לזוז למעלה", + "moveDown": "לזוז למטה", + "skillsTitle": "מיומנויות", + "addSkill": "הוסף מיומנות", + "skillNameLabel": "שם מיומנות", + "skillMarkdownLabel": "תוכן מיומנות", + "skillsEmpty": "עדיין אין כישורים", + "skillNameRequired": "הזן שם מיומנות", + "skillNameInvalid": "שם המיומנות חייב להתחיל באות או ספרה ולהשתמש רק באותיות קטנות, ספרות ומקפים", + "skillContentRequired": "נדרש תוכן מיומנות", + "skillSaveFailed": "לא ניתן היה לשמור את המיומנות", + "skillsHint": "הדבק SKILL.md ושמו ימלא; הוסף מיומנות מהטלפון שלך ללא גרירה ושחרור.", + "skillEditTitle": "ערוך מיומנות", + "mcp": { + "title": "שרתי MCP", + "add": "הוסף שרת MCP", + "empty": "עדיין אין שרתי MCP", + "name": "שם השרת", + "type": "סוג שרת", + "localType": "מְקוֹמִי", + "remoteType": "מְרוּחָק", + "command": "שורת הפקודה", + "url": "כתובת אתר", + "environment": "סביבה (JSON)", + "headers": "כותרות (JSON)", + "timeout": "פסק זמן", + "secretCount": "ספירה סודית", + "namePlaceholder": "my-server", + "commandPlaceholder": "npx @example/mcp-server", + "jsonPlaceholder": "{\n \"API_KEY\": \"sk-...\"\n}", + "nameRequired": "הזן שם שרת", + "nameInvalid": "השם יכול להשתמש רק באותיות, מספרים, קווים תחתונים ומקפים", + "commandRequired": "הזן פקודה", + "commandTooLong": "השתמש בעד 50 ארגומנטים של 500 תווים כל אחד", + "urlRequired": "הזן כתובת אתר", + "urlInvalid": "הזן כתובת אתר חוקית", + "urlTooLong": "הכתובת URL חייבת להכיל 2048 תווים או פחות", + "jsonInvalid": "הזן JSON חוקי - אובייקט של ערכי מחרוזת", + "jsonTooLarge": "השתמש בעד 50 ערכים, מפתחות עד 128 וערכים עד 4096 תווים", + "timeoutInvalid": "הזמן הקצוב חייב להיות מספר שלם של אלפיות שניות", + "edit": "ערוך שרת MCP", + "saveFailed": "לא ניתן לשמור את שרת MCP" + }, + "agents": { + "title": "סוכני פרופיל", + "add": "הוסף סוכן", + "empty": "עדיין אין סוכנים", + "name": "שם הסוכן", + "slug": "שבלול סוכן", + "description": "תיאור הסוכן", + "model": "ביטול דגם", + "modelPlaceholder": "השתמש בברירת המחדל של הפגישה", + "prompt": "הודעת מערכת", + "steps": "צעדים מרביים", + "stepsPlaceholder": "למשל 50", + "temperature": "טמפרטורה", + "temperaturePlaceholder": "למשל 0.2", + "topP": "top_p", + "topPPlaceholder": "למשל 0.95", + "slugPlaceholder": "my-agent", + "namePlaceholder": "הסוכן שלי", + "promptPlaceholder": "אתה...", + "tools": "כְּלֵי עֲבוֹדָה", + "visibility": "רְאוּת", + "visibilityPrimary": "ראשי - מוצג בבוחר", + "visibilitySubagent": "סוכן משנה - ניתן להתקשרות מאחרים בלבד", + "visibilityAll": "הכל - שניהם", + "saveFailed": "לא ניתן היה להציל את הסוכן", + "nameRequired": "הזן שם סוכן", + "slugRequired": "הזן שבלול סוכן", + "slugInvalid": "Slug חייב להתחיל באות ולהשתמש רק באותיות קטנות, ספרות ומקפים", + "slugConflict": "השבלול הזה שמור על ידי סוכן מובנה", + "edit": "ערוך סוכן" + }, + "slashCommands": { + "title": "פקודות סלאש", + "add": "הוסף פקודת נטוי", + "empty": "עדיין אין פקודות לוכסן", + "name": "שם פקודה", + "description": "תיאור הפקודה", + "template": "תבנית", + "namePlaceholder": "explain", + "templatePlaceholder": "הסבר $ARGUMENTS", + "agent": "סוֹכֵן", + "subtask": "הפעל כתת-מטלת", + "edit": "ערוך פקודת נטוי", + "saveFailed": "לא ניתן לשמור את הפקודה", + "nameRequired": "הזן שם פקודה", + "nameInvalid": "השם חייב להתחיל באות קטנה ולהשתמש רק באותיות קטנות, ספרות ומקפים", + "templateRequired": "הזן תבנית פקודה", + "nameConflict": "השם הזה שמור על ידי פקודה מובנית" + }, + "repoBindings": { + "title": "פרופילי ברירת מחדל עבור Repos", + "add": "הוסף ברירת מחדל", + "empty": "לא הוגדרו ברירת מחדל", + "emptyDescription": "הוסף ברירת מחדל להחלה אוטומטית של פרופיל בעת תחילת הפעלות עבור ריפו.", + "repo": "בחר מאגר...", + "profile": "בחר פרופיל", + "remove": "לְהַתִיר", + "private": "פְּרָטִי", + "search": "חפש במאגרים...", + "addedToast": "פרופיל ברירת המחדל הוגדר עבור \"{{repo}}\"", + "addFailed": "לא ניתן להגדיר פרופיל ברירת מחדל", + "removedToast": "פרופיל ברירת המחדל הוסר עבור \"{{repo}}\"", + "removeFailed": "לא ניתן להסיר את פרופיל ברירת המחדל", + "pinnedTitle": "מוצמד למאגרים", + "pinRepo": "הצמד ריפו", + "pinnedEmpty": "לא מוצמד לאף מאגרים.", + "pinnedDescription": "נטען אוטומטית בכל פעם שהפעלה מכוונת לאחד מהמאגרים הללו.", + "pinnedToast": "הוצמד אל \"{{repo}}\"", + "pinFailed": "לא ניתן היה להצמיד ריפו", + "unboundToast": "ריפו לא כבול", + "unbindFailed": "לא ניתן היה לבטל את קשירת המאגר" + } } } diff --git a/apps/mobile/src/i18n/locales/hi.json b/apps/mobile/src/i18n/locales/hi.json index f10a59a412e..372c5fcd629 100644 --- a/apps/mobile/src/i18n/locales/hi.json +++ b/apps/mobile/src/i18n/locales/hi.json @@ -1687,7 +1687,33 @@ "continueFrom": "{{title}} जारी रखा जा रहा है।", "importingSession": "सत्र आयात किया जा रहा है", "cliCannotContinue": "यह CLI सत्र जारी नहीं रख सकता। Cloud Agent या कोई अन्य CLI चुनें।", - "importFailed": "यह सत्र आयात नहीं हो सका।" + "importFailed": "यह सत्र आयात नहीं हो सका।", + "pickProfile": "एक प्रोफ़ाइल चुनें", + "noProfile": "कोई प्रोफ़ाइल नहीं", + "manageProfiles": "प्रोफ़ाइल प्रबंधित करें", + "noProfiles": "अभी तक कोई प्रोफ़ाइल नहीं. पर्यावरण चर, एमसीपी सर्वर और कौशल जोड़ने के लिए एक बनाएं।", + "advancedConfig": "उन्नत विन्यास", + "advancedConfigDescription": "सहेजे गए कॉन्फ़िगरेशन को लोड करने के लिए एक प्रोफ़ाइल का चयन करें, और अतिरिक्त पर्यावरण चर और सेटअप कमांड को मैन्युअल रूप से कॉन्फ़िगर करें", + "saveAsProfile": "प्रोफ़ाइल के रूप में सहेजें", + "saveProfileDescription": "वर्तमान परिवेश चर और सेटअप कमांड को पुन: प्रयोज्य प्रोफ़ाइल के रूप में सहेजें।", + "profileSummary": "{{vars}} पर्यावरण चर · {{commands}} सेटअप आदेश", + "profileSaved": "प्रोफ़ाइल \"{{name}}\" सहेजी गई", + "organizationProfiles": "संगठन प्रोफ़ाइल", + "personalProfiles": "व्यक्तिगत प्रोफाइल", + "yourProfiles": "आपकी प्रोफाइल", + "failedToLoadProfiles": "प्रोफ़ाइल लोड करने में विफल", + "nameTooLong": "प्रोफ़ाइल नाम बहुत लंबा है", + "descriptionTooLong": "विवरण बहुत लंबा है", + "secretsEncrypted": "भंडारण से पहले रहस्य एन्क्रिप्ट किए जाते हैं।", + "profileActive": "प्रोफ़ाइल सक्रिय", + "profilesActive": "प्रोफाइल सक्रिय", + "customConfigActive": "कस्टम कॉन्फ़िगरेशन सक्रिय", + "configNeedsAttention": "कॉन्फिग पर ध्यान देने की जरूरत है", + "repoProfile": "रेपो प्रोफ़ाइल", + "selectedProfile": "चयनित प्रोफ़ाइल", + "manualOverrides": "मैन्युअल ओवरराइड", + "openSettingsToReview": "समीक्षा करने के लिए सेटिंग्स खोलें", + "profileSelection": "प्रोफ़ाइल चयन" }, "partRenderer": { "updatedFileCount_one": "{{displayCount}} फ़ाइल अपडेट की गई।", @@ -3224,5 +3250,180 @@ "multiplierError": "1 या उससे अधिक का गुणक दर्ज करें", "permissionDenied": "आपको खर्च की चेतावनियाँ प्रबंधित करने की अनुमति नहीं है।", "pushOffByCategory": "सूचनाओं में बंद" + }, + "profiles": { + "title": "प्रोफाइल", + "entrySubtitle": "सभी सत्रों में पर्यावरण सेटिंग्स का पुन: उपयोग करें", + "newProfile": "नई प्रोफ़ाइल", + "emptyTitle": "अभी तक कोई प्रोफ़ाइल नहीं", + "emptyDescription": "सभी सत्रों में पर्यावरण चर, सेटअप कमांड और कौशल का पुन: उपयोग करने के लिए एक प्रोफ़ाइल बनाएं।", + "loadFailed": "प्रोफ़ाइल लोड नहीं हो सकीं", + "counts": { + "vars": "{{count}} चर", + "mcp": "{{count}} MCP", + "skills": "{{count}} कौशल", + "commands": "{{count}} आदेश", + "varsShort": "{{count}}च", + "skillsShort": "{{count}}क", + "commandsShort": "{{count}}आ" + }, + "nameLabel": "प्रोफ़ाइल नाम", + "namePlaceholder": "जैसे बैकएंड डिबगिंग", + "descriptionLabel": "प्रोफाइल विवरण", + "descriptionPlaceholder": "वैकल्पिक विवरण", + "ownerLabel": "प्रोफ़ाइल स्वामी", + "nameRequired": "प्रोफ़ाइल नाम दर्ज करें", + "createAction": "प्रोफ़ाइल बनाएं", + "createFailed": "प्रोफ़ाइल नहीं बनाई जा सकी", + "createdToast": "प्रोफ़ाइल \"{{name}}\" बनाई गई", + "settingsSectionTitle": "विन्यास", + "defaultSectionTitle": "डिफ़ॉल्ट प्रोफ़ाइल", + "personalDefaultDescription": "जब किसी रिपॉजिटरी में पिन की गई प्रोफ़ाइल न हो तो स्वतः लोड हो जाता है।", + "organizationDefaultDescription": "उन सदस्यों के लिए स्वतः लोड किया गया जिनका कोई व्यक्तिगत डिफ़ॉल्ट नहीं है।", + "setAsDefault": "डिफाल्ट के रूप में सेट", + "removeDefault": "डिफ़ॉल्ट के रूप में हटाएँ", + "saveFailed": "प्रोफ़ाइल सहेजी नहीं जा सकी", + "deleteTitle": "प्रोफ़ाइल हटाएं", + "deleteMessage": "यह प्रोफ़ाइल और उसकी सेटिंग्स को स्थायी रूप से हटा देता है। इसे असंपादित नहीं किया जा सकता है।", + "deleteFailed": "प्रोफ़ाइल हटाई नहीं जा सकी", + "deleteBlocked": "इस प्रोफ़ाइल का उपयोग वेबहुक ट्रिगर द्वारा किया जाता है। पहले इसे उन ट्रिगर्स से हटाएँ।", + "deletedToast": "प्रोफ़ाइल \"{{name}}\" हटा दी गई", + "variablesTitle": "पर्यावरण चर", + "addVariable": "वेरिएबल जोड़ें", + "keyLabel": "चाबी", + "keyTooLong": "कुंजी 256 अक्षरों या उससे कम की होनी चाहिए", + "keyDuplicate": "यह कुंजी पहले से ही प्रोफ़ाइल में मौजूद है", + "valueLabel": "कीमत", + "secretLabel": "गुप्त", + "secrets": { + "markSecret": "रहस्य के रूप में चिह्नित करें", + "masked": "छिपा हुआ मूल्य", + "reveal": "मूल्य प्रकट करें", + "newValuePlaceholder": "नया गुप्त मान दर्ज करें", + "valuePlaceholder": "गुप्त मान (एन्क्रिप्टेड)" + }, + "variablesEmpty": "अभी तक कोई चर नहीं", + "variablesSaveFailed": "वेरिएबल सहेजा नहीं जा सका", + "commandsTitle": "सेटअप आदेश", + "addCommand": "आदेश जोड़ें", + "commandLabel": "आज्ञा", + "commandPlaceholder": "e.g. pnpm install", + "commandsEmpty": "अभी तक कोई सेटअप आदेश नहीं", + "commandsSaveFailed": "सेटअप आदेश सहेजे नहीं जा सके", + "moveUp": "बढ़ाना", + "moveDown": "नीचे की ओर", + "skillsTitle": "कौशल", + "addSkill": "कौशल जोड़ें", + "skillNameLabel": "कौशल का नाम", + "skillMarkdownLabel": "कौशल सामग्री", + "skillsEmpty": "अभी तक कोई कौशल नहीं है", + "skillNameRequired": "एक कौशल नाम दर्ज करें", + "skillNameInvalid": "कौशल का नाम किसी अक्षर या अंक से शुरू होना चाहिए और केवल छोटे अक्षरों, अंकों और डैश का उपयोग करना चाहिए", + "skillContentRequired": "कौशल सामग्री की आवश्यकता है", + "skillSaveFailed": "कौशल सहेजा नहीं जा सका", + "skillsHint": "एक SKILL.md चिपकाएँ और उसका नाम भरें; अपने फ़ोन से बिना खींचें और छोड़े एक कौशल जोड़ें।", + "skillEditTitle": "कौशल संपादित करें", + "mcp": { + "title": "एमसीपी सर्वर", + "add": "एमसीपी सर्वर जोड़ें", + "empty": "अभी तक कोई MCP सर्वर नहीं है", + "name": "सर्वर का नाम", + "type": "सर्वर प्रकार", + "localType": "स्थानीय", + "remoteType": "दूर", + "command": "कमांड लाइन", + "url": "यूआरएल", + "environment": "पर्यावरण (JSON)", + "headers": "हेडर (JSON)", + "timeout": "समयबाह्य", + "secretCount": "गुप्त गिनती", + "namePlaceholder": "my-server", + "commandPlaceholder": "npx @example/mcp-server", + "jsonPlaceholder": "{\n \"API_KEY\": \"sk-...\"\n}", + "nameRequired": "सर्वर नाम दर्ज करें", + "nameInvalid": "नाम में केवल अक्षर, संख्या, अंडरस्कोर और डैश का उपयोग किया जा सकता है", + "commandRequired": "एक आदेश दर्ज करें", + "commandTooLong": "अधिकतम 50 आर्गुमेंट का उपयोग करें, प्रत्येक 500 अक्षरों का", + "urlRequired": "एक यूआरएल दर्ज करें", + "urlInvalid": "एक मान्य यूआरएल दर्ज करें", + "urlTooLong": "URL 2048 अक्षरों या उससे कम का होना चाहिए", + "jsonInvalid": "वैध JSON दर्ज करें - स्ट्रिंग मानों का एक ऑब्जेक्ट", + "jsonTooLarge": "अधिकतम 50 प्रविष्टियाँ उपयोग करें, कुंजियाँ 128 तक और मान 4096 अक्षरों तक", + "timeoutInvalid": "टाइमआउट पूर्णांक मिलीसेकेंड का होना चाहिए", + "edit": "एमसीपी सर्वर संपादित करें", + "saveFailed": "MCP सर्वर सहेजा नहीं जा सका" + }, + "agents": { + "title": "प्रोफ़ाइल एजेंट", + "add": "एजेंट जोड़ें", + "empty": "अभी तक कोई एजेंट नहीं", + "name": "एजेंट का नाम", + "slug": "एजेंट स्लग", + "description": "एजेंट विवरण", + "model": "मॉडल ओवरराइड", + "modelPlaceholder": "सत्र डिफ़ॉल्ट का उपयोग करें", + "prompt": "सिस्टम प्रॉम्प्ट", + "steps": "अधिकतम चरण", + "stepsPlaceholder": "उदा. 50", + "temperature": "तापमान", + "temperaturePlaceholder": "उदा. 0.2", + "topP": "top_p", + "topPPlaceholder": "उदा. 0.95", + "slugPlaceholder": "my-agent", + "namePlaceholder": "मेरा एजेंट", + "promptPlaceholder": "तुम एक ...", + "tools": "औजार", + "visibility": "दृश्यता", + "visibilityPrimary": "प्राथमिक - पिकर में दिखाया गया है", + "visibilitySubagent": "सबएजेंट - केवल दूसरों से कॉल करने योग्य", + "visibilityAll": "सभी - दोनों", + "saveFailed": "एजेंट को बचाया नहीं जा सका", + "nameRequired": "एक एजेंट का नाम दर्ज करें", + "slugRequired": "एक एजेंट स्लग दर्ज करें", + "slugInvalid": "स्लग को एक अक्षर से शुरू करना चाहिए और केवल छोटे अक्षरों, अंकों और डैश का उपयोग करना चाहिए", + "slugConflict": "वह स्लग एक अंतर्निहित एजेंट द्वारा आरक्षित है", + "edit": "एजेंट संपादित करें" + }, + "slashCommands": { + "title": "स्लैश आदेश", + "add": "स्लैश कमांड जोड़ें", + "empty": "अभी तक कोई स्लैश आदेश नहीं", + "name": "आदेश का नाम", + "description": "आदेश विवरण", + "template": "खाका", + "namePlaceholder": "explain", + "templatePlaceholder": "$ARGUMENTS स्पष्ट करें", + "agent": "प्रतिनिधि", + "subtask": "उपकार्य के रूप में चलाएँ", + "edit": "स्लैश कमांड संपादित करें", + "saveFailed": "आदेश सहेजा नहीं जा सका", + "nameRequired": "एक कमांड नाम दर्ज करें", + "nameInvalid": "नाम छोटे अक्षर से शुरू होना चाहिए और केवल छोटे अक्षर, अंक और डैश का उपयोग करना चाहिए", + "templateRequired": "एक कमांड टेम्पलेट दर्ज करें", + "nameConflict": "वह नाम एक अंतर्निहित कमांड द्वारा आरक्षित है" + }, + "repoBindings": { + "title": "रिपोज़ के लिए डिफ़ॉल्ट प्रोफ़ाइल", + "add": "डिफ़ॉल्ट जोड़ें", + "empty": "कोई डिफ़ॉल्ट कॉन्फ़िगर नहीं किया गया", + "emptyDescription": "रेपो के लिए सत्र शुरू करते समय प्रोफ़ाइल को स्वचालित रूप से लागू करने के लिए एक डिफ़ॉल्ट जोड़ें।", + "repo": "रिपॉजिटरी का चयन करें...", + "profile": "प्रोफ़ाइल चुनें", + "remove": "अनबाइंड", + "private": "निजी", + "search": "रिपॉजिटरी खोजें…", + "addedToast": "\"{{repo}}\" के लिए डिफ़ॉल्ट प्रोफ़ाइल सेट", + "addFailed": "डिफ़ॉल्ट प्रोफ़ाइल सेट नहीं किया जा सका", + "removedToast": "\"{{repo}}\" के लिए डिफ़ॉल्ट प्रोफ़ाइल हटा दी गई", + "removeFailed": "डिफ़ॉल्ट प्रोफ़ाइल को हटाया नहीं जा सका", + "pinnedTitle": "रिपॉजिटरी पर पिन किया गया", + "pinRepo": "एक रेपो पिन करें", + "pinnedEmpty": "किसी भी रिपॉजिटरी पर पिन नहीं किया गया।", + "pinnedDescription": "जब भी कोई सत्र इन रिपॉजिटरी में से किसी एक को लक्षित करता है तो स्वचालित रूप से लोड हो जाता है।", + "pinnedToast": "\"{{repo}}\" पर पिन किया गया", + "pinFailed": "रेपो पिन नहीं किया जा सका", + "unboundToast": "रेपो अनबाउंड", + "unbindFailed": "रेपो को अनबाइंड नहीं किया जा सका" + } } } diff --git a/apps/mobile/src/i18n/locales/hr.json b/apps/mobile/src/i18n/locales/hr.json index ac1243729fd..32bc66f1419 100644 --- a/apps/mobile/src/i18n/locales/hr.json +++ b/apps/mobile/src/i18n/locales/hr.json @@ -2219,7 +2219,33 @@ "continueFrom": "Nastavlja se sesija {{title}}.", "importingSession": "Uvoz sesije", "cliCannotContinue": "Ovaj CLI ne može nastaviti sesiju. Odaberi Cloud Agent ili drugi CLI.", - "importFailed": "Uvoz ove sesije nije uspio." + "importFailed": "Uvoz ove sesije nije uspio.", + "pickProfile": "Odaberite profil", + "noProfile": "Bez profila", + "manageProfiles": "Upravljanje profilima", + "noProfiles": "Još nema profila. Stvorite jedan za dodavanje varijabli okoline, MCP poslužitelja i vještina.", + "advancedConfig": "Napredna konfiguracija", + "advancedConfigDescription": "Odaberite profil za učitavanje spremljenih konfiguracija i ručno konfigurirajte dodatne varijable okruženja i naredbe za postavljanje", + "saveAsProfile": "Spremi kao profil", + "saveProfileDescription": "Spremite trenutne varijable okruženja i naredbe za postavljanje kao profil za višekratnu upotrebu.", + "profileSummary": "{{vars}} varijable okruženja · {{commands}} naredbe za postavljanje", + "profileSaved": "Profil \"{{name}}\" spremljen", + "organizationProfiles": "Profili organizacije", + "personalProfiles": "Osobni profili", + "yourProfiles": "Vaši profili", + "failedToLoadProfiles": "Učitavanje profila nije uspjelo", + "nameTooLong": "Ime profila je predugačko", + "descriptionTooLong": "Opis je predug", + "secretsEncrypted": "Tajne su šifrirane prije pohranjivanja.", + "profileActive": "Profil aktivan", + "profilesActive": "Profili aktivni", + "customConfigActive": "Prilagođena konfiguracija aktivna", + "configNeedsAttention": "Konfiguraciji je potrebna pažnja", + "repoProfile": "Repo profil", + "selectedProfile": "Odabrani profil", + "manualOverrides": "Ručna nadjačavanja", + "openSettingsToReview": "Otvorite Postavke za pregled", + "profileSelection": "Izbor profila" }, "partRenderer": { "updatedFileCount_one": "Ažurirana je {{displayCount}} datoteka.", @@ -3267,5 +3293,180 @@ "multiplierError": "Unesi množitelj 1 ili veći", "permissionDenied": "Nemaš dopuštenje za upravljanje upozorenjima o potrošnji.", "pushOffByCategory": "Isključeno u Obavijestima" + }, + "profiles": { + "title": "Profili", + "entrySubtitle": "Ponovno upotrijebite postavke okruženja kroz sesije", + "newProfile": "Novi profil", + "emptyTitle": "Još nema profila", + "emptyDescription": "Izradite profil za ponovno korištenje varijabli okoline, naredbi za postavljanje i vještina u više sesija.", + "loadFailed": "Nije moguće učitati profile", + "counts": { + "vars": "{{count}} varijabli", + "mcp": "{{count}} MCP", + "skills": "{{count}} vještina", + "commands": "{{count}} naredbi", + "varsShort": "{{count}}var", + "skillsShort": "{{count}}vje", + "commandsShort": "{{count}}nar" + }, + "nameLabel": "Naziv profila", + "namePlaceholder": "npr. Pozadinsko otklanjanje pogrešaka", + "descriptionLabel": "Opis profila", + "descriptionPlaceholder": "Opcijski opis", + "ownerLabel": "Vlasnik profila", + "nameRequired": "Unesite naziv profila", + "createAction": "Izradi profil", + "createFailed": "Nije moguće izraditi profil", + "createdToast": "Profil \"{{name}}\" stvoren", + "settingsSectionTitle": "Konfiguracija", + "defaultSectionTitle": "Zadani profil", + "personalDefaultDescription": "Automatski se učitava kada nijedno spremište nema zakačen profil.", + "organizationDefaultDescription": "Automatski se učitava za članove koji nemaju osobne zadane postavke.", + "setAsDefault": "Postavi kao zadano", + "removeDefault": "Ukloni kao zadano", + "saveFailed": "Nije moguće spremiti profil", + "deleteTitle": "Izbriši profil", + "deleteMessage": "Ovo trajno uklanja profil i njegove postavke. Ovo se ne može poništiti.", + "deleteFailed": "Nije moguće izbrisati profil", + "deleteBlocked": "Ovaj profil koristi okidač web-dojavnika. Najprije ga uklonite s tih okidača.", + "deletedToast": "Profil \"{{name}}\" izbrisan", + "variablesTitle": "Varijable okoline", + "addVariable": "Dodaj varijablu", + "keyLabel": "Ključ", + "keyTooLong": "Ključ može imati najviše 256 znakova", + "keyDuplicate": "Ovaj ključ već postoji u profilu", + "valueLabel": "Vrijednost", + "secretLabel": "Tajna", + "secrets": { + "markSecret": "Označi kao tajno", + "masked": "Skrivena vrijednost", + "reveal": "Otkrijte vrijednost", + "newValuePlaceholder": "Unesite novu tajnu vrijednost", + "valuePlaceholder": "Tajna vrijednost (šifrirana)" + }, + "variablesEmpty": "Još nema varijabli", + "variablesSaveFailed": "Nije moguće spremiti varijablu", + "commandsTitle": "Naredbe za postavljanje", + "addCommand": "Dodaj naredbu", + "commandLabel": "Naredba", + "commandPlaceholder": "e.g. pnpm install", + "commandsEmpty": "Još nema naredbi za postavljanje", + "commandsSaveFailed": "Nije moguće spremiti naredbe za postavljanje", + "moveUp": "Pomakni se gore", + "moveDown": "Pomakni se dolje", + "skillsTitle": "Vještine", + "addSkill": "Dodajte vještinu", + "skillNameLabel": "Naziv vještine", + "skillMarkdownLabel": "Sadržaj vještina", + "skillsEmpty": "Još nema vještina", + "skillNameRequired": "Unesite naziv vještine", + "skillNameInvalid": "Naziv vještine mora započeti slovom ili znamenkom i koristiti samo mala slova, znamenke i crtice", + "skillContentRequired": "Potreban je sadržaj vještina", + "skillSaveFailed": "Nije moguće spremiti vještinu", + "skillsHint": "Zalijepite SKILL.md i popunite njegovo ime; dodajte vještinu sa svog telefona bez povlačenja i ispuštanja.", + "skillEditTitle": "Uredi vještinu", + "mcp": { + "title": "MCP poslužitelji", + "add": "Dodajte MCP poslužitelj", + "empty": "Još nema MCP poslužitelja", + "name": "Naziv poslužitelja", + "type": "Vrsta poslužitelja", + "localType": "Lokalni", + "remoteType": "Daljinski", + "command": "Naredbeni redak", + "url": "URL", + "environment": "Okruženje (JSON)", + "headers": "Zaglavlja (JSON)", + "timeout": "Istek vremena", + "secretCount": "Tajno brojanje", + "namePlaceholder": "my-server", + "commandPlaceholder": "npx @example/mcp-server", + "jsonPlaceholder": "{\n \"API_KEY\": \"sk-...\"\n}", + "nameRequired": "Unesite naziv poslužitelja", + "nameInvalid": "Ime može sadržavati samo slova, brojeve, podvlake i crtice", + "commandRequired": "Unesite naredbu", + "commandTooLong": "Koristite najviše 50 argumenata od po 500 znakova", + "urlRequired": "Unesite URL", + "urlInvalid": "Unesite važeći URL", + "urlTooLong": "URL može imati najviše 2048 znakova", + "jsonInvalid": "Unesite važeći JSON — objekt vrijednosti niza", + "jsonTooLarge": "Koristite najviše 50 unosa, ključevi do 128 i vrijednosti do 4096 znakova", + "timeoutInvalid": "Istek mora biti cijeli broj milisekundi", + "edit": "Uredi MCP poslužitelj", + "saveFailed": "Nije moguće spremiti MCP poslužitelj" + }, + "agents": { + "title": "Agenti profila", + "add": "Dodaj agenta", + "empty": "Još nema agenata", + "name": "Ime agenta", + "slug": "Agent puž", + "description": "Opis agenta", + "model": "Nadjačavanje modela", + "modelPlaceholder": "Koristi zadanu vrijednost sesije", + "prompt": "Upit sustava", + "steps": "Maks. koraka", + "stepsPlaceholder": "npr. 50", + "temperature": "Temperatura", + "temperaturePlaceholder": "npr. 0.2", + "topP": "top_p", + "topPPlaceholder": "npr. 0.95", + "slugPlaceholder": "my-agent", + "namePlaceholder": "Moj agent", + "promptPlaceholder": "ti si...", + "tools": "Alati", + "visibility": "Vidljivost", + "visibilityPrimary": "Primarno — prikazano u alatu za odabir", + "visibilitySubagent": "Subagent — samo poziv od drugih", + "visibilityAll": "Sve — oboje", + "saveFailed": "Nije moguće spremiti agenta", + "nameRequired": "Unesite ime agenta", + "slugRequired": "Unesite agenta", + "slugInvalid": "Puž mora započeti slovom i koristiti samo mala slova, znamenke i crtice", + "slugConflict": "Taj je puž rezerviran od strane ugrađenog agenta", + "edit": "Uredi agenta" + }, + "slashCommands": { + "title": "Naredbe kose crte", + "add": "Dodajte naredbu kose crte", + "empty": "Još nema naredbi kose crte", + "name": "Naziv naredbe", + "description": "Opis naredbe", + "template": "Predložak", + "namePlaceholder": "explain", + "templatePlaceholder": "Objasnite $ARGUMENTS", + "agent": "Agent", + "subtask": "Pokreni kao podzadatak", + "edit": "Naredba za uređivanje kose crte", + "saveFailed": "Nije moguće spremiti naredbu", + "nameRequired": "Unesite naziv naredbe", + "nameInvalid": "Ime mora započeti malim slovom i koristiti samo mala slova, znamenke i crtice", + "templateRequired": "Unesite predložak naredbe", + "nameConflict": "To je ime rezervirano ugrađenom naredbom" + }, + "repoBindings": { + "title": "Zadani profili za Repos", + "add": "Dodaj zadano", + "empty": "Nisu konfigurirane zadane postavke", + "emptyDescription": "Dodajte zadanu postavku za automatsku primjenu profila prilikom pokretanja sesija za repo.", + "repo": "Odaberite spremište…", + "profile": "Odaberite profil", + "remove": "Odvezati", + "private": "Privatno", + "search": "Pretraži spremišta…", + "addedToast": "Zadani profil postavljen za \"{{repo}}\"", + "addFailed": "Nije moguće postaviti zadani profil", + "removedToast": "Zadani profil uklonjen za \"{{repo}}\"", + "removeFailed": "Nije moguće ukloniti zadani profil", + "pinnedTitle": "Prikvačeno na spremišta", + "pinRepo": "Prikvači repo", + "pinnedEmpty": "Nije prikvačeno ni na jedno spremište.", + "pinnedDescription": "Automatski se učitava kad god sesija cilja jedno od ovih spremišta.", + "pinnedToast": "Prikvačeno na \"{{repo}}\"", + "pinFailed": "Nije moguće prikvačiti repo", + "unboundToast": "Repo bez veza", + "unbindFailed": "Nije moguće odvezati repo" + } } } diff --git a/apps/mobile/src/i18n/locales/ht.json b/apps/mobile/src/i18n/locales/ht.json index 39da4b07593..c051af3e08b 100644 --- a/apps/mobile/src/i18n/locales/ht.json +++ b/apps/mobile/src/i18n/locales/ht.json @@ -2191,7 +2191,33 @@ "continueFrom": "Nou ap kontinye {{title}}.", "importingSession": "Ap enpòte sesyon an", "cliCannotContinue": "CLI sa a pa ka kontinye yon sesyon. Chwazi Cloud Agent oswa yon lòt CLI.", - "importFailed": "Nou pa t ka enpòte sesyon sa a." + "importFailed": "Nou pa t ka enpòte sesyon sa a.", + "pickProfile": "Chwazi yon pwofil", + "noProfile": "Pa gen pwofil", + "manageProfiles": "Jere pwofil yo", + "noProfiles": "Pa gen pwofil ankò. Kreye youn pou ajoute varyab anviwònman, sèvè MCP, ak konpetans.", + "advancedConfig": "Konfigirasyon avanse", + "advancedConfigDescription": "Chwazi yon pwofil pou chaje konfigirasyon ki sove yo, epi manyèlman konfigirasyon varyab anviwònman adisyonèl ak kòmandman konfigirasyon", + "saveAsProfile": "Sove kòm pwofil", + "saveProfileDescription": "Sove varyab anviwònman aktyèl yo ak kòmandman konfigirasyon kòm yon pwofil ki kapab itilize ankò.", + "profileSummary": "{{vars}} varyab anviwònman · {{commands}} kòmandman konfigirasyon", + "profileSaved": "Pwofil \"{{name}}\" sove", + "organizationProfiles": "Pwofil òganizasyon", + "personalProfiles": "Pwofil pèsonèl", + "yourProfiles": "Des ou", + "failedToLoadProfiles": "Echwe pou chaje pwofil yo", + "nameTooLong": "Non pwofil la twò long", + "descriptionTooLong": "Deskripsyon twò long", + "secretsEncrypted": "Sekrè yo chiffres anvan depo.", + "profileActive": "Profile aktif", + "profilesActive": "Des aktif", + "customConfigActive": "Custom konfigirasyon aktif", + "configNeedsAttention": "Konfigirasyon bezwen atansyon", + "repoProfile": "Repo pwofil", + "selectedProfile": "Chwazi pwofil la", + "manualOverrides": "Manyèl overrides", + "openSettingsToReview": "Louvri Anviwònman pou revize", + "profileSelection": "Seleksyon pwofil" }, "partRenderer": { "updatedFileCount_one": "Nou mete {{displayCount}} fichye ajou.", @@ -3224,5 +3250,180 @@ "multiplierError": "Antre yon miltiplikatè 1 oswa plis", "permissionDenied": "Ou pa gen pèmisyon pou jere alèt depans yo.", "pushOffByCategory": "Fèmen nan Notifikasyon" + }, + "profiles": { + "title": "Pwofil yo", + "entrySubtitle": "Reutilize anviwònman anviwònman atravè sesyon yo", + "newProfile": "Nouvo pwofil", + "emptyTitle": "Pa gen pwofil ankò", + "emptyDescription": "Kreye yon pwofil pou reitilize varyab anviwònman yo, lòd konfigirasyon, ak ladrès atravè sesyon yo.", + "loadFailed": "Pa t 'kapab chaje pwofil yo", + "counts": { + "vars": "{{count}} varyab", + "mcp": "{{count}} MCP", + "skills": "{{count}} konpetans", + "commands": "{{count}} kòmandman", + "varsShort": "{{count}}v", + "skillsShort": "{{count}}k", + "commandsShort": "{{count}}kò" + }, + "nameLabel": "Non pwofil la", + "namePlaceholder": "pa egzanp Backend debogaj", + "descriptionLabel": "Deskripsyon Profile", + "descriptionPlaceholder": "Si ou vle deskripsyon", + "ownerLabel": "Pwopriyetè pwofil", + "nameRequired": "Antre yon non pwofil", + "createAction": "Kreye pwofil", + "createFailed": "Pa t 'kapab kreye pwofil", + "createdToast": "Pwofil \"{{name}}\" kreye", + "settingsSectionTitle": "Konfigirasyon", + "defaultSectionTitle": "Default pwofil", + "personalDefaultDescription": "Oto-chaje lè pa gen okenn depo ki gen yon pwofil kole.", + "organizationDefaultDescription": "Oto-chaje pou manm ki pa gen okenn default pèsonèl.", + "setAsDefault": "Mete kòm default", + "removeDefault": "Retire kòm default", + "saveFailed": "Pa t 'kapab sove pwofil la", + "deleteTitle": "Efase pwofil", + "deleteMessage": "Sa a pou tout tan retire pwofil la ak paramèt li yo. Sa a pa ka defèt.", + "deleteFailed": "Pa t 'kapab efase pwofil la", + "deleteBlocked": "Pwofil sa a itilize pa yon deklanche webhook. Retire li nan deklannche sa yo an premye.", + "deletedToast": "Pwofil \"{{name}}\" efase", + "variablesTitle": "Varyab anviwònman an", + "addVariable": "Ajoute varyab", + "keyLabel": "Kle", + "keyTooLong": "Kle a dwe gen 256 karaktè oswa mwens", + "keyDuplicate": "Kle sa a deja egziste nan pwofil la", + "valueLabel": "Valè", + "secretLabel": "Sekrè", + "secrets": { + "markSecret": "Make kòm sekrè", + "masked": "Valè kache", + "reveal": "Revele valè", + "newValuePlaceholder": "Antre yon nouvo valè sekrè", + "valuePlaceholder": "Valè sekrè (chifre)" + }, + "variablesEmpty": "Pa gen varyab ankò", + "variablesSaveFailed": "Pa t 'kapab sove varyab", + "commandsTitle": "Enstalasyon kòmandman yo", + "addCommand": "Ajoute lòd", + "commandLabel": "Kòmandman", + "commandPlaceholder": "e.g. pnpm install", + "commandsEmpty": "Pa gen lòd konfigirasyon ankò", + "commandsSaveFailed": "Pa t 'kapab sove lòd konfigirasyon", + "moveUp": "Deplase moute", + "moveDown": "Deplase desann", + "skillsTitle": "Konpetans", + "addSkill": "Ajoute konpetans", + "skillNameLabel": "Non konpetans", + "skillMarkdownLabel": "Kontni konpetans", + "skillsEmpty": "Pa gen ladrès ankò", + "skillNameRequired": "Antre yon non konpetans", + "skillNameInvalid": "Non konpetans yo dwe kòmanse ak yon lèt oswa yon chif epi sèvi ak sèlman lèt miniskil, chif, ak tire", + "skillContentRequired": "Kontni konpetans obligatwa", + "skillSaveFailed": "Pa t 'kapab sove konpetans", + "skillsHint": "Kole yon SKILL.md epi non li ranpli; ajoute yon konpetans nan telefòn ou san yo pa trennen ak gout.", + "skillEditTitle": "Edit konpetans", + "mcp": { + "title": "MCP serveurs", + "add": "Ajoute sèvè MCP", + "empty": "Pa gen sèvè MCP ankò", + "name": "Non sèvè", + "type": "Kalite sèvè", + "localType": "Lokal", + "remoteType": "Aleka", + "command": "Liy lòd", + "url": "URL", + "environment": "Anviwònman (JSON)", + "headers": "Tèt (JSON)", + "timeout": "Delè", + "secretCount": "Konte sekrè", + "namePlaceholder": "my-server", + "commandPlaceholder": "npx @example/mcp-server", + "jsonPlaceholder": "{\n \"API_KEY\": \"sk-...\"\n}", + "nameRequired": "Antre yon non sèvè", + "nameInvalid": "Non ka itilize sèlman lèt, nimewo, tirè ak tirè", + "commandRequired": "Antre yon lòd", + "commandTooLong": "Sèvi ak pa plis pase 50 agiman, chak 500 karaktè", + "urlRequired": "Antre yon URL", + "urlInvalid": "Antre yon URL ki valab", + "urlTooLong": "URL la dwe gen 2048 karaktè oswa mwens", + "jsonInvalid": "Antre JSON valab - yon objè ki gen valè fisèl", + "jsonTooLarge": "Sèvi ak pa plis pase 50 antre, kle jiska 128 ak valè jiska 4096 karaktè", + "timeoutInvalid": "Timeout dwe yon kantite antye milisgond", + "edit": "Edit sèvè MCP", + "saveFailed": "Pa t 'kapab sove sèvè MCP" + }, + "agents": { + "title": "Ajan pwofil yo", + "add": "Ajoute ajan", + "empty": "Pa gen ajan ankò", + "name": "Non ajan", + "slug": "Slug ajan", + "description": "Deskripsyon ajan", + "model": "Modèl override", + "modelPlaceholder": "Sèvi ak sesyon default", + "prompt": "Enstriksyon sistèm", + "steps": "Maksimòm etap", + "stepsPlaceholder": "pa ekzanp 50", + "temperature": "Tanperati", + "temperaturePlaceholder": "pa ekzanp 0.2", + "topP": "top_p", + "topPPlaceholder": "pa ekzanp 0.95", + "slugPlaceholder": "my-agent", + "namePlaceholder": "Ajan mwen an", + "promptPlaceholder": "Ou se yon...", + "tools": "Zouti", + "visibility": "Vizibilite", + "visibilityPrimary": "Prensipal - yo montre nan seleksyon an", + "visibilitySubagent": "Subagent - sèlman rele nan men lòt moun", + "visibilityAll": "Tout - tou de", + "saveFailed": "Pa t 'kapab sove ajan", + "nameRequired": "Antre yon non ajan", + "slugRequired": "Antre yon slug ajan", + "slugInvalid": "Slug dwe kòmanse ak yon lèt epi sèvi ak sèlman lèt miniskil, chif, ak tire", + "slugConflict": "Slug sa rezève pa yon ajan entegre", + "edit": "Edit ajan" + }, + "slashCommands": { + "title": "Koupe kòmandman", + "add": "Ajoute lòd koupe", + "empty": "Pa gen kòmandman koupe ankò", + "name": "Non kòmand", + "description": "Kòmandman deskripsyon", + "template": "Modèl", + "namePlaceholder": "explain", + "templatePlaceholder": "Eksplike $ARGUMENTS", + "agent": "Ajan", + "subtask": "Kouri kòm subtach", + "edit": "Edit lòd koupe", + "saveFailed": "Pa t 'kapab sove lòd", + "nameRequired": "Antre yon non kòmand", + "nameInvalid": "Non yo dwe kòmanse ak yon lèt miniskil epi itilize sèlman lèt miniskil, chif ak tire", + "templateRequired": "Antre yon modèl lòd", + "nameConflict": "Non sa a rezève pa yon kòmandman entegre" + }, + "repoBindings": { + "title": "Default Profiles pou Repos", + "add": "Ajoute default", + "empty": "Pa gen par défaut configuré", + "emptyDescription": "Ajoute yon default pou aplike otomatikman yon pwofil lè w kòmanse sesyon yo pou yon repo.", + "repo": "Chwazi depo...", + "profile": "Chwazi pwofil", + "remove": "Demare", + "private": "Prive", + "search": "Chèche depo...", + "addedToast": "Default pwofil mete pou \"{{repo}}\"", + "addFailed": "Pa t 'kapab mete pwofil default", + "removedToast": "Pwofil defo yo retire pou \"{{repo}}\"", + "removeFailed": "Pa t 'kapab retire pwofil default", + "pinnedTitle": "Mete sou repozitwa yo", + "pinRepo": "Pin yon repo", + "pinnedEmpty": "Pa kole nan okenn depo.", + "pinnedDescription": "Oto-chaje chak fwa yon sesyon vize youn nan depo sa yo.", + "pinnedToast": "Mete sou \"{{repo}}\"", + "pinFailed": "Pa t 'kapab pin repo", + "unboundToast": "Depo demare", + "unbindFailed": "Pa t 'kapab dezole repo" + } } } diff --git a/apps/mobile/src/i18n/locales/hu.json b/apps/mobile/src/i18n/locales/hu.json index 72d01883ceb..579512e126c 100644 --- a/apps/mobile/src/i18n/locales/hu.json +++ b/apps/mobile/src/i18n/locales/hu.json @@ -2191,7 +2191,33 @@ "continueFrom": "Ezt a munkamenetet folytatod: {{title}}.", "importingSession": "Munkamenet importálása…", "cliCannotContinue": "Ez a CLI nem támogatja a munkamenetek folytatását. Válaszd a Cloud Agentet vagy egy másik CLI-t.", - "importFailed": "Nem sikerült importálni ezt a munkamenetet." + "importFailed": "Nem sikerült importálni ezt a munkamenetet.", + "pickProfile": "Válassz egy profilt", + "noProfile": "Nincs profil", + "manageProfiles": "Profilok kezelése", + "noProfiles": "Még nincsenek profilok. Hozzon létre egyet a környezeti változók, MCP-kiszolgálók és készségek hozzáadásához.", + "advancedConfig": "Speciális konfiguráció", + "advancedConfigDescription": "Válasszon egy profilt a mentett konfigurációk betöltéséhez, és manuálisan konfigurálja a további környezeti változókat és beállítási parancsokat", + "saveAsProfile": "Mentés profilként", + "saveProfileDescription": "Mentse el az aktuális környezeti változókat és a beállítási parancsokat újrafelhasználható profilként.", + "profileSummary": "{{vars}} környezeti változók · {{commands}} beállítási parancsok", + "profileSaved": "A \"{{name}}\" profil mentve", + "organizationProfiles": "Szervezeti profilok", + "personalProfiles": "Személyes profilok", + "yourProfiles": "Az Ön profiljai", + "failedToLoadProfiles": "Nem sikerült betölteni a profilokat", + "nameTooLong": "A profilnév túl hosszú", + "descriptionTooLong": "A leírás túl hosszú", + "secretsEncrypted": "A titkokat tárolás előtt titkosítják.", + "profileActive": "Profil aktív", + "profilesActive": "Profilok aktívak", + "customConfigActive": "Egyéni konfiguráció aktív", + "configNeedsAttention": "A konfiguráció figyelmet igényel", + "repoProfile": "Repo profil", + "selectedProfile": "Kiválasztott profil", + "manualOverrides": "Kézi felülírások", + "openSettingsToReview": "Nyissa meg a Beállításokat az áttekintéshez", + "profileSelection": "Profil kiválasztása" }, "partRenderer": { "updatedFileCount_one": "{{displayCount}} fájl frissült.", @@ -3224,5 +3250,180 @@ "multiplierError": "Adj meg 1 vagy nagyobb szorzót", "permissionDenied": "Nincs jogosultságod a költésfigyelmeztetések kezelésére.", "pushOffByCategory": "Értesítésekben kikapcsolva" + }, + "profiles": { + "title": "Profilok", + "entrySubtitle": "Használja újra a környezeti beállításokat a munkamenetekben", + "newProfile": "Új profil", + "emptyTitle": "Még nincsenek profilok", + "emptyDescription": "Hozzon létre egy profilt a környezeti változók, a beállítási parancsok és a készségek munkamenetek közötti újrafelhasználásához.", + "loadFailed": "Nem sikerült betölteni a profilokat", + "counts": { + "vars": "{{count}} változó", + "mcp": "{{count}} MCP", + "skills": "{{count}} készség", + "commands": "{{count}} parancs", + "varsShort": "{{count}}v", + "skillsShort": "{{count}}k", + "commandsShort": "{{count}}p" + }, + "nameLabel": "Profil neve", + "namePlaceholder": "például Háttérbeli hibakeresés", + "descriptionLabel": "Profil leírása", + "descriptionPlaceholder": "Opcionális leírás", + "ownerLabel": "Profil tulajdonosa", + "nameRequired": "Adjon meg egy profilnevet", + "createAction": "Profil létrehozása", + "createFailed": "Nem sikerült profilt létrehozni", + "createdToast": "A \"{{name}}\" profil létrehozva", + "settingsSectionTitle": "Konfiguráció", + "defaultSectionTitle": "Alapértelmezett profil", + "personalDefaultDescription": "Automatikusan betöltődik, ha egyetlen lerakat sem rendelkezik rögzített profillal.", + "organizationDefaultDescription": "Automatikusan betöltve azon tagok számára, akiknek nincs személyes alapértelmezésük.", + "setAsDefault": "Beállítás alapértelmezettként", + "removeDefault": "Eltávolítás alapértelmezettként", + "saveFailed": "Nem sikerült menteni a profilt", + "deleteTitle": "Profil törlése", + "deleteMessage": "Ezzel véglegesen eltávolítja a profilt és beállításait. Ezt nem lehet visszavonni.", + "deleteFailed": "Nem sikerült törölni a profilt", + "deleteBlocked": "Ezt a profilt egy webhook-indító használja. Először távolítsa el a triggerekből.", + "deletedToast": "A \"{{name}}\" profil törölve", + "variablesTitle": "Környezeti változók", + "addVariable": "Változó hozzáadása", + "keyLabel": "Kulcsfontosságú", + "keyTooLong": "A kulcs legfeljebb 256 karakter lehet", + "keyDuplicate": "Ez a kulcs már létezik a profilban", + "valueLabel": "Érték", + "secretLabel": "Titkos", + "secrets": { + "markSecret": "Jelölje meg titkosként", + "masked": "Rejtett érték", + "reveal": "Érték feltárása", + "newValuePlaceholder": "Adj meg új titkos értéket", + "valuePlaceholder": "Titkos érték (titkosítva)" + }, + "variablesEmpty": "Még nincsenek változók", + "variablesSaveFailed": "Nem sikerült menteni a változót", + "commandsTitle": "Beállítási parancsok", + "addCommand": "Parancs hozzáadása", + "commandLabel": "Parancs", + "commandPlaceholder": "e.g. pnpm install", + "commandsEmpty": "Még nincsenek beállítási parancsok", + "commandsSaveFailed": "Nem sikerült menteni a beállítási parancsokat", + "moveUp": "Lépj felfelé", + "moveDown": "Mozgás lefelé", + "skillsTitle": "Készségek", + "addSkill": "Add hozzá a készségeket", + "skillNameLabel": "Képesség neve", + "skillMarkdownLabel": "Ügyességi tartalom", + "skillsEmpty": "Még nincsenek készségek", + "skillNameRequired": "Adja meg a készség nevét", + "skillNameInvalid": "A készség nevének betűvel vagy számjegygel kell kezdődnie, és csak kisbetűket, számjegyeket és kötőjeleket kell használnia", + "skillContentRequired": "Képzettségi tartalom szükséges", + "skillSaveFailed": "Nem sikerült megmenteni a készségeket", + "skillsHint": "Illesszen be egy SKILL.md fájlt, és a neve kitölti; adj hozzá egy képességet a telefonodról fogd és vidd nélkül.", + "skillEditTitle": "Képesség szerkesztése", + "mcp": { + "title": "MCP szerverek", + "add": "MCP szerver hozzáadása", + "empty": "Még nincsenek MCP-kiszolgálók", + "name": "Szerver neve", + "type": "Szerver típusa", + "localType": "Helyi", + "remoteType": "Távoli", + "command": "Parancssor", + "url": "URL", + "environment": "Környezet (JSON)", + "headers": "Fejlécek (JSON)", + "timeout": "Időtúllépés", + "secretCount": "Titkos szám", + "namePlaceholder": "my-server", + "commandPlaceholder": "npx @example/mcp-server", + "jsonPlaceholder": "{\n \"API_KEY\": \"sk-...\"\n}", + "nameRequired": "Adja meg a szerver nevét", + "nameInvalid": "A név csak betűket, számokat, aláhúzásjeleket és kötőjeleket tartalmazhat", + "commandRequired": "Írjon be egy parancsot", + "commandTooLong": "Legfeljebb 50 argumentumot használjon, egyenként 500 karakterrel", + "urlRequired": "Adjon meg egy URL-t", + "urlInvalid": "Adjon meg egy érvényes URL-t", + "urlTooLong": "Az URL legfeljebb 2048 karakter lehet", + "jsonInvalid": "Írjon be érvényes JSON-t – karakterlánc-értékekből álló objektumot", + "jsonTooLarge": "Legfeljebb 50 bejegyzést használjon, kulcsok 128-ig és értékek 4096 karakterig", + "timeoutInvalid": "Az időtúllépésnek egész számú ezredmásodpercnek kell lennie", + "edit": "MCP szerver szerkesztése", + "saveFailed": "Nem sikerült menteni az MCP-kiszolgálót" + }, + "agents": { + "title": "Profil ügynökök", + "add": "Ügynök hozzáadása", + "empty": "Még nincsenek ügynökök", + "name": "Ügynök neve", + "slug": "Ügynökcsiga", + "description": "Ügynök leírása", + "model": "Modell felülbírálása", + "modelPlaceholder": "A munkamenet alapértelmezett használata", + "prompt": "Rendszer prompt", + "steps": "Max. lépések", + "stepsPlaceholder": "pl. 50", + "temperature": "Hőmérséklet", + "temperaturePlaceholder": "pl. 0.2", + "topP": "top_p", + "topPPlaceholder": "pl. 0.95", + "slugPlaceholder": "my-agent", + "namePlaceholder": "Ügynököm", + "promptPlaceholder": "Ön egy...", + "tools": "Eszközök", + "visibility": "Láthatóság", + "visibilityPrimary": "Elsődleges – a választóban látható", + "visibilitySubagent": "Alügynök – csak másoktól hívható", + "visibilityAll": "Mind - mindkettő", + "saveFailed": "Nem sikerült menteni az ügynököt", + "nameRequired": "Adja meg az ügynök nevét", + "slugRequired": "Adjon meg egy ügynökcsiga", + "slugInvalid": "A Slug-nak betűvel kell kezdődnie, és csak kisbetűket, számjegyeket és kötőjeleket kell használnia", + "slugConflict": "Ezt a csigát egy beépített ügynök foglalta le", + "edit": "Ügynök szerkesztése" + }, + "slashCommands": { + "title": "Slash parancsok", + "add": "Add hozzá a perjel parancsot", + "empty": "Még nincsenek perjel parancsok", + "name": "A parancs neve", + "description": "A parancs leírása", + "template": "Sablon", + "namePlaceholder": "explain", + "templatePlaceholder": "Magyarázza el $ARGUMENTS", + "agent": "Ügynök", + "subtask": "Futtassa részfeladatként", + "edit": "Szerkessze a perjel parancsot", + "saveFailed": "Nem sikerült menteni a parancsot", + "nameRequired": "Adja meg a parancs nevét", + "nameInvalid": "A névnek kisbetűvel kell kezdődnie, és csak kisbetűket, számjegyeket és kötőjeleket kell használnia", + "templateRequired": "Írjon be egy parancssablont", + "nameConflict": "Ezt a nevet egy beépített parancs foglalja le" + }, + "repoBindings": { + "title": "Alapértelmezett profilok repóhoz", + "add": "Alapértelmezés hozzáadása", + "empty": "Nincsenek alapértelmezett beállítások", + "emptyDescription": "Adjon hozzá egy alapértelmezett profilt, amely automatikusan alkalmazza a profilt a repo munkameneteinek indításakor.", + "repo": "Tárhely kiválasztása…", + "profile": "Profil kiválasztása", + "remove": "Kiold", + "private": "Magán", + "search": "Adattárak keresése…", + "addedToast": "Alapértelmezett profil beállítva a következőhöz: \"{{repo}}\"", + "addFailed": "Nem sikerült beállítani az alapértelmezett profilt", + "removedToast": "A(z) „{{repo}}” alapértelmezett profilja eltávolítva", + "removeFailed": "Nem sikerült eltávolítani az alapértelmezett profilt", + "pinnedTitle": "Rögzítve a tárhelyekhez", + "pinRepo": "Repo rögzítése", + "pinnedEmpty": "Nincs rögzítve egyetlen adattárhoz sem.", + "pinnedDescription": "Automatikusan betöltődik, amikor egy munkamenet megcélozza ezen lerakatok valamelyikét.", + "pinnedToast": "Rögzítve a következőhöz: \"{{repo}}\"", + "pinFailed": "Nem sikerült rögzíteni a repót", + "unboundToast": "Repo kötetlen", + "unbindFailed": "Nem sikerült feloldani a repót" + } } } diff --git a/apps/mobile/src/i18n/locales/hy.json b/apps/mobile/src/i18n/locales/hy.json index 1070b55c339..f32b3e76c1d 100644 --- a/apps/mobile/src/i18n/locales/hy.json +++ b/apps/mobile/src/i18n/locales/hy.json @@ -2191,7 +2191,33 @@ "continueFrom": "Շարունակվում է «{{title}}» նիստը։", "importingSession": "Նիստը ներմուծվում է", "cliCannotContinue": "Այս CLI-ն չի կարող շարունակել նիստը։ Ընտրեք Cloud Agent-ը կամ մեկ այլ CLI։", - "importFailed": "Չհաջողվեց ներմուծել այս նիստը։" + "importFailed": "Չհաջողվեց ներմուծել այս նիստը։", + "pickProfile": "Ընտրեք պրոֆիլ", + "noProfile": "Ոչ մի պրոֆիլ", + "manageProfiles": "Կառավարեք պրոֆիլները", + "noProfiles": "Դեռևս պրոֆիլներ չկան: Ստեղծեք մեկը՝ շրջակա միջավայրի փոփոխականներ, MCP սերվերներ և հմտություններ ավելացնելու համար:", + "advancedConfig": "Ընդլայնված կոնֆիգուրացիա", + "advancedConfigDescription": "Ընտրեք պրոֆիլ՝ պահպանված կոնֆիգուրացիաները բեռնելու համար և ձեռքով կարգավորեք շրջակա միջավայրի լրացուցիչ փոփոխականները և կարգաբերման հրամանները", + "saveAsProfile": "Պահպանել որպես պրոֆիլ", + "saveProfileDescription": "Պահպանեք ընթացիկ միջավայրի փոփոխականները և կարգաբերման հրամանները որպես բազմակի օգտագործման պրոֆիլ:", + "profileSummary": "{{vars}} միջավայրի փոփոխականներ · {{commands}} կարգաբերման հրամաններ", + "profileSaved": "«{{name}}» պրոֆիլը պահված է", + "organizationProfiles": "Կազմակերպության պրոֆիլներ", + "personalProfiles": "Անձնական պրոֆիլներ", + "yourProfiles": "Ձեր պրոֆիլները", + "failedToLoadProfiles": "Չհաջողվեց բեռնել պրոֆիլները", + "nameTooLong": "Պրոֆիլի անունը չափազանց երկար է", + "descriptionTooLong": "Նկարագրությունը չափազանց երկար է", + "secretsEncrypted": "Գաղտնիքները կոդավորված են պահեստավորումից առաջ:", + "profileActive": "Պրոֆիլն ակտիվ է", + "profilesActive": "Ակտիվ պրոֆիլներ", + "customConfigActive": "Հատուկ կազմաձևը ակտիվ է", + "configNeedsAttention": "Config-ը ուշադրության կարիք ունի", + "repoProfile": "Repo պրոֆիլը", + "selectedProfile": "Ընտրված պրոֆիլ", + "manualOverrides": "Ձեռքով անտեսում", + "openSettingsToReview": "Բացեք Կարգավորումները՝ վերանայելու համար", + "profileSelection": "Պրոֆիլի ընտրություն" }, "partRenderer": { "updatedFileCount_one": "Թարմացվել է {{displayCount}} ֆայլ։", @@ -3224,5 +3250,180 @@ "multiplierError": "Մուտքագրիր 1 կամ ավելի բազմապատկիչ", "permissionDenied": "Դուք ծախսերի ծանուցումները կառավարելու թույլտվություն չունեք։", "pushOffByCategory": "Անջատված է Ծանուցումներում" + }, + "profiles": { + "title": "Պրոֆիլներ", + "entrySubtitle": "Կրկին օգտագործեք միջավայրի կարգավորումները նիստերի ընթացքում", + "newProfile": "Նոր պրոֆիլ", + "emptyTitle": "Դեռևս պրոֆիլներ չկան", + "emptyDescription": "Ստեղծեք պրոֆիլ՝ շրջակա միջավայրի փոփոխականները, կարգաբերման հրամանները և հմտությունները սեսիաներում կրկին օգտագործելու համար:", + "loadFailed": "Չհաջողվեց բեռնել պրոֆիլները", + "counts": { + "vars": "{{count}} փոփոխական", + "mcp": "{{count}} MCP", + "skills": "{{count}} հմտություն", + "commands": "{{count}} հրաման", + "varsShort": "{{count}}փ", + "skillsShort": "{{count}}հ", + "commandsShort": "{{count}}հր" + }, + "nameLabel": "Պրոֆիլի անունը", + "namePlaceholder": "օր. Backend-ի վրիպազերծում", + "descriptionLabel": "Պրոֆիլի նկարագրություն", + "descriptionPlaceholder": "Ընտրովի նկարագրություն", + "ownerLabel": "Պրոֆիլի սեփականատեր", + "nameRequired": "Մուտքագրեք պրոֆիլի անունը", + "createAction": "Ստեղծել պրոֆիլ", + "createFailed": "Չհաջողվեց պրոֆիլ ստեղծել", + "createdToast": "Ստեղծվել է «{{name}}» պրոֆիլը", + "settingsSectionTitle": "Կոնֆիգուրացիա", + "defaultSectionTitle": "Կանխադրված պրոֆիլը", + "personalDefaultDescription": "Ավտոմատ բեռնվում է, երբ ոչ մի պահեստ չունի ամրացված պրոֆիլ:", + "organizationDefaultDescription": "Ավտոմատ բեռնված այն անդամների համար, ովքեր չունեն անձնական լռելյայն:", + "setAsDefault": "Սահմանել որպես լռելյայն", + "removeDefault": "Հեռացնել որպես լռելյայն", + "saveFailed": "Չհաջողվեց պահպանել պրոֆիլը", + "deleteTitle": "Ջնջել պրոֆիլը", + "deleteMessage": "Սա ընդմիշտ կհեռացնի պրոֆիլը և դրա կարգավորումները: Սա հնարավոր չէ հետարկել:", + "deleteFailed": "Չհաջողվեց ջնջել պրոֆիլը", + "deleteBlocked": "Այս պրոֆիլն օգտագործվում է webhook գործարկիչով: Նախ հեռացրեք այն այդ ձգաններից:", + "deletedToast": "«{{name}}» պրոֆիլը ջնջվել է", + "variablesTitle": "Շրջակա միջավայրի փոփոխականներ", + "addVariable": "Ավելացնել փոփոխական", + "keyLabel": "Բանալին", + "keyTooLong": "Բանալին պետք է լինի 256 նիշ կամ ավելի քիչ", + "keyDuplicate": "Այս բանալին արդեն գոյություն ունի պրոֆիլում", + "valueLabel": "Արժեք", + "secretLabel": "Գաղտնիք", + "secrets": { + "markSecret": "Նշել որպես գաղտնի", + "masked": "Թաքնված արժեք", + "reveal": "Բացահայտել արժեքը", + "newValuePlaceholder": "Մուտքագրեք նոր գաղտնի արժեք", + "valuePlaceholder": "Գաղտնի արժեք (գաղտնագրված)" + }, + "variablesEmpty": "Փոփոխականներ դեռ չկան", + "variablesSaveFailed": "Չհաջողվեց պահպանել փոփոխականը", + "commandsTitle": "Կարգավորման հրամաններ", + "addCommand": "Ավելացնել հրաման", + "commandLabel": "Հրաման", + "commandPlaceholder": "e.g. pnpm install", + "commandsEmpty": "Կարգավորման հրամաններ դեռ չկան", + "commandsSaveFailed": "Չհաջողվեց պահել կարգավորումների հրամանները", + "moveUp": "Շարժվեք վերև", + "moveDown": "Տեղափոխեք ներքև", + "skillsTitle": "Հմտություններ", + "addSkill": "Ավելացնել հմտություն", + "skillNameLabel": "Հմտության անուն", + "skillMarkdownLabel": "Հմտությունների բովանդակություն", + "skillsEmpty": "Դեռևս ոչ մի հմտություններ", + "skillNameRequired": "Մուտքագրեք հմտության անուն", + "skillNameInvalid": "Հմտության անունը պետք է սկսվի տառով կամ թվանշանով և օգտագործի միայն փոքրատառեր, թվանշաններ և գծիկներ", + "skillContentRequired": "Հմտությունների բովանդակությունը պարտադիր է", + "skillSaveFailed": "Չհաջողվեց պահպանել հմտությունը", + "skillsHint": "Տեղադրեք SKILL.md-ը և լրացվում է դրա անունը; ավելացնել հմտություն ձեր հեռախոսից՝ առանց քաշելու և թողնելու:", + "skillEditTitle": "Խմբագրման հմտություն", + "mcp": { + "title": "MCP սերվերներ", + "add": "Ավելացնել MCP սերվեր", + "empty": "MCP սերվերներ դեռ չկան", + "name": "Սերվերի անունը", + "type": "Սերվերի տեսակը", + "localType": "Տեղական", + "remoteType": "Հեռակառավարվող", + "command": "Հրամանի տող", + "url": "URL", + "environment": "Շրջակա միջավայր (JSON)", + "headers": "Վերնագրեր (JSON)", + "timeout": "Ժամկետանց", + "secretCount": "Գաղտնի հաշվարկ", + "namePlaceholder": "my-server", + "commandPlaceholder": "npx @example/mcp-server", + "jsonPlaceholder": "{\n \"API_KEY\": \"sk-...\"\n}", + "nameRequired": "Մուտքագրեք սերվերի անունը", + "nameInvalid": "Անունը կարող է օգտագործել միայն տառեր, թվեր, ընդգծումներ և գծիկներ", + "commandRequired": "Մուտքագրեք հրաման", + "commandTooLong": "Օգտագործեք առավելագույնը 50 արգումենտ՝ յուրաքանչյուրը 500 նիշ", + "urlRequired": "Մուտքագրեք URL", + "urlInvalid": "Մուտքագրեք վավեր URL", + "urlTooLong": "URL-ը պետք է լինի 2048 նիշ կամ ավելի քիչ", + "jsonInvalid": "Մուտքագրեք վավեր JSON՝ լարային արժեքների օբյեկտ", + "jsonTooLarge": "Օգտագործեք առավելագույնը 50 մուտք, բանալիներ՝ մինչև 128 և արժեքներ՝ մինչև 4096 նիշ", + "timeoutInvalid": "Ժամկետը պետք է լինի միլիվայրկյանների ամբողջ քանակ", + "edit": "Խմբագրել MCP սերվերը", + "saveFailed": "Չհաջողվեց պահպանել MCP սերվերը" + }, + "agents": { + "title": "Պրոֆիլային գործակալներ", + "add": "Ավելացնել գործակալ", + "empty": "Գործակալներ դեռ չկան", + "name": "Գործակալի անունը", + "slug": "Գործակալ slug", + "description": "Գործակալի նկարագրությունը", + "model": "Մոդելի վերացում", + "modelPlaceholder": "Օգտագործեք նստաշրջանի լռելյայն", + "prompt": "Համակարգի հուշում", + "steps": "Առավելագույն քայլեր", + "stepsPlaceholder": "օր. 50", + "temperature": "Ջերմաստիճան", + "temperaturePlaceholder": "օր. 0.2", + "topP": "top_p", + "topPPlaceholder": "օր. 0.95", + "slugPlaceholder": "my-agent", + "namePlaceholder": "Իմ գործակալը", + "promptPlaceholder": "Դու մի...", + "tools": "Գործիքներ", + "visibility": "Տեսանելիություն", + "visibilityPrimary": "Առաջնային — ցուցադրվում է ընտրիչում", + "visibilitySubagent": "Ենթաբաժին. կարելի է կանչել միայն ուրիշներից", + "visibilityAll": "Բոլորը - երկուսն էլ", + "saveFailed": "Չհաջողվեց փրկել գործակալին", + "nameRequired": "Մուտքագրեք գործակալի անունը", + "slugRequired": "Մուտքագրեք գործակալ slug", + "slugInvalid": "Slug-ը պետք է սկսվի տառով և օգտագործի միայն փոքրատառեր, թվանշաններ և գծիկներ", + "slugConflict": "Այդ slug-ը վերապահված է ներկառուցված գործակալի կողմից", + "edit": "Խմբագրել գործակալ" + }, + "slashCommands": { + "title": "Slash հրամաններ", + "add": "Ավելացնել շեղ հրաման", + "empty": "Շեղի հրամաններ դեռ չկան", + "name": "Հրամանի անունը", + "description": "Հրամանի նկարագրություն", + "template": "Կաղապար", + "namePlaceholder": "explain", + "templatePlaceholder": "Բացատրեք $ARGUMENTS", + "agent": "Գործակալ", + "subtask": "Գործարկել որպես ենթաառաջադրանք", + "edit": "Խմբագրել շեղ հրամանը", + "saveFailed": "Չհաջողվեց պահել հրամանը", + "nameRequired": "Մուտքագրեք հրամանի անունը", + "nameInvalid": "Անունը պետք է սկսվի փոքրատառով և օգտագործի միայն փոքրատառեր, թվանշաններ և գծիկներ", + "templateRequired": "Մուտքագրեք հրամանի ձևանմուշ", + "nameConflict": "Այդ անունը վերապահված է ներկառուցված հրամանով" + }, + "repoBindings": { + "title": "Կանխադրված պրոֆիլներ ռեպո-ների համար", + "add": "Ավելացնել կանխադրված", + "empty": "Կանխադրված կարգավորումներ չկան", + "emptyDescription": "Ավելացրեք լռելյայն՝ ռեպո նստաշրջաններ սկսելիս պրոֆիլը ավտոմատ կերպով կիրառելու համար:", + "repo": "Ընտրեք պահեստ…", + "profile": "Ընտրեք պրոֆիլը", + "remove": "Ապակապել", + "private": "Մասնավոր", + "search": "Որոնել պահոցներ…", + "addedToast": "«{{repo}}»-ի կանխադրված պրոֆիլը սահմանված է", + "addFailed": "Չհաջողվեց սահմանել կանխադրված պրոֆիլը", + "removedToast": "«{{repo}}»-ի կանխադրված պրոֆիլը հեռացվել է", + "removeFailed": "Չհաջողվեց հեռացնել կանխադրված պրոֆիլը", + "pinnedTitle": "Ամրացված է պահոցներին", + "pinRepo": "Ամրացրեք ռեպո", + "pinnedEmpty": "Ամրացված չէ որևէ պահեստում:", + "pinnedDescription": "Ավտոմատ բեռնվում է, երբ նիստն ուղղված է այս պահոցներից մեկին:", + "pinnedToast": "Ամրացված է «{{repo}}»-ին", + "pinFailed": "Չհաջողվեց ամրացնել ռեպո", + "unboundToast": "Repo-ն չկապված", + "unbindFailed": "Չհաջողվեց անջատել ռեպո-ն" + } } } diff --git a/apps/mobile/src/i18n/locales/id.json b/apps/mobile/src/i18n/locales/id.json index 4a229395ca8..39546961b2e 100644 --- a/apps/mobile/src/i18n/locales/id.json +++ b/apps/mobile/src/i18n/locales/id.json @@ -1687,7 +1687,33 @@ "continueFrom": "Melanjutkan sesi {{title}}.", "importingSession": "Mengimpor sesi", "cliCannotContinue": "CLI ini tidak dapat melanjutkan sesi. Pilih Cloud Agent atau CLI lain.", - "importFailed": "Tidak dapat mengimpor sesi ini." + "importFailed": "Tidak dapat mengimpor sesi ini.", + "pickProfile": "Pilih profil", + "noProfile": "Tidak ada profil", + "manageProfiles": "Kelola profil", + "noProfiles": "Belum ada profil. Buat satu untuk menambahkan variabel lingkungan, server MCP, dan keterampilan.", + "advancedConfig": "Konfigurasi Lanjutan", + "advancedConfigDescription": "Pilih profil untuk memuat konfigurasi tersimpan, dan konfigurasikan variabel lingkungan tambahan dan perintah pengaturan secara manual", + "saveAsProfile": "Simpan sebagai Profil", + "saveProfileDescription": "Simpan variabel lingkungan saat ini dan perintah pengaturan sebagai profil yang dapat digunakan kembali.", + "profileSummary": "{{vars}} variabel lingkungan · {{commands}} perintah pengaturan", + "profileSaved": "Profil \"{{name}}\" disimpan", + "organizationProfiles": "Profil Organisasi", + "personalProfiles": "Profil Pribadi", + "yourProfiles": "Profil Anda", + "failedToLoadProfiles": "Gagal memuat profil", + "nameTooLong": "Nama profil terlalu panjang", + "descriptionTooLong": "Deskripsi terlalu panjang", + "secretsEncrypted": "Rahasia dienkripsi sebelum disimpan.", + "profileActive": "Profil aktif", + "profilesActive": "Profil aktif", + "customConfigActive": "Konfigurasi khusus aktif", + "configNeedsAttention": "Konfigurasi perlu diperhatikan", + "repoProfile": "Profil repo", + "selectedProfile": "Profil yang dipilih", + "manualOverrides": "Penggantian manual", + "openSettingsToReview": "Buka Pengaturan untuk meninjau", + "profileSelection": "Pemilihan profil" }, "partRenderer": { "updatedFileCount_one": "{{displayCount}} berkas telah diperbarui.", @@ -3224,5 +3250,180 @@ "multiplierError": "Masukkan pengali 1 atau lebih", "permissionDenied": "Kamu tidak memiliki izin untuk mengelola peringatan pengeluaran.", "pushOffByCategory": "Nonaktif di Notifikasi" + }, + "profiles": { + "title": "Profil", + "entrySubtitle": "Gunakan kembali pengaturan lingkungan di seluruh sesi", + "newProfile": "Profil baru", + "emptyTitle": "Belum ada profil", + "emptyDescription": "Buat profil untuk menggunakan kembali variabel lingkungan, perintah pengaturan, dan keterampilan di seluruh sesi.", + "loadFailed": "Tidak dapat memuat profil", + "counts": { + "vars": "{{count}} variabel", + "mcp": "{{count}} MCP", + "skills": "{{count}} keterampilan", + "commands": "{{count}} perintah", + "varsShort": "{{count}}v", + "skillsShort": "{{count}}k", + "commandsShort": "{{count}}p" + }, + "nameLabel": "Nama profil", + "namePlaceholder": "misalnya Backend debugging", + "descriptionLabel": "Deskripsi profil", + "descriptionPlaceholder": "Deskripsi opsional", + "ownerLabel": "Pemilik profil", + "nameRequired": "Masukkan nama profil", + "createAction": "Buat profil", + "createFailed": "Tidak dapat membuat profil", + "createdToast": "Profil \"{{name}}\" dibuat", + "settingsSectionTitle": "Konfigurasi", + "defaultSectionTitle": "Profil bawaan", + "personalDefaultDescription": "Dimuat secara otomatis ketika tidak ada repositori yang memiliki profil yang disematkan.", + "organizationDefaultDescription": "Dimuat otomatis untuk anggota yang tidak memiliki default pribadi.", + "setAsDefault": "Tetapkan sebagai bawaan", + "removeDefault": "Hapus sebagai default", + "saveFailed": "Tidak dapat menyimpan profil", + "deleteTitle": "Hapus profil", + "deleteMessage": "Tindakan ini akan menghapus profil dan pengaturannya secara permanen. Hal ini tidak dapat dibatalkan.", + "deleteFailed": "Tidak dapat menghapus profil", + "deleteBlocked": "Profil ini digunakan oleh pemicu webhook. Hapus dulu dari pemicu tersebut.", + "deletedToast": "Profil \"{{name}}\" dihapus", + "variablesTitle": "Variabel lingkungan", + "addVariable": "Tambahkan variabel", + "keyLabel": "Kunci", + "keyTooLong": "Kunci harus 256 karakter atau kurang", + "keyDuplicate": "Kunci ini sudah ada di profil", + "valueLabel": "Nilai", + "secretLabel": "Rahasia", + "secrets": { + "markSecret": "Tandai sebagai rahasia", + "masked": "Nilai tersembunyi", + "reveal": "Mengungkapkan nilai", + "newValuePlaceholder": "Masukkan nilai rahasia baru", + "valuePlaceholder": "Nilai rahasia (terenkripsi)" + }, + "variablesEmpty": "Belum ada variabel", + "variablesSaveFailed": "Tidak dapat menyimpan variabel", + "commandsTitle": "Perintah pengaturan", + "addCommand": "Tambahkan perintah", + "commandLabel": "Memerintah", + "commandPlaceholder": "e.g. pnpm install", + "commandsEmpty": "Belum ada perintah pengaturan", + "commandsSaveFailed": "Tidak dapat menyimpan perintah penyiapan", + "moveUp": "Naik", + "moveDown": "Pindah ke bawah", + "skillsTitle": "Keterampilan", + "addSkill": "Tambahkan keterampilan", + "skillNameLabel": "Nama keterampilan", + "skillMarkdownLabel": "Konten keterampilan", + "skillsEmpty": "Belum ada keterampilan", + "skillNameRequired": "Masukkan nama keterampilan", + "skillNameInvalid": "Nama keterampilan harus dimulai dengan huruf atau angka dan hanya menggunakan huruf kecil, angka, dan tanda hubung", + "skillContentRequired": "Konten keterampilan diperlukan", + "skillSaveFailed": "Tidak dapat menyimpan keterampilan", + "skillsHint": "Tempelkan SKILL.md dan isikan namanya; tambahkan keterampilan dari ponsel Anda tanpa drag and drop.", + "skillEditTitle": "Mengedit keterampilan", + "mcp": { + "title": "server MCP", + "add": "Tambahkan server MCP", + "empty": "Belum ada server MCP", + "name": "Nama server", + "type": "Jenis server", + "localType": "Lokal", + "remoteType": "Terpencil", + "command": "Baris perintah", + "url": "URL", + "environment": "Lingkungan (JSON)", + "headers": "Header (JSON)", + "timeout": "Batas waktu", + "secretCount": "Hitungan rahasia", + "namePlaceholder": "my-server", + "commandPlaceholder": "npx @example/mcp-server", + "jsonPlaceholder": "{\n \"API_KEY\": \"sk-...\"\n}", + "nameRequired": "Masukkan nama server", + "nameInvalid": "Nama hanya boleh menggunakan huruf, angka, garis bawah, dan tanda hubung", + "commandRequired": "Masukkan perintah", + "commandTooLong": "Gunakan maksimal 50 argumen yang masing-masing 500 karakter", + "urlRequired": "Masukkan URL", + "urlInvalid": "Masukkan URL yang valid", + "urlTooLong": "URL harus 2048 karakter atau kurang", + "jsonInvalid": "Masukkan JSON yang valid — objek nilai string", + "jsonTooLarge": "Gunakan maksimal 50 entri, kunci hingga 128 dan nilai hingga 4096 karakter", + "timeoutInvalid": "Batas waktu harus berupa bilangan bulat dalam milidetik", + "edit": "Sunting server MCP", + "saveFailed": "Tidak dapat menyimpan server MCP" + }, + "agents": { + "title": "Agen profil", + "add": "Tambahkan agen", + "empty": "Belum ada agen", + "name": "Nama agen", + "slug": "Agen siput", + "description": "Deskripsi agen", + "model": "Penggantian model", + "modelPlaceholder": "Gunakan sesi default", + "prompt": "Perintah sistem", + "steps": "Maks. langkah", + "stepsPlaceholder": "mis. 50", + "temperature": "Suhu", + "temperaturePlaceholder": "mis. 0.2", + "topP": "top_p", + "topPPlaceholder": "mis. 0.95", + "slugPlaceholder": "my-agent", + "namePlaceholder": "Agen saya", + "promptPlaceholder": "Anda adalah ...", + "tools": "Peralatan", + "visibility": "Visibilitas", + "visibilityPrimary": "Utama — ditampilkan di alat pilih", + "visibilitySubagent": "Subagen — hanya dapat dihubungi dari orang lain", + "visibilityAll": "Semua—keduanya", + "saveFailed": "Tidak dapat menyelamatkan agen", + "nameRequired": "Masukkan nama agen", + "slugRequired": "Masukkan siput agen", + "slugInvalid": "Siput harus dimulai dengan huruf dan hanya menggunakan huruf kecil, angka, dan tanda hubung", + "slugConflict": "Siput itu dicadangkan oleh agen bawaan", + "edit": "Sunting agen" + }, + "slashCommands": { + "title": "Perintah garis miring", + "add": "Tambahkan perintah garis miring", + "empty": "Belum ada perintah garis miring", + "name": "Nama perintah", + "description": "Deskripsi perintah", + "template": "Templat", + "namePlaceholder": "explain", + "templatePlaceholder": "Jelaskan $ARGUMENTS", + "agent": "Agen", + "subtask": "Jalankan sebagai subtugas", + "edit": "Edit perintah garis miring", + "saveFailed": "Tidak dapat menyimpan perintah", + "nameRequired": "Masukkan nama perintah", + "nameInvalid": "Nama harus dimulai dengan huruf kecil dan hanya menggunakan huruf kecil, angka, dan tanda hubung", + "templateRequired": "Masukkan templat perintah", + "nameConflict": "Nama itu dicadangkan oleh perintah bawaan" + }, + "repoBindings": { + "title": "Profil Default untuk Repo", + "add": "Tambahkan bawaan", + "empty": "Tidak ada konfigurasi default", + "emptyDescription": "Tambahkan default untuk menerapkan profil secara otomatis saat memulai sesi untuk repo.", + "repo": "Pilih repositori…", + "profile": "Pilih profil", + "remove": "Memperlonggar", + "private": "Pribadi", + "search": "Cari repositori…", + "addedToast": "Profil default disetel untuk \"{{repo}}\"", + "addFailed": "Tidak dapat menyetel profil default", + "removedToast": "Profil default dihapus karena \"{{repo}}\"", + "removeFailed": "Tidak dapat menghapus profil default", + "pinnedTitle": "Disematkan ke repositori", + "pinRepo": "Sematkan repo", + "pinnedEmpty": "Tidak disematkan ke repositori mana pun.", + "pinnedDescription": "Dimuat secara otomatis setiap kali sesi menargetkan salah satu repositori ini.", + "pinnedToast": "Disematkan ke \"{{repo}}\"", + "pinFailed": "Tidak dapat menyematkan repo", + "unboundToast": "Repo tidak terikat", + "unbindFailed": "Tidak dapat melepaskan ikatan repo" + } } } diff --git a/apps/mobile/src/i18n/locales/ig.json b/apps/mobile/src/i18n/locales/ig.json index daafb98bb19..4bbafbc6c10 100644 --- a/apps/mobile/src/i18n/locales/ig.json +++ b/apps/mobile/src/i18n/locales/ig.json @@ -2191,7 +2191,33 @@ "continueFrom": "A na-aga n'ihu na {{title}}.", "importingSession": "Na-ebubata nnọkọ", "cliCannotContinue": "CLI a enweghị ike ịga n'ihu na nnọkọ. Họrọ Cloud Agent ma ọ bụ CLI ọzọ.", - "importFailed": "Enweghị ike ibubata nnọkọ a." + "importFailed": "Enweghị ike ibubata nnọkọ a.", + "pickProfile": "Họrọ profaịlụ", + "noProfile": "Enweghị profaịlụ", + "manageProfiles": "Jikwaa profaịlụ", + "noProfiles": "Enweghị profaịlụ. Mepụta otu iji gbakwunye mgbanwe gburugburu ebe obibi, sava MCP, na nka.", + "advancedConfig": "Nhazi dị elu", + "advancedConfigDescription": "Họrọ profaịlụ iji buo nhazi echekwara, wee jiri aka hazie mgbanwe gburugburu ebe obibi na iwu ntọala", + "saveAsProfile": "Chekwa dị ka profaịlụ", + "saveProfileDescription": "Chekwaa mgbanwe gburugburu ebe obibi ugbu a yana iwu ntọlite dị ka profaịlụ enwere ike ijikwa ya.", + "profileSummary": "{{vars}} mgbanwe gburugburu · {{commands}} iwu nhazi", + "profileSaved": "A na-echekwa profaịlụ \"{{name}}\".", + "organizationProfiles": "Profaịlụ nhazi", + "personalProfiles": "Profaịlụ nkeonwe", + "yourProfiles": "Profaịlụ gị", + "failedToLoadProfiles": "Ịbunye profaịlụ agaghị eme", + "nameTooLong": "Aha profaịlụ dị ogologo", + "descriptionTooLong": "Nkọwa dị ogologo", + "secretsEncrypted": "A na-ezobe ihe nzuzo tupu nchekwa.", + "profileActive": "Profaịlụ na-arụ ọrụ", + "profilesActive": "Profaịlụ na-arụ ọrụ", + "customConfigActive": "Nhazi omenala na-arụ ọrụ", + "configNeedsAttention": "Config chọrọ nlebara anya", + "repoProfile": "Profaịlụ repo", + "selectedProfile": "profaịlụ ahọpụtara", + "manualOverrides": "Ntugharị akwụkwọ ntuziaka", + "openSettingsToReview": "Mepee Ntọala ka ịlele", + "profileSelection": "Nhọrọ profaịlụ" }, "partRenderer": { "updatedFileCount_one": "Emelitere faịlụ {{displayCount}}.", @@ -3224,5 +3250,180 @@ "multiplierError": "Tinye ọnụọgụ mmụba nke 1 ma ọ bụ karịa", "permissionDenied": "Ị nweghị ikike ijikwa ọkwa gbasara mmefu.", "pushOffByCategory": "Gbanyụrụ n'ime Ọkwa" + }, + "profiles": { + "title": "Profaịlụ", + "entrySubtitle": "Jigharịa ntọala gburugburu ebe niile n'ofe oge", + "newProfile": "Profaịlụ ọhụrụ", + "emptyTitle": "Enweghị profaịlụ", + "emptyDescription": "Mepụta profaịlụ iji megharịa mgbanwe gburugburu ebe obibi, iwu ntọlite na nka n'ofe oge.", + "loadFailed": "Enweghị ike ibunye profaịlụ", + "counts": { + "vars": "{{count}} mgbanwe", + "mcp": "{{count}} MCP", + "skills": "{{count}} nkà", + "commands": "{{count}} iwu", + "varsShort": "{{count}}m", + "skillsShort": "{{count}}n", + "commandsShort": "{{count}}i" + }, + "nameLabel": "Aha profaịlụ", + "namePlaceholder": "eg. Ndozi azụ azụ", + "descriptionLabel": "Nkọwa profaịlụ", + "descriptionPlaceholder": "Nkọwa nhọrọ", + "ownerLabel": "Onye nwe profaịlụ", + "nameRequired": "Tinye aha profaịlụ", + "createAction": "Mepụta profaịlụ", + "createFailed": "Enweghị ike ịmepụta profaịlụ", + "createdToast": "Mepụtara profaịlụ \"{{name}}\".", + "settingsSectionTitle": "Nhazi", + "defaultSectionTitle": "Profaịlụ ezighi ezi", + "personalDefaultDescription": "Ebuuru akpaaka mgbe enweghị ebe nchekwa nwere profaịlụ etinyere.", + "organizationDefaultDescription": "Ebudoro onwe ya maka ndị otu na-enweghị ndabara nkeonwe.", + "setAsDefault": "Tọọ ka ndabara", + "removeDefault": "Wepu dị ka ndabara", + "saveFailed": "Enweghị ike ịchekwa profaịlụ", + "deleteTitle": "Hichapụ profaịlụ", + "deleteMessage": "Nke a na-ewepụ profaịlụ na ntọala ya kpamkpam. Enweghị ike imegharị nke a.", + "deleteFailed": "Enweghị ike ihichapụ profaịlụ", + "deleteBlocked": "A na-eji profaịlụ webhook mkpalite. Buru ụzọ wepụ ya na ihe ndị ahụ na-akpalite ya.", + "deletedToast": "Profaịlụ \"{{name}}\" ehichapụrụ", + "variablesTitle": "Ụdị mgbanwe gburugburu", + "addVariable": "Tinye mgbanwe", + "keyLabel": "Igodo", + "keyTooLong": "Igodo ga-abụ mkpụrụedemede 256 ma ọ bụ obere", + "keyDuplicate": "Igodo a dịworị na profaịlụ", + "valueLabel": "Uru", + "secretLabel": "Nzuzo", + "secrets": { + "markSecret": "Kaa akara dị ka nzuzo", + "masked": "Uru zoro ezo", + "reveal": "Gosi uru", + "newValuePlaceholder": "Tinye uru nzuzo ọhụrụ", + "valuePlaceholder": "Uru nzuzo (ezoro ezo)" + }, + "variablesEmpty": "Enweghị mgbanwe ma", + "variablesSaveFailed": "Enweghị ike ichekwa mgbanwe", + "commandsTitle": "Nhazi iwu", + "addCommand": "Tinye iwu", + "commandLabel": "Iwu", + "commandPlaceholder": "e.g. pnpm install", + "commandsEmpty": "Enweghị iwu ntọlite ka", + "commandsSaveFailed": "Enweghị ike ichekwa iwu ntọala", + "moveUp": "Bulie elu", + "moveDown": "Budata ala", + "skillsTitle": "Nkà", + "addSkill": "Tinye nka", + "skillNameLabel": "Aha nka", + "skillMarkdownLabel": "Ọdịnaya nka", + "skillsEmpty": "Enweghị nka ma", + "skillNameRequired": "Tinye aha nka", + "skillNameInvalid": "Aha nka ga-ebidorịrị site na mkpụrụedemede ma ọ bụ mkpụrụọnụọgụgụ ma jiri naanị obere mkpụrụedemede, mkpụrụọnụọgụgụ na ngwucha", + "skillContentRequired": "Achọrọ ọdịnaya nka", + "skillSaveFailed": "Enweghị ike ichekwa nka", + "skillsHint": "Tapawa SKILL.md na aha ya juputara; tinye nka na ekwentị gị na-adọkpụrụghị na dobe.", + "skillEditTitle": "Dezie nka", + "mcp": { + "title": "Sava MCP", + "add": "Tinye ihe nkesa MCP", + "empty": "Enweghị sava MCP ma", + "name": "Aha sava", + "type": "Ụdị nkesa", + "localType": "Mpaghara", + "remoteType": "Ime ime", + "command": "Ahịrị iwu", + "url": "URL", + "environment": "Gburugburu ebe (JSON)", + "headers": "Ndị isi (JSON)", + "timeout": "Nkeji oge", + "secretCount": "Ọnụ ọgụgụ nzuzo", + "namePlaceholder": "my-server", + "commandPlaceholder": "npx @example/mcp-server", + "jsonPlaceholder": "{\n \"API_KEY\": \"sk-...\"\n}", + "nameRequired": "Tinye aha nkesa", + "nameInvalid": "Aha nwere ike iji naanị mkpụrụedemede, ọnụọgụ, akara akara, na dashes", + "commandRequired": "Tinye iwu", + "commandTooLong": "Ejila ihe karịrị arụmụka 50, nke ọ bụla mkpụrụedemede 500", + "urlRequired": "Tinye URL", + "urlInvalid": "Tinye URL ziri ezi", + "urlTooLong": "URL ga-abụ mkpụrụedemede 2048 ma ọ bụ obere", + "jsonInvalid": "Tinye JSON bara uru - ihe nwere ụkpụrụ eriri", + "jsonTooLarge": "Ejila ihe karịrị ndenye 50, igodo ruo 128 na uru ruo mkpụrụedemede 4096", + "timeoutInvalid": "Oge ngwụcha ga-abụrịrị ọnụ ọgụgụ millisekọnd dum", + "edit": "Dezie ihe nkesa MCP", + "saveFailed": "Enweghị ike ichekwa ihe nkesa MCP" + }, + "agents": { + "title": "Ndị ọrụ profaịlụ", + "add": "Tinye onye nnọchi anya", + "empty": "Enweghị ndị nnọchi anya", + "name": "Aha onye nnọchi anya", + "slug": "Slug onye nnọchi anya", + "description": "Nkọwa onye nnọchi anya", + "model": "Mbufe ihe nlereanya", + "modelPlaceholder": "Jiri ndabara oge", + "prompt": "Ngwa ngwa sistemụ", + "steps": "Kachasị nzọụkwụ", + "stepsPlaceholder": "dịka 50", + "temperature": "Okpomọkụ", + "temperaturePlaceholder": "dịka 0.2", + "topP": "top_p", + "topPPlaceholder": "dịka 0.95", + "slugPlaceholder": "my-agent", + "namePlaceholder": "Onye nnọchi anya m", + "promptPlaceholder": "Ị bụ onye...", + "tools": "Ngwa", + "visibility": "Nhụta", + "visibilityPrimary": "Primary — gosiri na onye na-ahọrọ", + "visibilitySubagent": "Subagent - naanị ndị ọzọ nwere ike ịkpọ", + "visibilityAll": "All - abụọ", + "saveFailed": "Enweghị ike ichekwa onye nnọchi anya", + "nameRequired": "Tinye aha onye nnọchi anya", + "slugRequired": "Tinye ihe nnọchite slug", + "slugInvalid": "Slug ga-eji mkpụrụedemede malite ma jiri naanị obere mkpụrụedemede, mkpụrụọnụọgụgụ, na ngwucha", + "slugConflict": "Edobere slug ahụ site n'aka onye arụrụ n'ime ya", + "edit": "Onye ọrụ dezie" + }, + "slashCommands": { + "title": "Iwu slash", + "add": "Tinye iwu slash", + "empty": "Enweghị iwu slash ma", + "name": "Aha iwu", + "description": "Nkọwa iwu", + "template": "Ụdị", + "namePlaceholder": "explain", + "templatePlaceholder": "Kọwaa $ARGUMENTS", + "agent": "Onye nnọchi anya", + "subtask": "Gbaa ọsọ dị ka obere ọrụ", + "edit": "Dezie slash iwu", + "saveFailed": "Enweghị ike ịchekwa iwu", + "nameRequired": "Tinye aha iwu", + "nameInvalid": "Aha ga-eji obere mkpụrụedemede malite ma jiri naanị obere mkpụrụedemede, mkpụrụọnụọgụgụ, na akara akara", + "templateRequired": "Tinye ndebiri iwu", + "nameConflict": "Edebere aha ahụ site na iwu arụnyere n'ime ya" + }, + "repoBindings": { + "title": "Profaịlụ ezighi ezi maka nkwụghachi", + "add": "Tinye ndabara", + "empty": "Ọ nweghị ndabara ahaziri", + "emptyDescription": "Tinye ndabara ka itinye profaịlụ na-akpaghị aka mgbe ịmalite nnọkọ maka repo.", + "repo": "Họrọ ebe nchekwa…", + "profile": "Họrọ profaịlụ", + "remove": "Kwụpụ", + "private": "Nkeonwe", + "search": "Chọọ ebe nchekwa…", + "addedToast": "Profaịlụ ahaziri maka \"{{repo}}\"", + "addFailed": "Enweghị ike ịtọ profaịlụ ndabere", + "removedToast": "Ewepula profaịlụ izizi maka \"{{repo}}\"", + "removeFailed": "Enweghị ike iwepụ profaịlụ nke ndabara", + "pinnedTitle": "Ejidere ya na ebe nchekwa", + "pinRepo": "Tinye repo", + "pinnedEmpty": "Edenyeghị ya na ebe nchekwa ọ bụla.", + "pinnedDescription": "A na-ebunye onwe ya mgbe ọ bụla nnọkọ na-eche otu n'ime ebe nchekwa ndị a.", + "pinnedToast": "Akọnyere na \"{{repo}}\"", + "pinFailed": "Enweghị ike itinye repo", + "unboundToast": "Repo ewepụghị", + "unbindFailed": "Enweghị ike iwepụ repo" + } } } diff --git a/apps/mobile/src/i18n/locales/is.json b/apps/mobile/src/i18n/locales/is.json index ef8a06dd94d..ca4fdf42a39 100644 --- a/apps/mobile/src/i18n/locales/is.json +++ b/apps/mobile/src/i18n/locales/is.json @@ -2191,7 +2191,33 @@ "continueFrom": "Haldið er áfram með {{title}}.", "importingSession": "Flytur lotuna inn", "cliCannotContinue": "Þetta CLI getur ekki haldið áfram með lotu. Veldu Cloud Agent eða annað CLI.", - "importFailed": "Ekki tókst að flytja inn þessa lotu." + "importFailed": "Ekki tókst að flytja inn þessa lotu.", + "pickProfile": "Veldu prófíl", + "noProfile": "Enginn prófíll", + "manageProfiles": "Stjórna prófílum", + "noProfiles": "Engir prófílar ennþá. Búðu til einn til að bæta við umhverfisbreytum, MCP netþjónum og færni.", + "advancedConfig": "Ítarleg stilling", + "advancedConfigDescription": "Veldu snið til að hlaða vistuðum stillingum og stilltu handvirkt viðbótarumhverfisbreytur og uppsetningarskipanir", + "saveAsProfile": "Vista sem prófíl", + "saveProfileDescription": "Vistaðu núverandi umhverfisbreytur og uppsetningarskipanir sem endurnýtanlegt snið.", + "profileSummary": "{{vars}} umhverfisbreytur · {{commands}} uppsetningarskipanir", + "profileSaved": "Prófíllinn „{{name}}“ vistaður", + "organizationProfiles": "Stofnunarsnið", + "personalProfiles": "Persónuleg snið", + "yourProfiles": "Prófílarnir þínir", + "failedToLoadProfiles": "Mistókst að hlaða sniðum", + "nameTooLong": "Nafn prófílsins er of langt", + "descriptionTooLong": "Lýsing er of löng", + "secretsEncrypted": "Leyndarmál eru dulkóðuð fyrir geymslu.", + "profileActive": "Prófíll virkur", + "profilesActive": "Snið virk", + "customConfigActive": "Sérsniðin stilling virk", + "configNeedsAttention": "Config þarfnast athygli", + "repoProfile": "Prófíll geymslu", + "selectedProfile": "Valið snið", + "manualOverrides": "Handvirkar yfirfærslur", + "openSettingsToReview": "Opnaðu Stillingar til að skoða", + "profileSelection": "Prófílval" }, "partRenderer": { "updatedFileCount_one": "{{displayCount}} skrá var uppfærð.", @@ -3224,5 +3250,180 @@ "multiplierError": "Sláðu inn margfaldara sem er 1 eða meira", "permissionDenied": "Þú hefur ekki heimild til að stjórna útgjaldaviðvörunum.", "pushOffByCategory": "Af í Tilkynningum" + }, + "profiles": { + "title": "Snið", + "entrySubtitle": "Endurnotaðu umhverfisstillingar yfir lotur", + "newProfile": "Nýtt snið", + "emptyTitle": "Engir prófílar ennþá", + "emptyDescription": "Búðu til prófíl til að endurnýta umhverfisbreytur, uppsetningarskipanir og færni yfir lotur.", + "loadFailed": "Ekki tókst að hlaða prófílum", + "counts": { + "vars": "{{count}} breytur", + "mcp": "{{count}} MCP", + "skills": "{{count}} færni", + "commands": "{{count}} skipanir", + "varsShort": "{{count}}b", + "skillsShort": "{{count}}f", + "commandsShort": "{{count}}s" + }, + "nameLabel": "Nafn prófíls", + "namePlaceholder": "t.d. Bakenda villuleit", + "descriptionLabel": "Prófíllýsing", + "descriptionPlaceholder": "Valfrjáls lýsing", + "ownerLabel": "Eigandi prófílsins", + "nameRequired": "Sláðu inn prófílnafn", + "createAction": "Búðu til prófíl", + "createFailed": "Ekki tókst að búa til prófíl", + "createdToast": "Prófíllinn „{{name}}“ búinn til", + "settingsSectionTitle": "Stillingar", + "defaultSectionTitle": "Sjálfgefið snið", + "personalDefaultDescription": "Sjálfvirkt hlaðið þegar engin geymsla er með festan prófíl.", + "organizationDefaultDescription": "Sjálfvirkt hlaðið fyrir meðlimi sem hafa engin persónuleg vanskil.", + "setAsDefault": "Stillt sem sjálfgefið", + "removeDefault": "Fjarlægja sem sjálfgefið", + "saveFailed": "Ekki tókst að vista prófílinn", + "deleteTitle": "Eyða prófíl", + "deleteMessage": "Þetta fjarlægir prófílinn og stillingar hans varanlega. Þetta er ekki hægt að afturkalla.", + "deleteFailed": "Ekki tókst að eyða prófílnum", + "deleteBlocked": "Þetta snið er notað af webhook kveikju. Fjarlægðu það fyrst úr þessum kveikjum.", + "deletedToast": "Prófílnum „{{name}}“ eytt", + "variablesTitle": "Umhverfisbreytur", + "addVariable": "Bæta við breytu", + "keyLabel": "Lykill", + "keyTooLong": "Lykillinn má vera að hámarki 256 stafir", + "keyDuplicate": "Þessi lykill er þegar til í prófílnum", + "valueLabel": "Gildi", + "secretLabel": "Leyndarmál", + "secrets": { + "markSecret": "Merktu sem leyndarmál", + "masked": "Falið gildi", + "reveal": "Sýndu gildi", + "newValuePlaceholder": "Sláðu inn nýtt leynilegt gildi", + "valuePlaceholder": "Leynilegt gildi (dulkóðað)" + }, + "variablesEmpty": "Engar breytur ennþá", + "variablesSaveFailed": "Ekki tókst að vista breytu", + "commandsTitle": "Uppsetningarskipanir", + "addCommand": "Bæta við skipun", + "commandLabel": "Skipun", + "commandPlaceholder": "e.g. pnpm install", + "commandsEmpty": "Engar uppsetningarskipanir ennþá", + "commandsSaveFailed": "Gat ekki vistað uppsetningarskipanir", + "moveUp": "Færðu þig upp", + "moveDown": "Færðu þig niður", + "skillsTitle": "Færni", + "addSkill": "Bættu við færni", + "skillNameLabel": "Nafn kunnáttu", + "skillMarkdownLabel": "Færniefni", + "skillsEmpty": "Engin kunnátta ennþá", + "skillNameRequired": "Sláðu inn hæfileikaheiti", + "skillNameInvalid": "Hæfniheiti verður að byrja á bókstaf eða tölustaf og nota aðeins lágstafi, tölustafi og bandstrik", + "skillContentRequired": "Færniefni er krafist", + "skillSaveFailed": "Ekki tókst að bjarga kunnáttu", + "skillsHint": "Límdu SKILL.md og nafn þess fyllir út; bættu við færni úr símanum þínum án þess að draga og sleppa.", + "skillEditTitle": "Breyta færni", + "mcp": { + "title": "MCP netþjóna", + "add": "Bættu við MCP netþjóni", + "empty": "Engir MCP netþjónar ennþá", + "name": "Nafn netþjóns", + "type": "Tegund netþjóns", + "localType": "Staðbundið", + "remoteType": "Fjarstýring", + "command": "Skipanalína", + "url": "URL", + "environment": "Umhverfi (JSON)", + "headers": "Hausar (JSON)", + "timeout": "Tímamörk", + "secretCount": "Leynitalning", + "namePlaceholder": "my-server", + "commandPlaceholder": "npx @example/mcp-server", + "jsonPlaceholder": "{\n \"API_KEY\": \"sk-...\"\n}", + "nameRequired": "Sláðu inn nafn netþjóns", + "nameInvalid": "Nafn getur aðeins notað bókstafi, tölustafi, undirstrik og strik", + "commandRequired": "Sláðu inn skipun", + "commandTooLong": "Notaðu að hámarki 50 rök, hvert 500 stafir", + "urlRequired": "Sláðu inn vefslóð", + "urlInvalid": "Sláðu inn gilda vefslóð", + "urlTooLong": "URL má vera að hámarki 2048 stafir", + "jsonInvalid": "Sláðu inn gilt JSON - hlut með strengjagildum", + "jsonTooLarge": "Notaðu að hámarki 50 færslur, lyklar upp að 128 og gildi upp að 4096 stafir", + "timeoutInvalid": "Tímamörk verða að vera heil tala af millisekúndum", + "edit": "Breyta MCP miðlara", + "saveFailed": "Gat ekki vistað MCP þjón" + }, + "agents": { + "title": "Umboðsmenn prófíla", + "add": "Bæta við umboðsmanni", + "empty": "Engir umboðsmenn ennþá", + "name": "Nafn umboðsmanns", + "slug": "Umboðssnillingur", + "description": "Lýsing umboðsmanns", + "model": "Hnekkt líkan", + "modelPlaceholder": "Notaðu sjálfgefið lotu", + "prompt": "Kerfisboð", + "steps": "Hámarks skref", + "stepsPlaceholder": "t.d. 50", + "temperature": "Hitastig", + "temperaturePlaceholder": "t.d. 0.2", + "topP": "top_p", + "topPPlaceholder": "t.d. 0.95", + "slugPlaceholder": "my-agent", + "namePlaceholder": "Umboðsmaður minn", + "promptPlaceholder": "Þú ert a...", + "tools": "Verkfæri", + "visibility": "Skyggni", + "visibilityPrimary": "Aðal — sýnt í veljarann", + "visibilitySubagent": "Subagent — aðeins hægt að hringja frá öðrum", + "visibilityAll": "Allt - bæði", + "saveFailed": "Ekki tókst að vista umboðsmann", + "nameRequired": "Sláðu inn nafn umboðsmanns", + "slugRequired": "Sláðu inn umboðssnigl", + "slugInvalid": "Snigl verður að byrja á bókstaf og nota aðeins lágstafi, tölustafi og strik", + "slugConflict": "Sá snigill er frátekinn af innbyggðum umboðsmanni", + "edit": "Breyta umboðsmanni" + }, + "slashCommands": { + "title": "Slash skipanir", + "add": "Bæta við skástrik skipun", + "empty": "Engar skástrik skipanir ennþá", + "name": "Nafn skipunar", + "description": "Skipunarlýsing", + "template": "Sniðmát", + "namePlaceholder": "explain", + "templatePlaceholder": "Útskýrðu $ARGUMENTS", + "agent": "Umboðsmaður", + "subtask": "Keyra sem undirverkefni", + "edit": "Breyta skástrik skipun", + "saveFailed": "Ekki tókst að vista skipunina", + "nameRequired": "Sláðu inn heiti skipunar", + "nameInvalid": "Nafn verður að byrja á lágstöfum og nota aðeins lágstafi, tölustafi og bandstrik", + "templateRequired": "Sláðu inn skipanasniðmát", + "nameConflict": "Það nafn er frátekið af innbyggðri skipun" + }, + "repoBindings": { + "title": "Sjálfgefin snið fyrir geymslur", + "add": "Bæta við sjálfgefnu", + "empty": "Engar sjálfgefnar stillingar", + "emptyDescription": "Bættu við sjálfgefnu til að nota prófíl sjálfkrafa þegar þú byrjar lotur fyrir geymslu.", + "repo": "Veldu geymslu…", + "profile": "Veldu prófíl", + "remove": "Afbinda", + "private": "Einkamál", + "search": "Leita í geymslum...", + "addedToast": "Sjálfgefið snið stillt fyrir „{{repo}}“", + "addFailed": "Gat ekki stillt sjálfgefna prófíl", + "removedToast": "Sjálfgefið snið fjarlægt fyrir „{{repo}}“", + "removeFailed": "Ekki tókst að fjarlægja sjálfgefna prófílinn", + "pinnedTitle": "Festur við geymslur", + "pinRepo": "Festu geymslu", + "pinnedEmpty": "Ekki fest við neinar geymslur.", + "pinnedDescription": "Sjálfvirkt hlaðið þegar lota miðar á eina af þessum geymslum.", + "pinnedToast": "Fest við „{{repo}}“", + "pinFailed": "Ekki tókst að festa geymslu", + "unboundToast": "Geymsla óbundin", + "unbindFailed": "Ekki tókst að aftengja geymslu" + } } } diff --git a/apps/mobile/src/i18n/locales/it.json b/apps/mobile/src/i18n/locales/it.json index 3699b52bda4..77ca52d264a 100644 --- a/apps/mobile/src/i18n/locales/it.json +++ b/apps/mobile/src/i18n/locales/it.json @@ -1668,7 +1668,33 @@ "continueFrom": "Stai continuando la sessione {{title}}.", "importingSession": "Importazione in corso…", "cliCannotContinue": "Questa CLI non può continuare una sessione. Scegli Cloud Agent o un'altra CLI.", - "importFailed": "Impossibile importare questa sessione." + "importFailed": "Impossibile importare questa sessione.", + "pickProfile": "Scegli un profilo", + "noProfile": "Nessun profilo", + "manageProfiles": "Gestisci i profili", + "noProfiles": "Nessun profilo ancora. Creane uno per aggiungere variabili di ambiente, server MCP e competenze.", + "advancedConfig": "Configurazione avanzata", + "advancedConfigDescription": "Seleziona un profilo per caricare le configurazioni salvate e configurare manualmente variabili di ambiente aggiuntive e comandi di configurazione", + "saveAsProfile": "Salva come profilo", + "saveProfileDescription": "Salva le variabili di ambiente correnti e i comandi di configurazione come profilo riutilizzabile.", + "profileSummary": "{{vars}} variabili di ambiente · {{commands}} comandi di configurazione", + "profileSaved": "Profilo \"{{name}}\" salvato", + "organizationProfiles": "Profili dell'organizzazione", + "personalProfiles": "Profili personali", + "yourProfiles": "I tuoi profili", + "failedToLoadProfiles": "Impossibile caricare i profili", + "nameTooLong": "Il nome del profilo è troppo lungo", + "descriptionTooLong": "La descrizione è troppo lunga", + "secretsEncrypted": "I segreti vengono crittografati prima dell'archiviazione.", + "profileActive": "Profilo attivo", + "profilesActive": "Profili attivi", + "customConfigActive": "Configurazione personalizzata attiva", + "configNeedsAttention": "La configurazione richiede attenzione", + "repoProfile": "Profilo del repository", + "selectedProfile": "Profilo selezionato", + "manualOverrides": "Sostituzioni manuali", + "openSettingsToReview": "Apri Impostazioni per rivedere", + "profileSelection": "Selezione del profilo" }, "partRenderer": { "updatedFileCount_one": "È stato aggiornato {{displayCount}} file.", @@ -3267,5 +3293,180 @@ "multiplierError": "Inserisci un moltiplicatore di 1 o più", "permissionDenied": "Non hai l'autorizzazione per gestire gli avvisi di spesa.", "pushOffByCategory": "Disattivato in Notifiche" + }, + "profiles": { + "title": "Profili", + "entrySubtitle": "Riutilizza le impostazioni dell'ambiente tra le sessioni", + "newProfile": "Nuovo profilo", + "emptyTitle": "Nessun profilo ancora", + "emptyDescription": "Crea un profilo per riutilizzare variabili di ambiente, comandi di configurazione e competenze tra le sessioni.", + "loadFailed": "Impossibile caricare i profili", + "counts": { + "vars": "{{count}} variabili", + "mcp": "{{count}} MCP", + "skills": "{{count}} competenze", + "commands": "{{count}} comandi", + "varsShort": "{{count}}v", + "skillsShort": "{{count}}comp", + "commandsShort": "{{count}}cmd" + }, + "nameLabel": "Nome del profilo", + "namePlaceholder": "per esempio. Debug del backend", + "descriptionLabel": "Descrizione del profilo", + "descriptionPlaceholder": "Descrizione facoltativa", + "ownerLabel": "Proprietario del profilo", + "nameRequired": "Inserisci un nome per il profilo", + "createAction": "Crea profilo", + "createFailed": "Impossibile creare il profilo", + "createdToast": "Profilo \"{{name}}\" creato", + "settingsSectionTitle": "Configurazione", + "defaultSectionTitle": "Profilo predefinito", + "personalDefaultDescription": "Caricato automaticamente quando nessun repository ha un profilo bloccato.", + "organizationDefaultDescription": "Caricamento automatico per i membri che non hanno impostazioni predefinite personali.", + "setAsDefault": "Imposta come predefinito", + "removeDefault": "Rimuovi come predefinito", + "saveFailed": "Impossibile salvare il profilo", + "deleteTitle": "Elimina profilo", + "deleteMessage": "Ciò rimuove permanentemente il profilo e le sue impostazioni. Questa operazione non può essere annullata.", + "deleteFailed": "Impossibile eliminare il profilo", + "deleteBlocked": "Questo profilo viene utilizzato da un trigger webhook. Rimuovilo prima da quei trigger.", + "deletedToast": "Profilo \"{{name}}\" eliminato", + "variablesTitle": "Variabili d'ambiente", + "addVariable": "Aggiungi variabile", + "keyLabel": "Chiave", + "keyTooLong": "La chiave deve contenere 256 caratteri o meno", + "keyDuplicate": "Questa chiave esiste già nel profilo", + "valueLabel": "Valore", + "secretLabel": "Segreto", + "secrets": { + "markSecret": "Contrassegna come segreto", + "masked": "Valore nascosto", + "reveal": "Rivela valore", + "newValuePlaceholder": "Inserisci un nuovo valore segreto", + "valuePlaceholder": "Valore segreto (cifrato)" + }, + "variablesEmpty": "Nessuna variabile ancora", + "variablesSaveFailed": "Impossibile salvare la variabile", + "commandsTitle": "Comandi di impostazione", + "addCommand": "Aggiungi comando", + "commandLabel": "Comando", + "commandPlaceholder": "e.g. pnpm install", + "commandsEmpty": "Nessun comando di configurazione ancora", + "commandsSaveFailed": "Impossibile salvare i comandi di configurazione", + "moveUp": "Vai su", + "moveDown": "Spostati giù", + "skillsTitle": "Competenze", + "addSkill": "Aggiungi abilità", + "skillNameLabel": "Nome dell'abilità", + "skillMarkdownLabel": "Contenuto delle competenze", + "skillsEmpty": "Nessuna competenza ancora", + "skillNameRequired": "Inserisci un nome per la competenza", + "skillNameInvalid": "Il nome della competenza deve iniziare con una lettera o un numero e utilizzare solo lettere minuscole, numeri e trattini", + "skillContentRequired": "È richiesto il contenuto delle competenze", + "skillSaveFailed": "Impossibile salvare l'abilità", + "skillsHint": "Incolla un SKILL.md e il suo nome si riempie; aggiungi un'abilità dal tuo telefono senza trascinare e rilasciare.", + "skillEditTitle": "Modifica abilità", + "mcp": { + "title": "server MCP", + "add": "Aggiungi il server MCP", + "empty": "Nessun server MCP ancora", + "name": "Nome del server", + "type": "Tipo di server", + "localType": "Locale", + "remoteType": "Remoto", + "command": "Riga di comando", + "url": "URL", + "environment": "Ambiente (JSON)", + "headers": "Intestazioni (JSON)", + "timeout": "Tempo scaduto", + "secretCount": "Conteggio segreto", + "namePlaceholder": "my-server", + "commandPlaceholder": "npx @example/mcp-server", + "jsonPlaceholder": "{\n \"API_KEY\": \"sk-...\"\n}", + "nameRequired": "Immettere un nome server", + "nameInvalid": "Il nome può contenere solo lettere, numeri, trattini bassi e trattini", + "commandRequired": "Inserisci un comando", + "commandTooLong": "Usa al massimo 50 argomenti da 500 caratteri ciascuno", + "urlRequired": "Inserisci un URL", + "urlInvalid": "Inserisci un URL valido", + "urlTooLong": "L'URL può contenere al massimo 2048 caratteri", + "jsonInvalid": "Inserisci un JSON valido: un oggetto di valori stringa", + "jsonTooLarge": "Usa al massimo 50 voci, chiavi fino a 128 e valori fino a 4096 caratteri", + "timeoutInvalid": "Il timeout deve essere un numero intero di millisecondi", + "edit": "Modifica il server MCP", + "saveFailed": "Impossibile salvare il server MCP" + }, + "agents": { + "title": "Agenti del profilo", + "add": "Aggiungi agente", + "empty": "Nessun agente ancora", + "name": "Nome dell'agente", + "slug": "Lumaca dell'agente", + "description": "Descrizione dell'agente", + "model": "Sostituzione del modello", + "modelPlaceholder": "Utilizza l'impostazione predefinita della sessione", + "prompt": "Richiesta di sistema", + "steps": "Passi massimi", + "stepsPlaceholder": "es. 50", + "temperature": "Temperatura", + "temperaturePlaceholder": "es. 0.2", + "topP": "top_p", + "topPPlaceholder": "es. 0.95", + "slugPlaceholder": "my-agent", + "namePlaceholder": "Il mio agente", + "promptPlaceholder": "Tu sei un...", + "tools": "Utensili", + "visibility": "Visibilità", + "visibilityPrimary": "Principale: mostrato nel selettore", + "visibilitySubagent": "Subagente: richiamabile solo da altri", + "visibilityAll": "Tutti, entrambi", + "saveFailed": "Impossibile salvare l'agente", + "nameRequired": "Inserisci il nome di un agente", + "slugRequired": "Inserisci uno slug dell'agente", + "slugInvalid": "Lo slug deve iniziare con una lettera e utilizzare solo lettere minuscole, cifre e trattini", + "slugConflict": "Questo slug è riservato da un agente integrato", + "edit": "Modifica agente" + }, + "slashCommands": { + "title": "Comandi barra", + "add": "Aggiungi il comando barra", + "empty": "Nessun comando slash ancora", + "name": "Nome del comando", + "description": "Descrizione del comando", + "template": "Modello", + "namePlaceholder": "explain", + "templatePlaceholder": "Spiega $ARGUMENTS", + "agent": "Agente", + "subtask": "Esegui come attività secondaria", + "edit": "Comando Modifica barra", + "saveFailed": "Impossibile salvare il comando", + "nameRequired": "Immettere un nome di comando", + "nameInvalid": "Il nome deve iniziare con una lettera minuscola e utilizzare solo lettere minuscole, numeri e trattini", + "templateRequired": "Immettere un modello di comando", + "nameConflict": "Quel nome è riservato da un comando integrato" + }, + "repoBindings": { + "title": "Profili predefiniti per repository", + "add": "Aggiungi predefinito", + "empty": "Nessuna impostazione predefinita configurata", + "emptyDescription": "Aggiungi un valore predefinito per applicare automaticamente un profilo all'avvio delle sessioni per un repository.", + "repo": "Seleziona archivio...", + "profile": "Seleziona profilo", + "remove": "Slegare", + "private": "Privato", + "search": "Cerca repository...", + "addedToast": "Profilo predefinito impostato per \"{{repo}}\"", + "addFailed": "Impossibile impostare il profilo predefinito", + "removedToast": "Profilo predefinito rimosso per \"{{repo}}\"", + "removeFailed": "Impossibile rimuovere il profilo predefinito", + "pinnedTitle": "Bloccato nei repository", + "pinRepo": "Aggiungi un repository", + "pinnedEmpty": "Non bloccato in alcun repository.", + "pinnedDescription": "Caricato automaticamente ogni volta che una sessione è destinata a uno di questi repository.", + "pinnedToast": "Fissato a \"{{repo}}\"", + "pinFailed": "Impossibile bloccare il repository", + "unboundToast": "Repo non vincolato", + "unbindFailed": "Impossibile scollegare il repository" + } } } diff --git a/apps/mobile/src/i18n/locales/ja.json b/apps/mobile/src/i18n/locales/ja.json index d3ee0fe8599..15accee026c 100644 --- a/apps/mobile/src/i18n/locales/ja.json +++ b/apps/mobile/src/i18n/locales/ja.json @@ -1687,7 +1687,33 @@ "continueFrom": "{{title}}を続行しています。", "importingSession": "セッションをインポート中", "cliCannotContinue": "このCLIではセッションを続行できません。Cloud Agentまたは別のCLIを選択してください。", - "importFailed": "このセッションをインポートできませんでした。" + "importFailed": "このセッションをインポートできませんでした。", + "pickProfile": "プロフィールを選択してください", + "noProfile": "プロフィールなし", + "manageProfiles": "プロファイルの管理", + "noProfiles": "まだプロフィールはありません。環境変数、MCP サーバー、スキルを追加するために作成します。", + "advancedConfig": "高度な構成", + "advancedConfigDescription": "プロファイルを選択して保存された構成をロードし、追加の環境変数とセットアップ コマンドを手動で構成します", + "saveAsProfile": "プロフィールとして保存", + "saveProfileDescription": "現在の環境変数とセットアップ コマンドを再利用可能なプロファイルとして保存します。", + "profileSummary": "{{vars}} 環境変数 · {{commands}} セットアップ コマンド", + "profileSaved": "プロファイル「{{name}}」が保存されました", + "organizationProfiles": "組織プロフィール", + "personalProfiles": "個人プロフィール", + "yourProfiles": "あなたのプロフィール", + "failedToLoadProfiles": "プロファイルのロードに失敗しました", + "nameTooLong": "プロフィール名が長すぎます", + "descriptionTooLong": "説明が長すぎます", + "secretsEncrypted": "シークレットは保存前に暗号化されます。", + "profileActive": "プロファイルがアクティブです", + "profilesActive": "プロファイルがアクティブです", + "customConfigActive": "カスタム構成がアクティブです", + "configNeedsAttention": "設定には注意が必要です", + "repoProfile": "リポジトリプロファイル", + "selectedProfile": "選択したプロファイル", + "manualOverrides": "手動オーバーライド", + "openSettingsToReview": "設定を開いて確認してください", + "profileSelection": "プロファイルの選択" }, "partRenderer": { "updatedFileCount_one": "{{displayCount}}件のファイルを更新しました。", @@ -3224,5 +3250,180 @@ "multiplierError": "1以上の倍率を入力してください", "permissionDenied": "支出アラートを管理する権限がありません。", "pushOffByCategory": "通知でオフ" + }, + "profiles": { + "title": "プロフィール", + "entrySubtitle": "セッション間で環境設定を再利用する", + "newProfile": "新しいプロフィール", + "emptyTitle": "まだプロフィールがありません", + "emptyDescription": "プロファイルを作成して、セッション間で環境変数、セットアップ コマンド、スキルを再利用します。", + "loadFailed": "プロファイルを読み込めませんでした", + "counts": { + "vars": "{{count}} 変数", + "mcp": "{{count}} MCP", + "skills": "{{count}} スキル", + "commands": "{{count}} コマンド", + "varsShort": "{{count}}変", + "skillsShort": "{{count}}ス", + "commandsShort": "{{count}}コ" + }, + "nameLabel": "プロファイル名", + "namePlaceholder": "例えばバックエンドのデバッグ", + "descriptionLabel": "プロフィールの説明", + "descriptionPlaceholder": "オプションの説明", + "ownerLabel": "プロフィール所有者", + "nameRequired": "プロファイル名を入力してください", + "createAction": "プロフィールの作成", + "createFailed": "プロフィールを作成できませんでした", + "createdToast": "プロファイル「{{name}}」が作成されました", + "settingsSectionTitle": "構成", + "defaultSectionTitle": "デフォルトのプロファイル", + "personalDefaultDescription": "リポジトリに固定プロファイルがない場合に自動ロードされます。", + "organizationDefaultDescription": "個人のデフォルトがないメンバーの場合は自動ロードされます。", + "setAsDefault": "デフォルトとして設定", + "removeDefault": "デフォルトとして削除", + "saveFailed": "プロフィールを保存できませんでした", + "deleteTitle": "プロフィールの削除", + "deleteMessage": "これにより、プロファイルとその設定が完全に削除されます。これを元に戻すことはできません。", + "deleteFailed": "プロフィールを削除できませんでした", + "deleteBlocked": "このプロファイルは Webhook トリガーによって使用されます。まずそれらのトリガーから削除してください。", + "deletedToast": "プロファイル「{{name}}」が削除されました", + "variablesTitle": "環境変数", + "addVariable": "変数の追加", + "keyLabel": "鍵", + "keyTooLong": "キーは256文字以内で入力してください", + "keyDuplicate": "このキーはすでにプロファイルに存在します", + "valueLabel": "価値", + "secretLabel": "秘密", + "secrets": { + "markSecret": "シークレットとしてマークする", + "masked": "隠された値", + "reveal": "値を明らかにする", + "newValuePlaceholder": "新しいシークレット値を入力", + "valuePlaceholder": "シークレット値(暗号化)" + }, + "variablesEmpty": "まだ変数がありません", + "variablesSaveFailed": "変数を保存できませんでした", + "commandsTitle": "セットアップコマンド", + "addCommand": "コマンドの追加", + "commandLabel": "指示", + "commandPlaceholder": "e.g. pnpm install", + "commandsEmpty": "セットアップ コマンドはまだありません", + "commandsSaveFailed": "セットアップコマンドを保存できませんでした", + "moveUp": "上に移動", + "moveDown": "下に移動", + "skillsTitle": "スキル", + "addSkill": "スキルの追加", + "skillNameLabel": "スキル名", + "skillMarkdownLabel": "スキル内容", + "skillsEmpty": "まだスキルがありません", + "skillNameRequired": "スキル名を入力してください", + "skillNameInvalid": "スキル名は文字または数字で始まり、小文字、数字、ダッシュのみを使用する必要があります", + "skillContentRequired": "スキル内容は必須です", + "skillSaveFailed": "スキルを保存できませんでした", + "skillsHint": "SKILL.md を貼り付けると、その名前が入力されます。ドラッグ アンド ドロップせずにスマートフォンからスキルを追加します。", + "skillEditTitle": "エディットスキル", + "mcp": { + "title": "MCPサーバー", + "add": "MCPサーバーの追加", + "empty": "MCP サーバーはまだありません", + "name": "サーバー名", + "type": "サーバーの種類", + "localType": "地元", + "remoteType": "リモート", + "command": "コマンドライン", + "url": "URL", + "environment": "環境(JSON)", + "headers": "ヘッダー (JSON)", + "timeout": "タイムアウト", + "secretCount": "シークレット数", + "namePlaceholder": "my-server", + "commandPlaceholder": "npx @example/mcp-server", + "jsonPlaceholder": "{\n \"API_KEY\": \"sk-...\"\n}", + "nameRequired": "サーバー名を入力してください", + "nameInvalid": "名前には文字、数字、アンダースコア、ダッシュのみを使用できます", + "commandRequired": "コマンドを入力してください", + "commandTooLong": "最大50個の引数(各500文字)を使用してください", + "urlRequired": "URLを入力してください", + "urlInvalid": "有効な URL を入力してください", + "urlTooLong": "URLは2048文字以内で入力してください", + "jsonInvalid": "有効な JSON (文字列値のオブジェクト) を入力してください", + "jsonTooLarge": "最大50件のエントリ(キーは128文字、値は4096文字まで)を使用してください", + "timeoutInvalid": "タイムアウトはミリ秒単位の整数でなければなりません", + "edit": "MCPサーバーの編集", + "saveFailed": "MCP サーバーを保存できませんでした" + }, + "agents": { + "title": "プロファイルエージェント", + "add": "エージェントの追加", + "empty": "まだエージェントがいません", + "name": "エージェント名", + "slug": "エージェントスラッグ", + "description": "エージェントの説明", + "model": "モデルオーバーライド", + "modelPlaceholder": "セッションのデフォルトを使用する", + "prompt": "システムプロンプト", + "steps": "最大ステップ数", + "stepsPlaceholder": "例: 50", + "temperature": "温度", + "temperaturePlaceholder": "例: 0.2", + "topP": "top_p", + "topPPlaceholder": "例: 0.95", + "slugPlaceholder": "my-agent", + "namePlaceholder": "私のエージェント", + "promptPlaceholder": "あなたは...", + "tools": "ツール", + "visibility": "可視性", + "visibilityPrimary": "プライマリ — ピッカーに表示されます", + "visibilitySubagent": "サブエージェント — 他からのみ呼び出し可能", + "visibilityAll": "すべて — 両方", + "saveFailed": "エージェントを保存できませんでした", + "nameRequired": "エージェント名を入力してください", + "slugRequired": "エージェントスラッグを入力してください", + "slugInvalid": "スラグは文字で始まり、小文字、数字、ダッシュのみを使用する必要があります", + "slugConflict": "そのスラッグは組み込みエージェントによって予約されています", + "edit": "エージェントの編集" + }, + "slashCommands": { + "title": "スラッシュコマンド", + "add": "スラッシュコマンドを追加", + "empty": "スラッシュコマンドはまだありません", + "name": "コマンド名", + "description": "コマンドの説明", + "template": "テンプレート", + "namePlaceholder": "explain", + "templatePlaceholder": "$ARGUMENTS の説明", + "agent": "エージェント", + "subtask": "サブタスクとして実行", + "edit": "スラッシュコマンドの編集", + "saveFailed": "コマンドを保存できませんでした", + "nameRequired": "コマンド名を入力してください", + "nameInvalid": "名前は小文字で始まり、小文字、数字、ダッシュのみを使用する必要があります", + "templateRequired": "コマンドテンプレートを入力してください", + "nameConflict": "その名前は組み込みコマンドによって予約されています" + }, + "repoBindings": { + "title": "リポジトリのデフォルトプロファイル", + "add": "デフォルトを追加", + "empty": "デフォルトは設定されていません", + "emptyDescription": "リポジトリのセッションを開始するときにプロファイルを自動的に適用するデフォルトを追加します。", + "repo": "リポジトリを選択してください…", + "profile": "プロファイルの選択", + "remove": "バインドを解除する", + "private": "プライベート", + "search": "リポジトリを検索…", + "addedToast": "「{{repo}}」に設定されたデフォルトのプロファイル", + "addFailed": "デフォルトのプロファイルを設定できませんでした", + "removedToast": "「{{repo}}」のデフォルトプロファイルが削除されました", + "removeFailed": "デフォルトのプロファイルを削除できませんでした", + "pinnedTitle": "リポジトリに固定", + "pinRepo": "リポジトリを固定する", + "pinnedEmpty": "どのリポジトリにも固定されていません。", + "pinnedDescription": "セッションがこれらのリポジトリのいずれかをターゲットにするたびに自動ロードされます。", + "pinnedToast": "「{{repo}}」に固定されています", + "pinFailed": "リポジトリを固定できませんでした", + "unboundToast": "バインドされていないリポジトリ", + "unbindFailed": "リポジトリのバインドを解除できませんでした" + } } } diff --git a/apps/mobile/src/i18n/locales/ka.json b/apps/mobile/src/i18n/locales/ka.json index 1d112b0d1e7..a3483924b2d 100644 --- a/apps/mobile/src/i18n/locales/ka.json +++ b/apps/mobile/src/i18n/locales/ka.json @@ -2191,7 +2191,33 @@ "continueFrom": "გრძელდება სესია: {{title}}.", "importingSession": "სესიის იმპორტი მიმდინარეობს…", "cliCannotContinue": "ამ CLI-ს სესიის გაგრძელება არ შეუძლია. აირჩიეთ Cloud Agent ან სხვა CLI.", - "importFailed": "ამ სესიის იმპორტირება ვერ მოხერხდა." + "importFailed": "ამ სესიის იმპორტირება ვერ მოხერხდა.", + "pickProfile": "აირჩიეთ პროფილი", + "noProfile": "პროფილი არ არის", + "manageProfiles": "პროფილების მართვა", + "noProfiles": "ჯერ არ არის პროფილები. შექმენით ერთი გარემოს ცვლადების, MCP სერვერების და უნარების დასამატებლად.", + "advancedConfig": "გაფართოებული კონფიგურაცია", + "advancedConfigDescription": "აირჩიეთ პროფილი შენახული კონფიგურაციების ჩასატვირთად და ხელით დააკონფიგურიროთ გარემოს დამატებითი ცვლადები და დაყენების ბრძანებები", + "saveAsProfile": "პროფილად შენახვა", + "saveProfileDescription": "შეინახეთ მიმდინარე გარემოს ცვლადები და დაყენების ბრძანებები, როგორც მრავალჯერადი გამოყენებადი პროფილი.", + "profileSummary": "{{vars}} გარემოს ცვლადები · {{commands}} დაყენების ბრძანებები", + "profileSaved": "პროფილი \"{{name}}\" შენახულია", + "organizationProfiles": "ორგანიზაციის პროფილები", + "personalProfiles": "პერსონალური პროფილები", + "yourProfiles": "თქვენი პროფილები", + "failedToLoadProfiles": "პროფილების ჩატვირთვა ვერ მოხერხდა", + "nameTooLong": "პროფილის სახელი ძალიან გრძელია", + "descriptionTooLong": "აღწერა ძალიან გრძელია", + "secretsEncrypted": "საიდუმლოებები დაშიფრულია შენახვამდე.", + "profileActive": "პროფილი აქტიურია", + "profilesActive": "პროფილები აქტიურია", + "customConfigActive": "მორგებული კონფიგურაცია აქტიურია", + "configNeedsAttention": "კონფიგურაციას ყურადღება სჭირდება", + "repoProfile": "რეპო პროფილი", + "selectedProfile": "არჩეული პროფილი", + "manualOverrides": "მექანიკური უგულებელყოფა", + "openSettingsToReview": "გადასახედად გახსენით პარამეტრები", + "profileSelection": "პროფილის შერჩევა" }, "partRenderer": { "updatedFileCount_one": "განახლდა {{displayCount}} ფაილი.", @@ -3224,5 +3250,180 @@ "multiplierError": "შეიყვანე 1 ან მეტი მამრავლი", "permissionDenied": "ხარჯვის შეტყობინებების მართვის უფლება არ გაქვთ.", "pushOffByCategory": "გამორთულია შეტყობინებებში" + }, + "profiles": { + "title": "პროფილები", + "entrySubtitle": "ხელახლა გამოიყენეთ გარემოს პარამეტრები სესიებზე", + "newProfile": "ახალი პროფილი", + "emptyTitle": "ჯერ არ არის პროფილები", + "emptyDescription": "შექმენით პროფილი გარემოს ცვლადების, დაყენების ბრძანებებისა და უნარების ხელახლა გამოსაყენებლად სესიებზე.", + "loadFailed": "პროფილების ჩატვირთვა ვერ მოხერხდა", + "counts": { + "vars": "{{count}} ცვლადი", + "mcp": "{{count}} MCP", + "skills": "{{count}} უნარი", + "commands": "{{count}} ბრძანება", + "varsShort": "{{count}}ც", + "skillsShort": "{{count}}უ", + "commandsShort": "{{count}}ბ" + }, + "nameLabel": "პროფილის სახელი", + "namePlaceholder": "მაგ. Backend-ის გამართვა", + "descriptionLabel": "პროფილის აღწერა", + "descriptionPlaceholder": "არჩევითი აღწერა", + "ownerLabel": "პროფილის მფლობელი", + "nameRequired": "შეიყვანეთ პროფილის სახელი", + "createAction": "პროფილის შექმნა", + "createFailed": "პროფილის შექმნა ვერ მოხერხდა", + "createdToast": "პროფილი \"{{name}}\" შეიქმნა", + "settingsSectionTitle": "კონფიგურაცია", + "defaultSectionTitle": "ნაგულისხმევი პროფილი", + "personalDefaultDescription": "ავტომატურად იტვირთება, როდესაც არცერთ საცავს არ აქვს ჩამაგრებული პროფილი.", + "organizationDefaultDescription": "ავტომატურად იტვირთება წევრებისთვის, რომლებსაც არ აქვთ პირადი ნაგულისხმევი.", + "setAsDefault": "ნაგულისხმევად დაყენება", + "removeDefault": "ნაგულისხმევად წაშლა", + "saveFailed": "პროფილის შენახვა ვერ მოხერხდა", + "deleteTitle": "პროფილის წაშლა", + "deleteMessage": "ეს სამუდამოდ წაშლის პროფილს და მის პარამეტრებს. ამის გაუქმება შეუძლებელია.", + "deleteFailed": "პროფილის წაშლა ვერ მოხერხდა", + "deleteBlocked": "ეს პროფილი გამოიყენება webhook ტრიგერით. ჯერ ამოიღეთ იგი ამ ტრიგერებიდან.", + "deletedToast": "პროფილი \"{{name}}\" წაიშალა", + "variablesTitle": "გარემოს ცვლადები", + "addVariable": "ცვლადის დამატება", + "keyLabel": "გასაღები", + "keyTooLong": "გასაღები უნდა იყოს 256 სიმბოლო ან ნაკლები", + "keyDuplicate": "ეს გასაღები უკვე არსებობს პროფილში", + "valueLabel": "ღირებულება", + "secretLabel": "საიდუმლო", + "secrets": { + "markSecret": "საიდუმლოდ მონიშვნა", + "masked": "ფარული ღირებულება", + "reveal": "გამოავლინე ღირებულება", + "newValuePlaceholder": "შეიყვანეთ ახალი საიდუმლო მნიშვნელობა", + "valuePlaceholder": "საიდუმლო მნიშვნელობა (დაშიფრული)" + }, + "variablesEmpty": "ჯერ არ არის ცვლადი", + "variablesSaveFailed": "ცვლადის შენახვა ვერ მოხერხდა", + "commandsTitle": "დაყენების ბრძანებები", + "addCommand": "დაამატეთ ბრძანება", + "commandLabel": "ბრძანება", + "commandPlaceholder": "e.g. pnpm install", + "commandsEmpty": "დაყენების ბრძანებები ჯერ არ არის", + "commandsSaveFailed": "დაყენების ბრძანებების შენახვა ვერ მოხერხდა", + "moveUp": "ზევით გადაადგილება", + "moveDown": "ქვევით გადაადგილება", + "skillsTitle": "უნარები", + "addSkill": "დაამატეთ უნარი", + "skillNameLabel": "უნარის სახელი", + "skillMarkdownLabel": "უნარის შინაარსი", + "skillsEmpty": "ჯერ არ არის უნარები", + "skillNameRequired": "შეიყვანეთ უნარის სახელი", + "skillNameInvalid": "უნარის სახელი უნდა დაიწყოს ასოთი ან ციფრით და გამოიყენოს მხოლოდ მცირე ასოები, ციფრები და ტირეები", + "skillContentRequired": "საჭიროა უნარების შინაარსი", + "skillSaveFailed": "უნარების შენახვა ვერ მოხერხდა", + "skillsHint": "ჩასვით SKILL.md და ივსება მისი სახელი; დაამატეთ უნარი თქვენი ტელეფონიდან გადათრევის გარეშე.", + "skillEditTitle": "რედაქტირების უნარი", + "mcp": { + "title": "MCP სერვერები", + "add": "დაამატეთ MCP სერვერი", + "empty": "MCP სერვერები ჯერ არ არის", + "name": "სერვერის სახელი", + "type": "სერვერის ტიპი", + "localType": "ადგილობრივი", + "remoteType": "დისტანციური", + "command": "ბრძანების ხაზი", + "url": "URL", + "environment": "გარემო (JSON)", + "headers": "სათაურები (JSON)", + "timeout": "ტაიმაუტი", + "secretCount": "საიდუმლო დათვლა", + "namePlaceholder": "my-server", + "commandPlaceholder": "npx @example/mcp-server", + "jsonPlaceholder": "{\n \"API_KEY\": \"sk-...\"\n}", + "nameRequired": "შეიყვანეთ სერვერის სახელი", + "nameInvalid": "სახელს შეუძლია გამოიყენოს მხოლოდ ასოები, რიცხვები, ხაზები და ტირეები", + "commandRequired": "შეიყვანეთ ბრძანება", + "commandTooLong": "გამოიყენეთ მაქსიმუმ 50 არგუმენტი, თითოეული 500 სიმბოლო", + "urlRequired": "შეიყვანეთ URL", + "urlInvalid": "შეიყვანეთ სწორი URL", + "urlTooLong": "URL უნდა იყოს 2048 სიმბოლო ან ნაკლები", + "jsonInvalid": "შეიყვანეთ სწორი JSON - სიმებიანი მნიშვნელობების ობიექტი", + "jsonTooLarge": "გამოიყენეთ მაქსიმუმ 50 ჩანაწერი, გასაღებები 128-მდე და მნიშვნელობები 4096 სიმბოლომდე", + "timeoutInvalid": "დროის ამოწურვა უნდა იყოს მილიწამების მთელი რაოდენობა", + "edit": "MCP სერვერის რედაქტირება", + "saveFailed": "MCP სერვერის შენახვა ვერ მოხერხდა" + }, + "agents": { + "title": "პროფილის აგენტები", + "add": "აგენტის დამატება", + "empty": "აგენტები ჯერ არ არის", + "name": "აგენტის სახელი", + "slug": "აგენტი შლაგი", + "description": "აგენტის აღწერა", + "model": "მოდელის გადაფარვა", + "modelPlaceholder": "გამოიყენეთ სესიის ნაგულისხმევი", + "prompt": "სისტემის მოთხოვნა", + "steps": "მაქს. ნაბიჯები", + "stepsPlaceholder": "მაგ. 50", + "temperature": "ტემპერატურა", + "temperaturePlaceholder": "მაგ. 0.2", + "topP": "top_p", + "topPPlaceholder": "მაგ. 0.95", + "slugPlaceholder": "my-agent", + "namePlaceholder": "ჩემი აგენტი", + "promptPlaceholder": "შენ ხარ...", + "tools": "ხელსაწყოები", + "visibility": "ხილვადობა", + "visibilityPrimary": "პირველადი - ნაჩვენებია ამომრჩეველში", + "visibilitySubagent": "სუბაგენტი - გამოძახება შესაძლებელია მხოლოდ სხვებისგან", + "visibilityAll": "ყველა - ორივე", + "saveFailed": "აგენტის შენახვა ვერ მოხერხდა", + "nameRequired": "შეიყვანეთ აგენტის სახელი", + "slugRequired": "შეიყვანეთ აგენტი შლაგი", + "slugInvalid": "Slug უნდა დაიწყოს ასოთი და გამოიყენოს მხოლოდ მცირე ასოები, ციფრები და ტირეები", + "slugConflict": "ეს შლაკი დაცულია ჩაშენებული აგენტის მიერ", + "edit": "რედაქტირების აგენტი" + }, + "slashCommands": { + "title": "Slash ბრძანებები", + "add": "დაამატე slash ბრძანება", + "empty": "Slash ბრძანებები ჯერ არ არის", + "name": "ბრძანების სახელი", + "description": "ბრძანების აღწერა", + "template": "შაბლონი", + "namePlaceholder": "explain", + "templatePlaceholder": "ახსენით $ARGUMENTS", + "agent": "აგენტი", + "subtask": "გაშვება როგორც ქვეამოცანა", + "edit": "დახრილი ბრძანების რედაქტირება", + "saveFailed": "ბრძანების შენახვა ვერ მოხერხდა", + "nameRequired": "შეიყვანეთ ბრძანების სახელი", + "nameInvalid": "სახელი უნდა იწყებოდეს მცირე ასოებით და გამოიყენოს მხოლოდ მცირე ასოები, ციფრები და ტირეები", + "templateRequired": "შეიყვანეთ ბრძანების შაბლონი", + "nameConflict": "ეს სახელი დაცულია ჩაშენებული ბრძანებით" + }, + "repoBindings": { + "title": "ნაგულისხმევი პროფილები რეპოსთვის", + "add": "ნაგულისხმევი დამატება", + "empty": "ნაგულისხმევი კონფიგურაცია არ არის", + "emptyDescription": "დაამატეთ ნაგულისხმევი პროფილის ავტომატურად გამოსაყენებლად რეპოს სესიების დაწყებისას.", + "repo": "აირჩიეთ საცავი…", + "profile": "აირჩიეთ პროფილი", + "remove": "მოხსნა", + "private": "პირადი", + "search": "საცავების ძიება…", + "addedToast": "ნაგულისხმევი პროფილი დაყენებულია „{{repo}}“-ისთვის", + "addFailed": "ნაგულისხმევი პროფილის დაყენება ვერ მოხერხდა", + "removedToast": "\"{{repo}}\"-ის ნაგულისხმევი პროფილი წაშლილია", + "removeFailed": "ნაგულისხმევი პროფილის წაშლა ვერ მოხერხდა", + "pinnedTitle": "ჩამაგრებულია საცავებში", + "pinRepo": "რეპოს ჩამაგრება", + "pinnedEmpty": "არ არის მიმაგრებული არცერთ საცავზე.", + "pinnedDescription": "ავტომატური ჩატვირთვა ყოველთვის, როდესაც სესია მიმართულია რომელიმე ამ საცავში.", + "pinnedToast": "ჩამაგრებულია „{{repo}}“-ზე", + "pinFailed": "რეპოს ჩამაგრება ვერ მოხერხდა", + "unboundToast": "რეპო შეუზღუდავია", + "unbindFailed": "რეპოს გაუქმება ვერ მოხერხდა" + } } } diff --git a/apps/mobile/src/i18n/locales/kk.json b/apps/mobile/src/i18n/locales/kk.json index e34aa2ccc57..3bf1bda3b38 100644 --- a/apps/mobile/src/i18n/locales/kk.json +++ b/apps/mobile/src/i18n/locales/kk.json @@ -2191,7 +2191,33 @@ "continueFrom": "«{{title}}» сессиясы жалғасуда.", "importingSession": "Сессия импортталуда", "cliCannotContinue": "Бұл CLI сессияны жалғастыра алмайды. Cloud Agent қызметін немесе басқа CLI құралын таңдаңыз.", - "importFailed": "Бұл сессияны импорттау мүмкін болмады." + "importFailed": "Бұл сессияны импорттау мүмкін болмады.", + "pickProfile": "Профиль таңдаңыз", + "noProfile": "Профиль жоқ", + "manageProfiles": "Профильдерді басқару", + "noProfiles": "Әзірге профильдер жоқ. Айнымалы ортаны, MCP серверлерін және дағдыларды қосу үшін біреуін жасаңыз.", + "advancedConfig": "Кеңейтілген конфигурация", + "advancedConfigDescription": "Сақталған конфигурацияларды жүктеу үшін профильді таңдаңыз және қосымша орта айнымалыларын және орнату пәрмендерін қолмен конфигурациялаңыз", + "saveAsProfile": "Профиль ретінде сақтау", + "saveProfileDescription": "Ағымдағы орта айнымалы мәндерін және орнату пәрмендерін қайта пайдалануға болатын профиль ретінде сақтаңыз.", + "profileSummary": "{{vars}} ортаның айнымалы мәндері · {{commands}} орнату пәрмендері", + "profileSaved": "\"{{name}}\" профилі сақталды", + "organizationProfiles": "Ұйым профильдері", + "personalProfiles": "Жеке профильдер", + "yourProfiles": "Сіздің профильдеріңіз", + "failedToLoadProfiles": "Профильдер жүктелмеді", + "nameTooLong": "Профиль атауы тым ұзын", + "descriptionTooLong": "Сипаттама тым ұзақ", + "secretsEncrypted": "Құпиялар сақтау алдында шифрланады.", + "profileActive": "Профиль белсенді", + "profilesActive": "Профильдер белсенді", + "customConfigActive": "Арнаулы конфигурация белсенді", + "configNeedsAttention": "Конфигурацияға назар аудару керек", + "repoProfile": "Репо профилі", + "selectedProfile": "Таңдалған профиль", + "manualOverrides": "Қолмен қайта анықтау", + "openSettingsToReview": "Қарап шығу үшін Параметрлерді ашыңыз", + "profileSelection": "Профиль таңдау" }, "partRenderer": { "updatedFileCount_one": "{{displayCount}} файл жаңартылды.", @@ -3224,5 +3250,180 @@ "multiplierError": "1 немесе одан үлкен көбейткіш енгіз", "permissionDenied": "Шығыс ескертулерін басқаруға рұқсатыңыз жоқ.", "pushOffByCategory": "Хабарландыруларда өшірулі" + }, + "profiles": { + "title": "Профильдер", + "entrySubtitle": "Сеанстарда орта параметрлерін қайта пайдаланыңыз", + "newProfile": "Жаңа профиль", + "emptyTitle": "Әзірге профильдер жоқ", + "emptyDescription": "Орташа айнымалы мәндерді, орнату пәрмендерін және сеанстардағы дағдыларды қайта пайдалану үшін профиль жасаңыз.", + "loadFailed": "Профильдерді жүктеу мүмкін болмады", + "counts": { + "vars": "{{count}} айнымалы", + "mcp": "{{count}} MCP", + "skills": "{{count}} дағды", + "commands": "{{count}} пәрмен", + "varsShort": "{{count}}а", + "skillsShort": "{{count}}д", + "commandsShort": "{{count}}п" + }, + "nameLabel": "Профиль аты", + "namePlaceholder": "мысалы Backend жөндеу", + "descriptionLabel": "Профиль сипаттамасы", + "descriptionPlaceholder": "Қосымша сипаттама", + "ownerLabel": "Профиль иесі", + "nameRequired": "Профиль атын енгізіңіз", + "createAction": "Профиль жасау", + "createFailed": "Профиль жасау мүмкін болмады", + "createdToast": "\"{{name}}\" профилі жасалды", + "settingsSectionTitle": "Конфигурация", + "defaultSectionTitle": "Әдепкі профиль", + "personalDefaultDescription": "Репозиторийде бекітілген профиль болмаған кезде автоматты түрде жүктеледі.", + "organizationDefaultDescription": "Жеке әдепкі жоқ мүшелер үшін автоматты түрде жүктеледі.", + "setAsDefault": "Әдепкі ретінде орнату", + "removeDefault": "Әдепкі ретінде жою", + "saveFailed": "Профильді сақтау мүмкін болмады", + "deleteTitle": "Профильді жою", + "deleteMessage": "Бұл профильді және оның параметрлерін біржола жояды. Бұл әрекетті қайтару мүмкін емес.", + "deleteFailed": "Профильді жою мүмкін болмады", + "deleteBlocked": "Бұл профиль вебхук триггерімен пайдаланылады. Алдымен оны сол триггерлерден алып тастаңыз.", + "deletedToast": "\"{{name}}\" профилі жойылды", + "variablesTitle": "Қоршаған ортаның айнымалылары", + "addVariable": "Айнымалыны қосыңыз", + "keyLabel": "Кілт", + "keyTooLong": "Кілт 256 таңбадан аспауы керек", + "keyDuplicate": "Бұл кілт профильде бұрыннан бар", + "valueLabel": "Мән", + "secretLabel": "Құпия", + "secrets": { + "markSecret": "Құпия деп белгілеңіз", + "masked": "Жасырын мән", + "reveal": "Құндылық таныту", + "newValuePlaceholder": "Жаңа құпия мәнді енгізіңіз", + "valuePlaceholder": "Құпия мән (шифрланған)" + }, + "variablesEmpty": "Айнымалылар әлі жоқ", + "variablesSaveFailed": "Айнымалы мәнді сақтау мүмкін болмады", + "commandsTitle": "Орнату пәрмендері", + "addCommand": "Пәрменді қосу", + "commandLabel": "Пәрмен", + "commandPlaceholder": "e.g. pnpm install", + "commandsEmpty": "Орнату пәрмендері әлі жоқ", + "commandsSaveFailed": "Орнату пәрмендерін сақтау мүмкін болмады", + "moveUp": "Жоғары жылжытыңыз", + "moveDown": "Төмен жылжытыңыз", + "skillsTitle": "Дағдылар", + "addSkill": "Шеберлікті қосыңыз", + "skillNameLabel": "Дағды атауы", + "skillMarkdownLabel": "Дағды мазмұны", + "skillsEmpty": "Әзірге дағдылар жоқ", + "skillNameRequired": "Дағды атын енгізіңіз", + "skillNameInvalid": "Дағды атауы әріптен немесе цифрдан басталып, тек кіші әріптерді, сандарды және сызықшаларды пайдалануы керек.", + "skillContentRequired": "Біліктілік мазмұны қажет", + "skillSaveFailed": "Шеберлікті сақтау мүмкін болмады", + "skillsHint": "SKILL.md қойыңыз және оның аты толтырылады; сүйреп апармай телефоныңыздан дағды қосыңыз.", + "skillEditTitle": "Дағдыны өңдеу", + "mcp": { + "title": "MCP серверлері", + "add": "MCP серверін қосыңыз", + "empty": "MCP серверлері әлі жоқ", + "name": "Сервер атауы", + "type": "Сервер түрі", + "localType": "Жергілікті", + "remoteType": "Қашықтан", + "command": "Пәрмен жолы", + "url": "URL", + "environment": "Қоршаған орта (JSON)", + "headers": "Тақырыптар (JSON)", + "timeout": "Үзіліс", + "secretCount": "Құпия санау", + "namePlaceholder": "my-server", + "commandPlaceholder": "npx @example/mcp-server", + "jsonPlaceholder": "{\n \"API_KEY\": \"sk-...\"\n}", + "nameRequired": "Сервер атауын енгізіңіз", + "nameInvalid": "Атау тек әріптерді, сандарды, астын сызуларды және сызықтарды пайдалана алады", + "commandRequired": "Пәрменді енгізіңіз", + "commandTooLong": "Ең көбі 50 аргумент пайдаланыңыз, әрқайсысы 500 таңба", + "urlRequired": "URL мекенжайын енгізіңіз", + "urlInvalid": "Жарамды URL мекенжайын енгізіңіз", + "urlTooLong": "URL 2048 таңбадан аспауы керек", + "jsonInvalid": "Жарамды JSON енгізіңіз — жол мәндерінің нысаны", + "jsonTooLarge": "Ең көбі 50 жазба пайдаланыңыз, кілттер 128-ге және мәндер 4096 таңбаға дейін", + "timeoutInvalid": "Күту уақыты миллисекундтардың бүтін саны болуы керек", + "edit": "MCP серверін өңдеу", + "saveFailed": "MCP серверін сақтау мүмкін болмады" + }, + "agents": { + "title": "Профиль агенттері", + "add": "Агент қосыңыз", + "empty": "Әзірге агенттер жоқ", + "name": "Агент аты", + "slug": "Слаг агент", + "description": "Агент сипаттамасы", + "model": "Үлгіні қайта анықтау", + "modelPlaceholder": "Сеанстың әдепкі параметрін пайдаланыңыз", + "prompt": "Жүйе шақыруы", + "steps": "Макс. қадам", + "stepsPlaceholder": "мыс. 50", + "temperature": "Температура", + "temperaturePlaceholder": "мыс. 0.2", + "topP": "top_p", + "topPPlaceholder": "мыс. 0.95", + "slugPlaceholder": "my-agent", + "namePlaceholder": "Менің агентім", + "promptPlaceholder": "Сіз ...", + "tools": "Құралдар", + "visibility": "Көріну", + "visibilityPrimary": "Негізгі — таңдаушыда көрсетіледі", + "visibilitySubagent": "Субагент — тек басқалардан шақыруға болады", + "visibilityAll": "Барлығы - екеуі де", + "saveFailed": "Агентті сақтау мүмкін болмады", + "nameRequired": "Агент атын енгізіңіз", + "slugRequired": "Агент слогын енгізіңіз", + "slugInvalid": "Слаг әріптен басталып, тек кіші әріптерді, сандарды және сызықшаларды қолдануы керек", + "slugConflict": "Бұл слагты кірістірілген агент сақтайды", + "edit": "Агентті өңдеу" + }, + "slashCommands": { + "title": "Slash пәрмендері", + "add": "Қиғаш сызық пәрменін қосыңыз", + "empty": "Әлі қиғаш сызық пәрмендері жоқ", + "name": "Пәрмен аты", + "description": "Команда сипаттамасы", + "template": "Үлгі", + "namePlaceholder": "explain", + "templatePlaceholder": "$ARGUMENTS түсіндіріңіз", + "agent": "Агент", + "subtask": "Қосымша тапсырма ретінде іске қосыңыз", + "edit": "Қиғаш сызықты өңдеу пәрмені", + "saveFailed": "Пәрменді сақтау мүмкін болмады", + "nameRequired": "Пәрмен атауын енгізіңіз", + "nameInvalid": "Атау кіші әріптен басталып, тек кіші әріптерді, сандарды және сызықшаларды пайдалануы керек", + "templateRequired": "Пәрмен үлгісін енгізіңіз", + "nameConflict": "Бұл атау кірістірілген пәрмен арқылы сақталады" + }, + "repoBindings": { + "title": "Репо үшін әдепкі профильдер", + "add": "Әдепкі қосу", + "empty": "Әдепкі параметрлер конфигурацияланбаған", + "emptyDescription": "Репо сеанстарын бастаған кезде профильді автоматты түрде қолдану үшін әдепкі параметрді қосыңыз.", + "repo": "Репозиторийді таңдау…", + "profile": "Профильді таңдаңыз", + "remove": "Босату", + "private": "Жеке", + "search": "Репозиторийлерді іздеу…", + "addedToast": "\"{{repo}}\" үшін әдепкі профиль орнатылған", + "addFailed": "Әдепкі профильді орнату мүмкін болмады", + "removedToast": "\"{{repo}}\" үшін әдепкі профиль жойылды", + "removeFailed": "Әдепкі профильді жою мүмкін болмады", + "pinnedTitle": "Репозиторийлерге бекітілген", + "pinRepo": "Репо бекітіңіз", + "pinnedEmpty": "Ешбір репозиторийлерге бекітілмеген.", + "pinnedDescription": "Сеанс осы репозитарийлердің біріне бағытталған сайын автоматты түрде жүктеледі.", + "pinnedToast": "\"{{repo}}\" қалтасына бекітілген", + "pinFailed": "Репо бекіту мүмкін болмады", + "unboundToast": "Репо шектелмеген", + "unbindFailed": "Репоны ажырату мүмкін болмады" + } } } diff --git a/apps/mobile/src/i18n/locales/km.json b/apps/mobile/src/i18n/locales/km.json index bd035906673..8d4b1c04ff6 100644 --- a/apps/mobile/src/i18n/locales/km.json +++ b/apps/mobile/src/i18n/locales/km.json @@ -2191,7 +2191,33 @@ "continueFrom": "កំពុងបន្តវគ្គ {{title}}។", "importingSession": "កំពុងនាំចូលវគ្គ", "cliCannotContinue": "CLI នេះមិនអាចបន្តវគ្គបានទេ។ ជ្រើសរើស Cloud Agent ឬ CLI ផ្សេងទៀត។", - "importFailed": "មិនអាចនាំចូលវគ្គនេះបានទេ។" + "importFailed": "មិនអាចនាំចូលវគ្គនេះបានទេ។", + "pickProfile": "ជ្រើសរើសប្រវត្តិរូប", + "noProfile": "គ្មានប្រវត្តិរូប", + "manageProfiles": "គ្រប់គ្រងទម្រង់", + "noProfiles": "មិនទាន់មានប្រវត្តិរូបទេ។ បង្កើតមួយដើម្បីបន្ថែមអថេរបរិស្ថាន ម៉ាស៊ីនមេ MCP និងជំនាញ។", + "advancedConfig": "ការកំណត់រចនាសម្ព័ន្ធកម្រិតខ្ពស់", + "advancedConfigDescription": "ជ្រើសរើសទម្រង់ដើម្បីផ្ទុកការកំណត់រចនាសម្ព័ន្ធដែលបានរក្សាទុក ហើយកំណត់រចនាសម្ព័ន្ធអថេរបរិស្ថានបន្ថែម និងពាក្យបញ្ជាដំឡើងដោយដៃ", + "saveAsProfile": "រក្សាទុកជាកម្រងព័ត៌មាន", + "saveProfileDescription": "រក្សាទុកអថេរបរិស្ថានបច្ចុប្បន្ន និងពាក្យបញ្ជាដំឡើងជាទម្រង់ដែលអាចប្រើឡើងវិញបាន។", + "profileSummary": "អថេរបរិស្ថាន {{vars}} · ពាក្យបញ្ជាដំឡើង {{commands}}", + "profileSaved": "បានរក្សាទុកប្រវត្តិរូប \"{{name}}\"។", + "organizationProfiles": "ទម្រង់អង្គការ", + "personalProfiles": "ប្រវត្តិរូបផ្ទាល់ខ្លួន", + "yourProfiles": "ប្រវត្តិរូបរបស់អ្នក។", + "failedToLoadProfiles": "បរាជ័យក្នុងការផ្ទុកទម្រង់", + "nameTooLong": "ឈ្មោះកម្រងព័ត៌មានវែងពេក", + "descriptionTooLong": "ការពិពណ៌នាគឺវែងពេក", + "secretsEncrypted": "អាថ៌កំបាំងត្រូវបានអ៊ិនគ្រីបមុនពេលផ្ទុក។", + "profileActive": "កម្រងព័ត៌មានសកម្ម", + "profilesActive": "កម្រងព័ត៌មានសកម្ម", + "customConfigActive": "ការកំណត់ផ្ទាល់ខ្លួនសកម្ម", + "configNeedsAttention": "Config ត្រូវការការយកចិត្តទុកដាក់", + "repoProfile": "កម្រងព័ត៌មាន Repo", + "selectedProfile": "កម្រងព័ត៌មានដែលបានជ្រើសរើស", + "manualOverrides": "ការបដិសេធដោយដៃ", + "openSettingsToReview": "បើកការកំណត់ដើម្បីពិនិត្យ", + "profileSelection": "ការជ្រើសរើសប្រវត្តិរូប" }, "partRenderer": { "updatedFileCount_one": "បានធ្វើបច្ចុប្បន្នភាពឯកសារចំនួន {{displayCount}}។", @@ -3224,5 +3250,180 @@ "multiplierError": "បញ្ចូលតម្លៃគុណ 1 ឬច្រើនជាងនេះ", "permissionDenied": "អ្នកមិនមានសិទ្ធិគ្រប់គ្រងការជូនដំណឹងអំពីការចំណាយទេ។", "pushOffByCategory": "បិទក្នុងការជូនដំណឹង" + }, + "profiles": { + "title": "ប្រវត្តិរូប", + "entrySubtitle": "ប្រើការកំណត់បរិស្ថានឡើងវិញនៅទូទាំងវគ្គ", + "newProfile": "ប្រវត្តិរូបថ្មី។", + "emptyTitle": "មិនទាន់មានប្រវត្តិរូបទេ។", + "emptyDescription": "បង្កើតទម្រង់ដើម្បីប្រើអថេរបរិស្ថានឡើងវិញ រៀបចំពាក្យបញ្ជា និងជំនាញនៅទូទាំងវគ្គ។", + "loadFailed": "មិនអាចផ្ទុកទម្រង់បានទេ។", + "counts": { + "vars": "{{count}} អថេរ", + "mcp": "{{count}} MCP", + "skills": "{{count}} ជំនាញ", + "commands": "{{count}} ពាក្យបញ្ជា", + "varsShort": "{{count}}អ", + "skillsShort": "{{count}}ជ", + "commandsShort": "{{count}}ព" + }, + "nameLabel": "ឈ្មោះប្រវត្តិរូប", + "namePlaceholder": "ឧ. ការកែកំហុសផ្នែកខាងក្រោយ", + "descriptionLabel": "ការពិពណ៌នាអំពីប្រវត្តិរូប", + "descriptionPlaceholder": "ការពិពណ៌នាស្រេចចិត្ត", + "ownerLabel": "ម្ចាស់ប្រវត្តិរូប", + "nameRequired": "បញ្ចូលឈ្មោះកម្រងព័ត៌មាន", + "createAction": "បង្កើតទម្រង់", + "createFailed": "មិនអាចបង្កើតកម្រងព័ត៌មានបានទេ។", + "createdToast": "បានបង្កើតប្រវត្តិរូប \"{{name}}\"។", + "settingsSectionTitle": "ការកំណត់រចនាសម្ព័ន្ធ", + "defaultSectionTitle": "ទម្រង់លំនាំដើម", + "personalDefaultDescription": "ផ្ទុកដោយស្វ័យប្រវត្តិនៅពេលដែលគ្មានឃ្លាំងមានទម្រង់ដែលបានខ្ទាស់។", + "organizationDefaultDescription": "ផ្ទុកដោយស្វ័យប្រវត្តិសម្រាប់សមាជិកដែលមិនមានលំនាំដើមផ្ទាល់ខ្លួន។", + "setAsDefault": "កំណត់ជាលំនាំដើម", + "removeDefault": "លុបចេញតាមលំនាំដើម", + "saveFailed": "មិនអាចរក្សាទុកកម្រងព័ត៌មានបានទេ។", + "deleteTitle": "លុបប្រវត្តិរូប", + "deleteMessage": "វាលុបទម្រង់ និងការកំណត់របស់វាចេញជាអចិន្ត្រៃយ៍។ នេះមិនអាចត្រឡប់វិញបានទេ។", + "deleteFailed": "មិនអាចលុបប្រវត្តិរូបបានទេ។", + "deleteBlocked": "ទម្រង់នេះត្រូវបានប្រើដោយ webhook trigger ។ យកវាចេញពីគន្លឹះទាំងនោះជាមុនសិន។", + "deletedToast": "បានលុបប្រវត្តិរូប \"{{name}}\"។", + "variablesTitle": "អថេរបរិស្ថាន", + "addVariable": "បន្ថែមអថេរ", + "keyLabel": "សោ", + "keyTooLong": "សោត្រូវតែមាន 256 តួអក្សរ ឬតិចជាងនេះ", + "keyDuplicate": "សោនេះមានរួចហើយនៅក្នុងកម្រងព័ត៌មាន", + "valueLabel": "តម្លៃ", + "secretLabel": "សម្ងាត់", + "secrets": { + "markSecret": "សម្គាល់ជាសម្ងាត់", + "masked": "តម្លៃលាក់", + "reveal": "បង្ហាញតម្លៃ", + "newValuePlaceholder": "បញ្ចូលតម្លៃសម្ងាត់ថ្មី", + "valuePlaceholder": "តម្លៃសម្ងាត់ (បានអ៊ិនគ្រីប)" + }, + "variablesEmpty": "មិនមានអថេរនៅឡើយ", + "variablesSaveFailed": "មិនអាចរក្សាទុកអថេរបានទេ។", + "commandsTitle": "ដំឡើងពាក្យបញ្ជា", + "addCommand": "បន្ថែមពាក្យបញ្ជា", + "commandLabel": "បញ្ជា", + "commandPlaceholder": "e.g. pnpm install", + "commandsEmpty": "មិនទាន់មានពាក្យបញ្ជាដំឡើងនៅឡើយទេ", + "commandsSaveFailed": "មិនអាចរក្សាទុកពាក្យបញ្ជាដំឡើងបានទេ។", + "moveUp": "ផ្លាស់ទីឡើងលើ", + "moveDown": "ផ្លាស់ទីចុះក្រោម", + "skillsTitle": "ជំនាញ", + "addSkill": "បន្ថែមជំនាញ", + "skillNameLabel": "ឈ្មោះជំនាញ", + "skillMarkdownLabel": "មាតិកាជំនាញ", + "skillsEmpty": "មិនទាន់មានជំនាញនៅឡើយ", + "skillNameRequired": "បញ្ចូលឈ្មោះជំនាញ", + "skillNameInvalid": "ឈ្មោះជំនាញត្រូវតែចាប់ផ្តើមដោយអក្សរ ឬខ្ទង់ ហើយប្រើតែអក្សរតូច ខ្ទង់ និងសញ្ញាដាច់ៗប៉ុណ្ណោះ។", + "skillContentRequired": "មាតិកាជំនាញត្រូវបានទាមទារ", + "skillSaveFailed": "មិនអាចរក្សាទុកជំនាញបានទេ។", + "skillsHint": "បិទភ្ជាប់ SKILL.md ហើយឈ្មោះរបស់វាបំពេញ; បន្ថែមជំនាញពីទូរស័ព្ទរបស់អ្នកដោយមិនចាំបាច់អូស និងទម្លាក់។", + "skillEditTitle": "កែសម្រួលជំនាញ", + "mcp": { + "title": "ម៉ាស៊ីនមេ MCP", + "add": "បន្ថែមម៉ាស៊ីនមេ MCP", + "empty": "មិនមានម៉ាស៊ីនមេ MCP នៅឡើយទេ", + "name": "ឈ្មោះម៉ាស៊ីនមេ", + "type": "ប្រភេទម៉ាស៊ីនមេ", + "localType": "ក្នុងស្រុក", + "remoteType": "ពីចម្ងាយ", + "command": "បន្ទាត់ពាក្យបញ្ជា", + "url": "URL", + "environment": "បរិស្ថាន (JSON)", + "headers": "បឋមកថា (JSON)", + "timeout": "អស់ពេល", + "secretCount": "ចំនួនសម្ងាត់", + "namePlaceholder": "my-server", + "commandPlaceholder": "npx @example/mcp-server", + "jsonPlaceholder": "{\n \"API_KEY\": \"sk-...\"\n}", + "nameRequired": "បញ្ចូលឈ្មោះម៉ាស៊ីនមេ", + "nameInvalid": "ឈ្មោះអាចប្រើតែអក្សរ លេខ សញ្ញាគូសក្រោម និងសញ្ញាចុចៗ", + "commandRequired": "បញ្ចូលពាក្យបញ្ជា", + "commandTooLong": "ប្រើអាគុយម៉ង់ច្រើនបំផុត 50 ដែលនីមួយៗ 500 តួអក្សរ", + "urlRequired": "បញ្ចូល URL", + "urlInvalid": "បញ្ចូល URL ត្រឹមត្រូវ។", + "urlTooLong": "URL ត្រូវតែមាន 2048 តួអក្សរ ឬតិចជាងនេះ", + "jsonInvalid": "បញ្ចូល JSON ត្រឹមត្រូវ — វត្ថុនៃតម្លៃខ្សែអក្សរ", + "jsonTooLarge": "ប្រើធាតុច្រើនបំផុត 50 សោរហូតដល់ 128 និងតម្លៃរហូតដល់ 4096 តួអក្សរ", + "timeoutInvalid": "ការអស់ពេលត្រូវតែជាចំនួនមីលីវិនាទីទាំងមូល", + "edit": "កែសម្រួលម៉ាស៊ីនមេ MCP", + "saveFailed": "មិនអាចរក្សាទុកម៉ាស៊ីនមេ MCP បានទេ។" + }, + "agents": { + "title": "ភ្នាក់ងារប្រវត្តិរូប", + "add": "បន្ថែមភ្នាក់ងារ", + "empty": "មិនទាន់មានភ្នាក់ងារនៅឡើយ", + "name": "ឈ្មោះភ្នាក់ងារ", + "slug": "ភ្នាក់ងារ slug", + "description": "ការពិពណ៌នាភ្នាក់ងារ", + "model": "ការបដិសេធគំរូ", + "modelPlaceholder": "ប្រើលំនាំដើមសម័យ", + "prompt": "ប្រអប់បញ្ចូលប្រព័ន្ធ", + "steps": "ជំហានអតិបរមា", + "stepsPlaceholder": "ឧ. 50", + "temperature": "សីតុណ្ហភាព", + "temperaturePlaceholder": "ឧ. 0.2", + "topP": "top_p", + "topPPlaceholder": "ឧ. 0.95", + "slugPlaceholder": "my-agent", + "namePlaceholder": "ភ្នាក់ងាររបស់ខ្ញុំ", + "promptPlaceholder": "អ្នកគឺជា...", + "tools": "ឧបករណ៍", + "visibility": "ភាពមើលឃើញ", + "visibilityPrimary": "បឋម - បង្ហាញក្នុងកម្មវិធីជ្រើសរើស", + "visibilitySubagent": "Subagent - អាចហៅបានតែពីអ្នកដទៃប៉ុណ្ណោះ។", + "visibilityAll": "ទាំងអស់ - ទាំងពីរ", + "saveFailed": "មិនអាចរក្សាទុកភ្នាក់ងារបានទេ។", + "nameRequired": "បញ្ចូលឈ្មោះភ្នាក់ងារ", + "slugRequired": "បញ្ចូល slug ភ្នាក់ងារ", + "slugInvalid": "Slug ត្រូវតែចាប់ផ្តើមដោយអក្សរ ហើយប្រើតែអក្សរតូច ខ្ទង់ និងសញ្ញាដាច់ៗប៉ុណ្ណោះ។", + "slugConflict": "ស្លុតនោះត្រូវបានបម្រុងទុកដោយភ្នាក់ងារដែលភ្ជាប់មកជាមួយ", + "edit": "កែសម្រួលភ្នាក់ងារ" + }, + "slashCommands": { + "title": "ពាក្យបញ្ជា Slash", + "add": "បន្ថែមពាក្យបញ្ជា slash", + "empty": "មិនទាន់មានពាក្យបញ្ជា slash នៅឡើយទេ", + "name": "ឈ្មោះពាក្យបញ្ជា", + "description": "ការពិពណ៌នាពាក្យបញ្ជា", + "template": "គំរូ", + "namePlaceholder": "explain", + "templatePlaceholder": "ពន្យល់ $ARGUMENTS", + "agent": "ភ្នាក់ងារ", + "subtask": "ដំណើរការជាកិច្ចការរង", + "edit": "កែសម្រួលពាក្យបញ្ជា slash", + "saveFailed": "មិនអាចរក្សាទុកពាក្យបញ្ជាបានទេ។", + "nameRequired": "បញ្ចូលឈ្មោះពាក្យបញ្ជា", + "nameInvalid": "ឈ្មោះត្រូវតែចាប់ផ្តើមដោយអក្សរតូច ហើយប្រើតែអក្សរតូច ខ្ទង់ និងសញ្ញាដាច់ៗប៉ុណ្ណោះ។", + "templateRequired": "បញ្ចូលគំរូពាក្យបញ្ជា", + "nameConflict": "ឈ្មោះនោះត្រូវបានបម្រុងទុកដោយពាក្យបញ្ជាដែលភ្ជាប់មកជាមួយ" + }, + "repoBindings": { + "title": "ទម្រង់លំនាំដើមសម្រាប់ Repos", + "add": "បន្ថែមលំនាំដើម", + "empty": "មិនមានការកំណត់លំនាំដើមទេ។", + "emptyDescription": "បន្ថែមលំនាំដើមដើម្បីអនុវត្តទម្រង់ដោយស្វ័យប្រវត្តិនៅពេលចាប់ផ្តើមវគ្គសម្រាប់ repo ។", + "repo": "ជ្រើសរើសឃ្លាំង...", + "profile": "ជ្រើសរើសទម្រង់", + "remove": "ស្រាយចំណង", + "private": "ឯកជន", + "search": "ស្វែងរកឃ្លាំង...", + "addedToast": "បានកំណត់ទម្រង់លំនាំដើមសម្រាប់ \"{{repo}}\"។", + "addFailed": "មិនអាចកំណត់ទម្រង់លំនាំដើមបានទេ។", + "removedToast": "បានលុបទម្រង់លំនាំដើមសម្រាប់ \"{{repo}}\"។", + "removeFailed": "មិនអាចលុបទម្រង់លំនាំដើមបានទេ។", + "pinnedTitle": "បានខ្ទាស់ទៅកន្លែងផ្ទុក", + "pinRepo": "ខ្ទាស់ repo", + "pinnedEmpty": "មិនបានខ្ទាស់ទៅកន្លែងផ្ទុកណាមួយទេ។", + "pinnedDescription": "ផ្ទុកដោយស្វ័យប្រវត្តិនៅពេលណាដែលវគ្គមួយកំណត់គោលដៅមួយក្នុងចំណោមឃ្លាំងទាំងនេះ។", + "pinnedToast": "បានខ្ទាស់ទៅ \"{{repo}}\"។", + "pinFailed": "មិនអាចខ្ទាស់ repo បានទេ។", + "unboundToast": "បានផ្ដាច់ចំណង repo។", + "unbindFailed": "មិនអាចផ្ដាច់ repo បានទេ។" + } } } diff --git a/apps/mobile/src/i18n/locales/kn.json b/apps/mobile/src/i18n/locales/kn.json index 1cc37aec343..e80e602ecd4 100644 --- a/apps/mobile/src/i18n/locales/kn.json +++ b/apps/mobile/src/i18n/locales/kn.json @@ -2191,7 +2191,33 @@ "continueFrom": "{{title}} ಅನ್ನು ಮುಂದುವರಿಸಲಾಗುತ್ತಿದೆ.", "importingSession": "ಸೆಶನ್ ಆಮದು ಆಗುತ್ತಿದೆ", "cliCannotContinue": "ಈ CLI ಮೂಲಕ ಸೆಶನ್ ಅನ್ನು ಮುಂದುವರಿಸಲು ಸಾಧ್ಯವಿಲ್ಲ. Cloud Agent ಅಥವಾ ಇನ್ನೊಂದು CLI ಅನ್ನು ಆಯ್ಕೆಮಾಡಿ.", - "importFailed": "ಈ ಸೆಶನ್ ಅನ್ನು ಆಮದು ಮಾಡಲು ಸಾಧ್ಯವಾಗಲಿಲ್ಲ." + "importFailed": "ಈ ಸೆಶನ್ ಅನ್ನು ಆಮದು ಮಾಡಲು ಸಾಧ್ಯವಾಗಲಿಲ್ಲ.", + "pickProfile": "ಪ್ರೊಫೈಲ್ ಅನ್ನು ಆರಿಸಿ", + "noProfile": "ಪ್ರೊಫೈಲ್ ಇಲ್ಲ", + "manageProfiles": "ಪ್ರೊಫೈಲ್‌ಗಳನ್ನು ನಿರ್ವಹಿಸಿ", + "noProfiles": "ಇನ್ನೂ ಯಾವುದೇ ಪ್ರೊಫೈಲ್‌ಗಳಿಲ್ಲ. ಪರಿಸರ ವೇರಿಯಬಲ್‌ಗಳು, MCP ಸರ್ವರ್‌ಗಳು ಮತ್ತು ಕೌಶಲ್ಯಗಳನ್ನು ಸೇರಿಸಲು ಒಂದನ್ನು ರಚಿಸಿ.", + "advancedConfig": "ಸುಧಾರಿತ ಸಂರಚನೆ", + "advancedConfigDescription": "ಉಳಿಸಿದ ಕಾನ್ಫಿಗರೇಶನ್‌ಗಳನ್ನು ಲೋಡ್ ಮಾಡಲು ಪ್ರೊಫೈಲ್ ಆಯ್ಕೆಮಾಡಿ ಮತ್ತು ಹೆಚ್ಚುವರಿ ಪರಿಸರ ವೇರಿಯಬಲ್‌ಗಳು ಮತ್ತು ಸೆಟಪ್ ಆಜ್ಞೆಗಳನ್ನು ಹಸ್ತಚಾಲಿತವಾಗಿ ಕಾನ್ಫಿಗರ್ ಮಾಡಿ", + "saveAsProfile": "ಪ್ರೊಫೈಲ್ ಆಗಿ ಉಳಿಸಿ", + "saveProfileDescription": "ಪ್ರಸ್ತುತ ಪರಿಸರ ವೇರಿಯಬಲ್‌ಗಳು ಮತ್ತು ಸೆಟಪ್ ಆಜ್ಞೆಗಳನ್ನು ಮರುಬಳಕೆ ಮಾಡಬಹುದಾದ ಪ್ರೊಫೈಲ್‌ನಂತೆ ಉಳಿಸಿ.", + "profileSummary": "{{vars}} ಪರಿಸರ ವೇರಿಯಬಲ್‌ಗಳು · {{commands}} ಸೆಟಪ್ ಆಜ್ಞೆಗಳು", + "profileSaved": "ಪ್ರೊಫೈಲ್ \"{{name}}\" ಉಳಿಸಲಾಗಿದೆ", + "organizationProfiles": "ಸಂಸ್ಥೆಯ ಪ್ರೊಫೈಲ್ಗಳು", + "personalProfiles": "ವೈಯಕ್ತಿಕ ಪ್ರೊಫೈಲ್ಗಳು", + "yourProfiles": "ನಿಮ್ಮ ಪ್ರೊಫೈಲ್‌ಗಳು", + "failedToLoadProfiles": "ಪ್ರೊಫೈಲ್‌ಗಳನ್ನು ಲೋಡ್ ಮಾಡಲು ವಿಫಲವಾಗಿದೆ", + "nameTooLong": "ಪ್ರೊಫೈಲ್ ಹೆಸರು ತುಂಬಾ ಉದ್ದವಾಗಿದೆ", + "descriptionTooLong": "ವಿವರಣೆ ತುಂಬಾ ಉದ್ದವಾಗಿದೆ", + "secretsEncrypted": "ಸಂಗ್ರಹಣೆಯ ಮೊದಲು ರಹಸ್ಯಗಳನ್ನು ಎನ್‌ಕ್ರಿಪ್ಟ್ ಮಾಡಲಾಗುತ್ತದೆ.", + "profileActive": "ಪ್ರೊಫೈಲ್ ಸಕ್ರಿಯವಾಗಿದೆ", + "profilesActive": "ಪ್ರೊಫೈಲ್‌ಗಳು ಸಕ್ರಿಯವಾಗಿವೆ", + "customConfigActive": "ಕಸ್ಟಮ್ ಸಂರಚನಾ ಸಕ್ರಿಯವಾಗಿದೆ", + "configNeedsAttention": "ಸಂರಚನೆಗೆ ಗಮನ ಬೇಕು", + "repoProfile": "ರೆಪೊ ಪ್ರೊಫೈಲ್", + "selectedProfile": "ಆಯ್ಕೆಮಾಡಿದ ಪ್ರೊಫೈಲ್", + "manualOverrides": "ಹಸ್ತಚಾಲಿತ ಅತಿಕ್ರಮಿಸುತ್ತದೆ", + "openSettingsToReview": "ಪರಿಶೀಲಿಸಲು ಸೆಟ್ಟಿಂಗ್‌ಗಳನ್ನು ತೆರೆಯಿರಿ", + "profileSelection": "ಪ್ರೊಫೈಲ್ ಆಯ್ಕೆ" }, "partRenderer": { "updatedFileCount_one": "{{displayCount}} ಫೈಲ್ ಅನ್ನು ನವೀಕರಿಸಲಾಗಿದೆ.", @@ -3224,5 +3250,180 @@ "multiplierError": "1 ಅಥವಾ ಹೆಚ್ಚಿನ ಗುಣಕವನ್ನು ನಮೂದಿಸಿ", "permissionDenied": "ಖರ್ಚಿನ ಎಚ್ಚರಿಕೆಗಳನ್ನು ನಿರ್ವಹಿಸಲು ನಿಮಗೆ ಅನುಮತಿ ಇಲ್ಲ.", "pushOffByCategory": "ಅಧಿಸೂಚನೆಗಳಲ್ಲಿ ಆಫ್" + }, + "profiles": { + "title": "ಪ್ರೊಫೈಲ್ಗಳು", + "entrySubtitle": "ಸೆಷನ್‌ಗಳಾದ್ಯಂತ ಪರಿಸರ ಸೆಟ್ಟಿಂಗ್‌ಗಳನ್ನು ಮರುಬಳಕೆ ಮಾಡಿ", + "newProfile": "ಹೊಸ ಪ್ರೊಫೈಲ್", + "emptyTitle": "ಇನ್ನೂ ಯಾವುದೇ ಪ್ರೊಫೈಲ್‌ಗಳಿಲ್ಲ", + "emptyDescription": "ಸೆಷನ್‌ಗಳಾದ್ಯಂತ ಪರಿಸರ ವೇರಿಯಬಲ್‌ಗಳು, ಸೆಟಪ್ ಕಮಾಂಡ್‌ಗಳು ಮತ್ತು ಕೌಶಲ್ಯಗಳನ್ನು ಮರುಬಳಕೆ ಮಾಡಲು ಪ್ರೊಫೈಲ್ ಅನ್ನು ರಚಿಸಿ.", + "loadFailed": "ಪ್ರೊಫೈಲ್‌ಗಳನ್ನು ಲೋಡ್ ಮಾಡಲು ಸಾಧ್ಯವಾಗಲಿಲ್ಲ", + "counts": { + "vars": "{{count}} ಅಸ್ಥಿರ", + "mcp": "{{count}} MCP", + "skills": "{{count}} ಕೌಶಲ್ಯಗಳು", + "commands": "{{count}} ಆಜ್ಞೆಗಳು", + "varsShort": "{{count}}ಅ", + "skillsShort": "{{count}}ಕ", + "commandsShort": "{{count}}ಆ" + }, + "nameLabel": "ಪ್ರೊಫೈಲ್ ಹೆಸರು", + "namePlaceholder": "ಉದಾ. ಬ್ಯಾಕೆಂಡ್ ಡೀಬಗ್ ಮಾಡುವಿಕೆ", + "descriptionLabel": "ಪ್ರೊಫೈಲ್ ವಿವರಣೆ", + "descriptionPlaceholder": "ಐಚ್ಛಿಕ ವಿವರಣೆ", + "ownerLabel": "ಪ್ರೊಫೈಲ್ ಮಾಲೀಕರು", + "nameRequired": "ಪ್ರೊಫೈಲ್ ಹೆಸರನ್ನು ನಮೂದಿಸಿ", + "createAction": "ಪ್ರೊಫೈಲ್ ರಚಿಸಿ", + "createFailed": "ಪ್ರೊಫೈಲ್ ರಚಿಸಲು ಸಾಧ್ಯವಾಗಲಿಲ್ಲ", + "createdToast": "ಪ್ರೊಫೈಲ್ \"{{name}}\" ರಚಿಸಲಾಗಿದೆ", + "settingsSectionTitle": "ಸಂರಚನೆ", + "defaultSectionTitle": "ಡೀಫಾಲ್ಟ್ ಪ್ರೊಫೈಲ್", + "personalDefaultDescription": "ಯಾವುದೇ ರೆಪೊಸಿಟರಿಯು ಪಿನ್ ಮಾಡಿದ ಪ್ರೊಫೈಲ್ ಅನ್ನು ಹೊಂದಿಲ್ಲದಿದ್ದಾಗ ಸ್ವಯಂ-ಲೋಡ್ ಮಾಡಲಾಗಿದೆ.", + "organizationDefaultDescription": "ವೈಯಕ್ತಿಕ ಡೀಫಾಲ್ಟ್ ಹೊಂದಿರದ ಸದಸ್ಯರಿಗೆ ಸ್ವಯಂ-ಲೋಡ್ ಮಾಡಲಾಗಿದೆ.", + "setAsDefault": "ಡೀಫಾಲ್ಟ್ ಆಗಿ ಹೊಂದಿಸಿ", + "removeDefault": "ಡೀಫಾಲ್ಟ್ ಆಗಿ ತೆಗೆದುಹಾಕಿ", + "saveFailed": "ಪ್ರೊಫೈಲ್ ಅನ್ನು ಉಳಿಸಲು ಸಾಧ್ಯವಾಗಲಿಲ್ಲ", + "deleteTitle": "ಪ್ರೊಫೈಲ್ ಅಳಿಸಿ", + "deleteMessage": "ಇದು ಪ್ರೊಫೈಲ್ ಮತ್ತು ಅದರ ಸೆಟ್ಟಿಂಗ್‌ಗಳನ್ನು ಶಾಶ್ವತವಾಗಿ ತೆಗೆದುಹಾಕುತ್ತದೆ. ಇದನ್ನು ರದ್ದುಗೊಳಿಸಲಾಗುವುದಿಲ್ಲ.", + "deleteFailed": "ಪ್ರೊಫೈಲ್ ಅಳಿಸಲು ಸಾಧ್ಯವಾಗಲಿಲ್ಲ", + "deleteBlocked": "ಈ ಪ್ರೊಫೈಲ್ ಅನ್ನು ವೆಬ್‌ಹೂಕ್ ಪ್ರಚೋದಕದಿಂದ ಬಳಸಲಾಗಿದೆ. ಅದನ್ನು ಮೊದಲು ಆ ಟ್ರಿಗ್ಗರ್‌ಗಳಿಂದ ತೆಗೆದುಹಾಕಿ.", + "deletedToast": "ಪ್ರೊಫೈಲ್ \"{{name}}\" ಅಳಿಸಲಾಗಿದೆ", + "variablesTitle": "ಪರಿಸರ ಅಸ್ಥಿರ", + "addVariable": "ವೇರಿಯಬಲ್ ಸೇರಿಸಿ", + "keyLabel": "ಕೀ", + "keyTooLong": "ಕೀಲಿಯು 256 ಅಕ್ಷರಗಳು ಅಥವಾ ಕಡಿಮೆ ಇರಬೇಕು", + "keyDuplicate": "ಈ ಕೀಲಿಯು ಈಗಾಗಲೇ ಪ್ರೊಫೈಲ್ನಲ್ಲಿದೆ", + "valueLabel": "ಮೌಲ್ಯ", + "secretLabel": "ರಹಸ್ಯ", + "secrets": { + "markSecret": "ರಹಸ್ಯವೆಂದು ಗುರುತಿಸಿ", + "masked": "ಗುಪ್ತ ಮೌಲ್ಯ", + "reveal": "ಮೌಲ್ಯವನ್ನು ಬಹಿರಂಗಪಡಿಸಿ", + "newValuePlaceholder": "ಹೊಸ ರಹಸ್ಯ ಮೌಲ್ಯವನ್ನು ನಮೂದಿಸಿ", + "valuePlaceholder": "ರಹಸ್ಯ ಮೌಲ್ಯ (ಎನ್ಕ್ರಿಪ್ಟ್ ಮಾಡಲಾಗಿದೆ)" + }, + "variablesEmpty": "ಇನ್ನೂ ಯಾವುದೇ ಅಸ್ಥಿರಗಳಿಲ್ಲ", + "variablesSaveFailed": "ವೇರಿಯಬಲ್ ಅನ್ನು ಉಳಿಸಲು ಸಾಧ್ಯವಾಗಲಿಲ್ಲ", + "commandsTitle": "ಆಜ್ಞೆಗಳನ್ನು ಹೊಂದಿಸಿ", + "addCommand": "ಆಜ್ಞೆಯನ್ನು ಸೇರಿಸಿ", + "commandLabel": "ಆಜ್ಞೆ", + "commandPlaceholder": "e.g. pnpm install", + "commandsEmpty": "ಇನ್ನೂ ಯಾವುದೇ ಸೆಟಪ್ ಆದೇಶಗಳಿಲ್ಲ", + "commandsSaveFailed": "ಸೆಟಪ್ ಕಮಾಂಡ್‌ಗಳನ್ನು ಉಳಿಸಲು ಸಾಧ್ಯವಾಗಲಿಲ್ಲ", + "moveUp": "ಮೇಲಕ್ಕೆ ಸರಿಸಿ", + "moveDown": "ಕೆಳಗೆ ಸರಿಸಿ", + "skillsTitle": "ಕೌಶಲ್ಯಗಳು", + "addSkill": "ಕೌಶಲ್ಯವನ್ನು ಸೇರಿಸಿ", + "skillNameLabel": "ಕೌಶಲ್ಯದ ಹೆಸರು", + "skillMarkdownLabel": "ಕೌಶಲ್ಯ ವಿಷಯ", + "skillsEmpty": "ಇನ್ನೂ ಯಾವುದೇ ಕೌಶಲ್ಯಗಳಿಲ್ಲ", + "skillNameRequired": "ಕೌಶಲ್ಯದ ಹೆಸರನ್ನು ನಮೂದಿಸಿ", + "skillNameInvalid": "ಕೌಶಲ್ಯದ ಹೆಸರು ಅಕ್ಷರ ಅಥವಾ ಅಂಕೆಯೊಂದಿಗೆ ಪ್ರಾರಂಭವಾಗಬೇಕು ಮತ್ತು ಸಣ್ಣ ಅಕ್ಷರಗಳು, ಅಂಕೆಗಳು ಮತ್ತು ಡ್ಯಾಶ್‌ಗಳನ್ನು ಮಾತ್ರ ಬಳಸಬೇಕು", + "skillContentRequired": "ಕೌಶಲ್ಯ ವಿಷಯದ ಅಗತ್ಯವಿದೆ", + "skillSaveFailed": "ಕೌಶಲ್ಯವನ್ನು ಉಳಿಸಲು ಸಾಧ್ಯವಾಗಲಿಲ್ಲ", + "skillsHint": "SKILL.md ಅನ್ನು ಅಂಟಿಸಿ ಮತ್ತು ಅದರ ಹೆಸರು ತುಂಬುತ್ತದೆ; ಡ್ರ್ಯಾಗ್ ಮತ್ತು ಡ್ರಾಪ್ ಇಲ್ಲದೆ ನಿಮ್ಮ ಫೋನ್‌ನಿಂದ ಕೌಶಲ್ಯವನ್ನು ಸೇರಿಸಿ.", + "skillEditTitle": "ಕೌಶಲ್ಯ ಸಂಪಾದಿಸಿ", + "mcp": { + "title": "MCP ಸರ್ವರ್‌ಗಳು", + "add": "MCP ಸರ್ವರ್ ಸೇರಿಸಿ", + "empty": "ಇನ್ನೂ ಯಾವುದೇ MCP ಸರ್ವರ್‌ಗಳಿಲ್ಲ", + "name": "ಸರ್ವರ್ ಹೆಸರು", + "type": "ಸರ್ವರ್ ಪ್ರಕಾರ", + "localType": "ಸ್ಥಳೀಯ", + "remoteType": "ರಿಮೋಟ್", + "command": "ಕಮಾಂಡ್ ಲೈನ್", + "url": "URL", + "environment": "ಪರಿಸರ (JSON)", + "headers": "ಹೆಡರ್‌ಗಳು (JSON)", + "timeout": "ಸಮಯ ಮೀರಿದೆ", + "secretCount": "ರಹಸ್ಯ ಎಣಿಕೆ", + "namePlaceholder": "my-server", + "commandPlaceholder": "npx @example/mcp-server", + "jsonPlaceholder": "{\n \"API_KEY\": \"sk-...\"\n}", + "nameRequired": "ಸರ್ವರ್ ಹೆಸರನ್ನು ನಮೂದಿಸಿ", + "nameInvalid": "ಹೆಸರು ಅಕ್ಷರಗಳು, ಸಂಖ್ಯೆಗಳು, ಅಂಡರ್‌ಸ್ಕೋರ್‌ಗಳು ಮತ್ತು ಡ್ಯಾಶ್‌ಗಳನ್ನು ಮಾತ್ರ ಬಳಸಬಹುದು", + "commandRequired": "ಆಜ್ಞೆಯನ್ನು ನಮೂದಿಸಿ", + "commandTooLong": "ಗರಿಷ್ಠ 50 ಆರ್ಗ್ಯುಮೆಂಟ್ಗಳನ್ನು ಬಳಸಿ, ಪ್ರತಿಯೊಂದೂ 500 ಅಕ್ಷರಗಳು", + "urlRequired": "URL ಅನ್ನು ನಮೂದಿಸಿ", + "urlInvalid": "ಮಾನ್ಯವಾದ URL ಅನ್ನು ನಮೂದಿಸಿ", + "urlTooLong": "URL 2048 ಅಕ್ಷರಗಳು ಅಥವಾ ಕಡಿಮೆ ಇರಬೇಕು", + "jsonInvalid": "ಮಾನ್ಯ JSON ಅನ್ನು ನಮೂದಿಸಿ - ಸ್ಟ್ರಿಂಗ್ ಮೌಲ್ಯಗಳ ವಸ್ತು", + "jsonTooLarge": "ಗರಿಷ್ಠ 50 ನಮೂದುಗಳನ್ನು ಬಳಸಿ, ಕೀಲಿಗಳು 128 ರವರೆಗೆ ಮತ್ತು ಮೌಲ್ಯಗಳು 4096 ಅಕ್ಷರಗಳವರೆಗೆ", + "timeoutInvalid": "ಸಮಯ ಮೀರುವುದು ಮಿಲಿಸೆಕೆಂಡ್‌ಗಳ ಸಂಪೂರ್ಣ ಸಂಖ್ಯೆಯಾಗಿರಬೇಕು", + "edit": "MCP ಸರ್ವರ್ ಅನ್ನು ಸಂಪಾದಿಸಿ", + "saveFailed": "MCP ಸರ್ವರ್ ಅನ್ನು ಉಳಿಸಲು ಸಾಧ್ಯವಾಗಲಿಲ್ಲ" + }, + "agents": { + "title": "ಪ್ರೊಫೈಲ್ ಏಜೆಂಟ್", + "add": "ಏಜೆಂಟ್ ಸೇರಿಸಿ", + "empty": "ಇನ್ನೂ ಏಜೆಂಟ್‌ಗಳಿಲ್ಲ", + "name": "ಏಜೆಂಟ್ ಹೆಸರು", + "slug": "ಏಜೆಂಟ್ ಸ್ಲಗ್", + "description": "ಏಜೆಂಟ್ ವಿವರಣೆ", + "model": "ಮಾದರಿ ಅತಿಕ್ರಮಣ", + "modelPlaceholder": "ಸೆಶನ್ ಡೀಫಾಲ್ಟ್ ಬಳಸಿ", + "prompt": "ಸಿಸ್ಟಮ್ ಪ್ರಾಂಪ್ಟ್", + "steps": "ಗರಿಷ್ಠ ಹಂತಗಳು", + "stepsPlaceholder": "ಉದಾ. 50", + "temperature": "ತಾಪಮಾನ", + "temperaturePlaceholder": "ಉದಾ. 0.2", + "topP": "top_p", + "topPPlaceholder": "ಉದಾ. 0.95", + "slugPlaceholder": "my-agent", + "namePlaceholder": "ನನ್ನ ಏಜೆಂಟ್", + "promptPlaceholder": "ನೀವು ಒಂದು ...", + "tools": "ಪರಿಕರಗಳು", + "visibility": "ಗೋಚರತೆ", + "visibilityPrimary": "ಪ್ರಾಥಮಿಕ - ಪಿಕ್ಕರ್‌ನಲ್ಲಿ ತೋರಿಸಲಾಗಿದೆ", + "visibilitySubagent": "ಸಬ್ಜೆಂಟ್ - ಇತರರಿಂದ ಮಾತ್ರ ಕರೆಯಬಹುದು", + "visibilityAll": "ಎಲ್ಲಾ - ಎರಡೂ", + "saveFailed": "ಏಜೆಂಟ್ ಅನ್ನು ಉಳಿಸಲು ಸಾಧ್ಯವಾಗಲಿಲ್ಲ", + "nameRequired": "ಏಜೆಂಟ್ ಹೆಸರನ್ನು ನಮೂದಿಸಿ", + "slugRequired": "ಏಜೆಂಟ್ ಸ್ಲಗ್ ಅನ್ನು ನಮೂದಿಸಿ", + "slugInvalid": "ಸ್ಲಗ್ ಅಕ್ಷರದಿಂದ ಪ್ರಾರಂಭವಾಗಬೇಕು ಮತ್ತು ಸಣ್ಣ ಅಕ್ಷರಗಳು, ಅಂಕೆಗಳು ಮತ್ತು ಡ್ಯಾಶ್‌ಗಳನ್ನು ಮಾತ್ರ ಬಳಸಬೇಕು", + "slugConflict": "ಆ ಸ್ಲಗ್ ಅನ್ನು ಅಂತರ್ನಿರ್ಮಿತ ಏಜೆಂಟ್ ಮೂಲಕ ಕಾಯ್ದಿರಿಸಲಾಗಿದೆ", + "edit": "ಎಡಿಟ್ ಏಜೆಂಟ್" + }, + "slashCommands": { + "title": "ಸ್ಲಾಶ್ ಆಜ್ಞೆಗಳು", + "add": "ಸ್ಲಾಶ್ ಆಜ್ಞೆಯನ್ನು ಸೇರಿಸಿ", + "empty": "ಇನ್ನೂ ಯಾವುದೇ ಸ್ಲಾಶ್ ಆಜ್ಞೆಗಳಿಲ್ಲ", + "name": "ಆಜ್ಞೆಯ ಹೆಸರು", + "description": "ಆಜ್ಞೆಯ ವಿವರಣೆ", + "template": "ಟೆಂಪ್ಲೇಟ್", + "namePlaceholder": "explain", + "templatePlaceholder": "$ARGUMENTS ಅನ್ನು ವಿವರಿಸಿ", + "agent": "ಏಜೆಂಟ್", + "subtask": "ಉಪಕಾರ್ಯದಂತೆ ರನ್ ಮಾಡಿ", + "edit": "ಸ್ಲಾಶ್ ಆಜ್ಞೆಯನ್ನು ಸಂಪಾದಿಸಿ", + "saveFailed": "ಆಜ್ಞೆಯನ್ನು ಉಳಿಸಲು ಸಾಧ್ಯವಾಗಲಿಲ್ಲ", + "nameRequired": "ಆಜ್ಞೆಯ ಹೆಸರನ್ನು ನಮೂದಿಸಿ", + "nameInvalid": "ಹೆಸರು ಸಣ್ಣ ಅಕ್ಷರದಿಂದ ಪ್ರಾರಂಭವಾಗಬೇಕು ಮತ್ತು ಸಣ್ಣ ಅಕ್ಷರಗಳು, ಅಂಕೆಗಳು ಮತ್ತು ಡ್ಯಾಶ್‌ಗಳನ್ನು ಮಾತ್ರ ಬಳಸಬೇಕು", + "templateRequired": "ಕಮಾಂಡ್ ಟೆಂಪ್ಲೇಟ್ ಅನ್ನು ನಮೂದಿಸಿ", + "nameConflict": "ಆ ಹೆಸರನ್ನು ಅಂತರ್ನಿರ್ಮಿತ ಆಜ್ಞೆಯಿಂದ ಕಾಯ್ದಿರಿಸಲಾಗಿದೆ" + }, + "repoBindings": { + "title": "Repos ಗಾಗಿ ಡೀಫಾಲ್ಟ್ ಪ್ರೊಫೈಲ್‌ಗಳು", + "add": "ಡೀಫಾಲ್ಟ್ ಸೇರಿಸಿ", + "empty": "ಯಾವುದೇ ಡೀಫಾಲ್ಟ್‌ಗಳನ್ನು ಕಾನ್ಫಿಗರ್ ಮಾಡಲಾಗಿಲ್ಲ", + "emptyDescription": "ರೆಪೊಗಾಗಿ ಸೆಷನ್‌ಗಳನ್ನು ಪ್ರಾರಂಭಿಸುವಾಗ ಪ್ರೊಫೈಲ್ ಅನ್ನು ಸ್ವಯಂಚಾಲಿತವಾಗಿ ಅನ್ವಯಿಸಲು ಡೀಫಾಲ್ಟ್ ಸೇರಿಸಿ.", + "repo": "ರೆಪೊಸಿಟರಿಯನ್ನು ಆಯ್ಕೆಮಾಡಿ...", + "profile": "ಪ್ರೊಫೈಲ್ ಆಯ್ಕೆಮಾಡಿ", + "remove": "ಬಿಚ್ಚಿಡು", + "private": "ಖಾಸಗಿ", + "search": "ರೆಪೊಸಿಟರಿಗಳನ್ನು ಹುಡುಕಿ...", + "addedToast": "\"{{repo}}\" ಗಾಗಿ ಡೀಫಾಲ್ಟ್ ಪ್ರೊಫೈಲ್ ಸೆಟ್", + "addFailed": "ಡೀಫಾಲ್ಟ್ ಪ್ರೊಫೈಲ್ ಅನ್ನು ಹೊಂದಿಸಲು ಸಾಧ್ಯವಾಗಲಿಲ್ಲ", + "removedToast": "\"{{repo}}\" ಗಾಗಿ ಡೀಫಾಲ್ಟ್ ಪ್ರೊಫೈಲ್ ಅನ್ನು ತೆಗೆದುಹಾಕಲಾಗಿದೆ", + "removeFailed": "ಡೀಫಾಲ್ಟ್ ಪ್ರೊಫೈಲ್ ಅನ್ನು ತೆಗೆದುಹಾಕಲು ಸಾಧ್ಯವಾಗಲಿಲ್ಲ", + "pinnedTitle": "ರೆಪೊಸಿಟರಿಗಳಿಗೆ ಪಿನ್ ಮಾಡಲಾಗಿದೆ", + "pinRepo": "ರೆಪೋವನ್ನು ಪಿನ್ ಮಾಡಿ", + "pinnedEmpty": "ಯಾವುದೇ ರೆಪೊಸಿಟರಿಗಳಿಗೆ ಪಿನ್ ಮಾಡಲಾಗಿಲ್ಲ.", + "pinnedDescription": "ಸೆಷನ್ ಈ ರೆಪೊಸಿಟರಿಗಳಲ್ಲಿ ಒಂದನ್ನು ಗುರಿಪಡಿಸಿದಾಗ ಸ್ವಯಂ-ಲೋಡ್ ಆಗುತ್ತದೆ.", + "pinnedToast": "\"{{repo}}\" ಗೆ ಪಿನ್ ಮಾಡಲಾಗಿದೆ", + "pinFailed": "ರೆಪೋವನ್ನು ಪಿನ್ ಮಾಡಲು ಸಾಧ್ಯವಾಗಲಿಲ್ಲ", + "unboundToast": "ರೆಪೊ ಅನ್ಬೌಂಡ್", + "unbindFailed": "ರೆಪೊವನ್ನು ಅನ್‌ಬೈಂಡ್ ಮಾಡಲು ಸಾಧ್ಯವಾಗಲಿಲ್ಲ" + } } } diff --git a/apps/mobile/src/i18n/locales/ko.json b/apps/mobile/src/i18n/locales/ko.json index 2b0ac10ca0a..ec3b916f5d2 100644 --- a/apps/mobile/src/i18n/locales/ko.json +++ b/apps/mobile/src/i18n/locales/ko.json @@ -1687,7 +1687,33 @@ "continueFrom": "{{title}} 세션을 이어서 진행합니다.", "importingSession": "세션 가져오는 중", "cliCannotContinue": "이 CLI에서는 세션을 이어서 진행할 수 없습니다. Cloud Agent 또는 다른 CLI를 선택하세요.", - "importFailed": "이 세션을 가져올 수 없습니다." + "importFailed": "이 세션을 가져올 수 없습니다.", + "pickProfile": "프로필을 선택하세요", + "noProfile": "프로필 없음", + "manageProfiles": "프로필 관리", + "noProfiles": "아직 프로필이 없습니다. 환경 변수, MCP 서버 및 기술을 추가하려면 하나를 만드십시오.", + "advancedConfig": "고급 구성", + "advancedConfigDescription": "프로필을 선택하여 저장된 구성을 로드하고 추가 환경 변수 및 설정 명령을 수동으로 구성하세요.", + "saveAsProfile": "프로필로 저장", + "saveProfileDescription": "현재 환경 변수와 설정 명령을 재사용 가능한 프로필로 저장합니다.", + "profileSummary": "{{vars}} 환경 변수 · {{commands}} 설정 명령", + "profileSaved": "프로필 \"{{name}}\"이 저장되었습니다.", + "organizationProfiles": "조직 프로필", + "personalProfiles": "개인 프로필", + "yourProfiles": "귀하의 프로필", + "failedToLoadProfiles": "프로필을 로드하지 못했습니다.", + "nameTooLong": "프로필 이름이 너무 깁니다.", + "descriptionTooLong": "설명이 너무 깁니다.", + "secretsEncrypted": "비밀은 저장되기 전에 암호화됩니다.", + "profileActive": "프로필 활성", + "profilesActive": "프로필이 활성 상태입니다.", + "customConfigActive": "맞춤 구성이 활성화되었습니다.", + "configNeedsAttention": "구성에 주의가 필요함", + "repoProfile": "레포 프로필", + "selectedProfile": "선택한 프로필", + "manualOverrides": "수동 재정의", + "openSettingsToReview": "설정을 열어 검토하세요.", + "profileSelection": "프로필 선택" }, "partRenderer": { "updatedFileCount_one": "파일 {{displayCount}}개를 업데이트했습니다.", @@ -3224,5 +3250,180 @@ "multiplierError": "1 이상의 배수를 입력하세요", "permissionDenied": "지출 알림을 관리할 권한이 없습니다.", "pushOffByCategory": "알림에서 꺼짐" + }, + "profiles": { + "title": "프로필", + "entrySubtitle": "세션 전체에서 환경 설정 재사용", + "newProfile": "새 프로필", + "emptyTitle": "아직 프로필이 없습니다.", + "emptyDescription": "세션 전체에서 환경 변수, 설정 명령 및 기술을 재사용하기 위한 프로필을 만듭니다.", + "loadFailed": "프로필을 로드할 수 없습니다.", + "counts": { + "vars": "{{count}} 변수", + "mcp": "{{count}} MCP", + "skills": "{{count}} 스킬", + "commands": "{{count}} 명령", + "varsShort": "{{count}}변", + "skillsShort": "{{count}}스", + "commandsShort": "{{count}}명" + }, + "nameLabel": "프로필 이름", + "namePlaceholder": "예를 들어 백엔드 디버깅", + "descriptionLabel": "프로필 설명", + "descriptionPlaceholder": "선택적 설명", + "ownerLabel": "프로필 소유자", + "nameRequired": "프로필 이름을 입력하세요.", + "createAction": "프로필 만들기", + "createFailed": "프로필을 만들 수 없습니다.", + "createdToast": "프로필 \"{{name}}\"이(가) 생성되었습니다.", + "settingsSectionTitle": "구성", + "defaultSectionTitle": "기본 프로필", + "personalDefaultDescription": "고정된 프로필이 있는 저장소가 없으면 자동으로 로드됩니다.", + "organizationDefaultDescription": "개인 불이행이 없는 회원을 위해 자동으로 로드됩니다.", + "setAsDefault": "기본값으로 설정", + "removeDefault": "기본값으로 제거", + "saveFailed": "프로필을 저장할 수 없습니다.", + "deleteTitle": "프로필 삭제", + "deleteMessage": "이렇게 하면 프로필과 해당 설정이 영구적으로 제거됩니다. 이 작업은 취소할 수 없습니다.", + "deleteFailed": "프로필을 삭제할 수 없습니다.", + "deleteBlocked": "이 프로필은 웹훅 트리거에서 사용됩니다. 먼저 해당 트리거에서 제거하십시오.", + "deletedToast": "프로필 \"{{name}}\"이(가) 삭제되었습니다.", + "variablesTitle": "환경변수", + "addVariable": "변수 추가", + "keyLabel": "열쇠", + "keyTooLong": "키는 256자 이하여야 합니다", + "keyDuplicate": "이 키는 이미 프로필에 있습니다.", + "valueLabel": "값", + "secretLabel": "비밀", + "secrets": { + "markSecret": "비밀로 표시", + "masked": "숨겨진 가치", + "reveal": "가치 공개", + "newValuePlaceholder": "새 비밀 값 입력", + "valuePlaceholder": "비밀 값(암호화됨)" + }, + "variablesEmpty": "아직 변수가 없습니다.", + "variablesSaveFailed": "변수를 저장할 수 없습니다.", + "commandsTitle": "설정 명령", + "addCommand": "명령 추가", + "commandLabel": "명령", + "commandPlaceholder": "e.g. pnpm install", + "commandsEmpty": "아직 설정 명령이 없습니다.", + "commandsSaveFailed": "설정 명령을 저장할 수 없습니다.", + "moveUp": "위로 이동", + "moveDown": "아래로 이동", + "skillsTitle": "기술", + "addSkill": "스킬 추가", + "skillNameLabel": "스킬명", + "skillMarkdownLabel": "스킬 내용", + "skillsEmpty": "아직 기술이 없습니다", + "skillNameRequired": "스킬 이름을 입력하세요", + "skillNameInvalid": "스킬 이름은 문자 또는 숫자로 시작해야 하며 소문자, 숫자, 대시만 사용할 수 있습니다.", + "skillContentRequired": "스킬 내용이 필요합니다", + "skillSaveFailed": "스킬 저장이 안되네요", + "skillsHint": "SKILL.md를 붙여넣으면 이름이 채워집니다. 드래그 앤 드롭 없이 휴대폰에서 스킬을 추가하세요.", + "skillEditTitle": "스킬 편집", + "mcp": { + "title": "MCP 서버", + "add": "MCP 서버 추가", + "empty": "아직 MCP 서버가 없습니다.", + "name": "서버 이름", + "type": "서버 유형", + "localType": "현지의", + "remoteType": "원격", + "command": "명령줄", + "url": "URL", + "environment": "환경(JSON)", + "headers": "헤더(JSON)", + "timeout": "시간 초과", + "secretCount": "비밀 카운트", + "namePlaceholder": "my-server", + "commandPlaceholder": "npx @example/mcp-server", + "jsonPlaceholder": "{\n \"API_KEY\": \"sk-...\"\n}", + "nameRequired": "서버 이름을 입력하세요.", + "nameInvalid": "이름에는 문자, 숫자, 밑줄, 대시만 사용할 수 있습니다.", + "commandRequired": "명령을 입력하세요", + "commandTooLong": "최대 50개의 인수(각 500자)를 사용하세요.", + "urlRequired": "URL을 입력하세요", + "urlInvalid": "유효한 URL을 입력하세요.", + "urlTooLong": "URL은 2048자 이하여야 합니다.", + "jsonInvalid": "유효한 JSON을 입력하세요 - 문자열 값의 객체", + "jsonTooLarge": "최대 50개의 항목(키는 128자, 값은 4096자까지)을 사용하세요.", + "timeoutInvalid": "제한시간은 정수 밀리초여야 합니다.", + "edit": "MCP 서버 편집", + "saveFailed": "MCP 서버를 저장할 수 없습니다." + }, + "agents": { + "title": "프로필 에이전트", + "add": "상담원 추가", + "empty": "아직 상담원 없음", + "name": "대리인 이름", + "slug": "에이전트 슬러그", + "description": "에이전트 설명", + "model": "모델 재정의", + "modelPlaceholder": "세션 기본값 사용", + "prompt": "시스템 프롬프트", + "steps": "최대 단계", + "stepsPlaceholder": "예: 50", + "temperature": "온도", + "temperaturePlaceholder": "예: 0.2", + "topP": "top_p", + "topPPlaceholder": "예: 0.95", + "slugPlaceholder": "my-agent", + "namePlaceholder": "내 에이전트", + "promptPlaceholder": "당신은 ...", + "tools": "도구", + "visibility": "시계", + "visibilityPrimary": "기본 — 선택기에 표시됨", + "visibilitySubagent": "하위 에이전트 — 다른 사람에게서만 호출 가능", + "visibilityAll": "모두 — 둘 다", + "saveFailed": "에이전트를 저장할 수 없습니다.", + "nameRequired": "상담사 이름을 입력하세요.", + "slugRequired": "에이전트 슬러그를 입력하세요", + "slugInvalid": "슬러그는 문자로 시작해야 하며 소문자, 숫자, 대시만 사용해야 합니다.", + "slugConflict": "해당 슬러그는 내장 에이전트에 의해 예약되어 있습니다.", + "edit": "에이전트 편집" + }, + "slashCommands": { + "title": "슬래시 명령", + "add": "슬래시 명령 추가", + "empty": "아직 슬래시 명령이 없습니다.", + "name": "명령 이름", + "description": "명령 설명", + "template": "주형", + "namePlaceholder": "explain", + "templatePlaceholder": "$ARGUMENTS 설명", + "agent": "대리인", + "subtask": "하위 작업으로 실행", + "edit": "슬래시 명령 편집", + "saveFailed": "명령을 저장할 수 없습니다", + "nameRequired": "명령 이름을 입력하세요.", + "nameInvalid": "이름은 소문자로 시작해야 하며 소문자, 숫자, 대시만 사용할 수 있습니다.", + "templateRequired": "명령 템플릿을 입력하세요.", + "nameConflict": "해당 이름은 내장 명령에 의해 예약되어 있습니다." + }, + "repoBindings": { + "title": "저장소의 기본 프로필", + "add": "기본값 추가", + "empty": "구성된 기본값이 없습니다.", + "emptyDescription": "저장소에 대한 세션을 시작할 때 프로필을 자동으로 적용하려면 기본값을 추가하세요.", + "repo": "저장소 선택…", + "profile": "프로필 선택", + "remove": "바인딩 해제", + "private": "사적인", + "search": "저장소 검색…", + "addedToast": "\"{{repo}}\"에 대한 기본 프로필 세트", + "addFailed": "기본 프로필을 설정할 수 없습니다.", + "removedToast": "\"{{repo}}\"에 대한 기본 프로필이 제거되었습니다.", + "removeFailed": "기본 프로필을 삭제할 수 없습니다.", + "pinnedTitle": "저장소에 고정됨", + "pinRepo": "저장소 고정", + "pinnedEmpty": "어떤 저장소에도 고정되지 않았습니다.", + "pinnedDescription": "세션이 이러한 저장소 중 하나를 대상으로 할 때마다 자동으로 로드됩니다.", + "pinnedToast": "\"{{repo}}\"에 고정됨", + "pinFailed": "저장소를 고정할 수 없습니다.", + "unboundToast": "레포 언바운드", + "unbindFailed": "저장소를 바인딩 해제할 수 없습니다." + } } } diff --git a/apps/mobile/src/i18n/locales/lo.json b/apps/mobile/src/i18n/locales/lo.json index 1a8374051d2..0a7e6c851ff 100644 --- a/apps/mobile/src/i18n/locales/lo.json +++ b/apps/mobile/src/i18n/locales/lo.json @@ -2191,7 +2191,33 @@ "continueFrom": "ກຳລັງເຮັດວຽກຕໍ່ຈາກ {{title}}.", "importingSession": "ກຳລັງນຳເຂົ້າເຊດຊັນ", "cliCannotContinue": "CLI ນີ້ບໍ່ສາມາດເຮັດວຽກຕໍ່ຈາກເຊດຊັນໄດ້. ເລືອກ Cloud Agent ຫຼື CLI ອື່ນ.", - "importFailed": "ບໍ່ສາມາດນຳເຂົ້າເຊດຊັນນີ້ໄດ້." + "importFailed": "ບໍ່ສາມາດນຳເຂົ້າເຊດຊັນນີ້ໄດ້.", + "pickProfile": "ເລືອກໂປຣໄຟລ໌", + "noProfile": "ບໍ່ມີໂປຣໄຟລ໌", + "manageProfiles": "ຈັດການໂປຣໄຟລ໌", + "noProfiles": "ບໍ່ມີໂປຣໄຟລ໌ເທື່ອ. ສ້າງອັນໜຶ່ງເພື່ອເພີ່ມຕົວແປສະພາບແວດລ້ອມ, ເຊີບເວີ MCP ແລະທັກສະ.", + "advancedConfig": "ການຕັ້ງຄ່າຂັ້ນສູງ", + "advancedConfigDescription": "ເລືອກໂປຣໄຟລ໌ເພື່ອໂຫຼດການຕັ້ງຄ່າທີ່ບັນທຶກໄວ້, ແລະກຳນົດຄ່າຕົວແປສະພາບແວດລ້ອມເພີ່ມເຕີມ ແລະຄຳສັ່ງການຕັ້ງຄ່າດ້ວຍຕົນເອງ", + "saveAsProfile": "ບັນທຶກເປັນໂປຣໄຟລ໌", + "saveProfileDescription": "ບັນທຶກຕົວແປສະພາບແວດລ້ອມປັດຈຸບັນ ແລະຄໍາສັ່ງຕັ້ງຄ່າເປັນໂປຣໄຟລ໌ທີ່ສາມາດໃຊ້ຄືນໄດ້.", + "profileSummary": "{{vars}} ຕົວແປສະພາບແວດລ້ອມ · {{commands}} ຄຳສັ່ງຕັ້ງຄ່າ", + "profileSaved": "ບັນທຶກໂປຣໄຟລ໌ \"{{name}}\" ແລ້ວ", + "organizationProfiles": "ໂປຣໄຟລ໌ອົງການຈັດຕັ້ງ", + "personalProfiles": "ໂປຣໄຟລ໌ສ່ວນຕົວ", + "yourProfiles": "ໂປຣໄຟລ໌ຂອງເຈົ້າ", + "failedToLoadProfiles": "ໂຫຼດໂປຣໄຟລ໌ບໍ່ສຳເລັດ", + "nameTooLong": "ຊື່ໂປຣໄຟລ໌ຍາວເກີນໄປ", + "descriptionTooLong": "ຄຳອະທິບາຍຍາວເກີນໄປ", + "secretsEncrypted": "ຄວາມລັບຖືກເຂົ້າລະຫັດກ່ອນທີ່ຈະເກັບຮັກສາ.", + "profileActive": "ໂປຣໄຟລ໌ເປີດໃຊ້ຢູ່", + "profilesActive": "ໂປຣໄຟລ໌ເປີດໃຊ້ຢູ່", + "customConfigActive": "ການຕັ້ງຄ່າແບບກຳນົດເອງເປີດຢູ່", + "configNeedsAttention": "Config ຕ້ອງການຄວາມສົນໃຈ", + "repoProfile": "ໂປຣໄຟລ໌ Repo", + "selectedProfile": "ໂປຣໄຟລ໌ທີ່ເລືອກ", + "manualOverrides": "ການລົບລ້າງດ້ວຍມື", + "openSettingsToReview": "ເປີດການຕັ້ງຄ່າເພື່ອກວດສອບ", + "profileSelection": "ການເລືອກໂປຣໄຟລ໌" }, "partRenderer": { "updatedFileCount_one": "ອັບເດດໄຟລ໌ແລ້ວ {{displayCount}} ໄຟລ໌.", @@ -3224,5 +3250,180 @@ "multiplierError": "ໃສ່ຕົວຄູນ 1 ຫຼື ຫຼາຍກວ່າ", "permissionDenied": "ທ່ານບໍ່ມີສິດຈັດການການແຈ້ງເຕືອນການໃຊ້ຈ່າຍ.", "pushOffByCategory": "ປິດໃນການແຈ້ງເຕືອນ" + }, + "profiles": { + "title": "ໂປຣໄຟລ໌", + "entrySubtitle": "ໃຊ້ຄືນການຕັ້ງຄ່າສະພາບແວດລ້ອມໃນທົ່ວເຊດຊັນ", + "newProfile": "ໂປຣໄຟລ໌ໃໝ່", + "emptyTitle": "ບໍ່ມີໂປຣໄຟລ໌ເທື່ອ", + "emptyDescription": "ສ້າງໂປຣໄຟລ໌ເພື່ອນຳໃຊ້ຕົວແປສະພາບແວດລ້ອມຄືນໃໝ່, ຕັ້ງຄ່າຄຳສັ່ງ ແລະທັກສະຕ່າງໆໃນທົ່ວເຊດຊັນ.", + "loadFailed": "ບໍ່ສາມາດໂຫຼດໂປຣໄຟລ໌ໄດ້", + "counts": { + "vars": "{{count}} ຕົວແປ", + "mcp": "{{count}} MCP", + "skills": "{{count}} ທັກສະ", + "commands": "{{count}} ຄຳສັ່ງ", + "varsShort": "{{count}}ຕ", + "skillsShort": "{{count}}ທ", + "commandsShort": "{{count}}ຄ" + }, + "nameLabel": "ຊື່ໂປຣໄຟລ໌", + "namePlaceholder": "ຕົວຢ່າງ: ການດີບັກດ້ານຫຼັງ", + "descriptionLabel": "ລາຍລະອຽດໂປຣໄຟລ໌", + "descriptionPlaceholder": "ຄໍາອະທິບາຍທາງເລືອກ", + "ownerLabel": "ເຈົ້າຂອງໂປຣໄຟລ໌", + "nameRequired": "ໃສ່ຊື່ໂປຣໄຟລ໌", + "createAction": "ສ້າງໂປຣໄຟລ໌", + "createFailed": "ບໍ່ສາມາດສ້າງໂປຣໄຟລ໌ໄດ້", + "createdToast": "ສ້າງໂປຣໄຟລ໌ \"{{name}}\" ແລ້ວ", + "settingsSectionTitle": "ການຕັ້ງຄ່າ", + "defaultSectionTitle": "ໂປຣໄຟລ໌ເລີ່ມຕົ້ນ", + "personalDefaultDescription": "ໂຫຼດອັດຕະໂນມັດເມື່ອບໍ່ມີບ່ອນເກັບຂໍ້ມູນມີໂປຣໄຟລ໌ທີ່ປັກໝຸດໄວ້.", + "organizationDefaultDescription": "ໂຫຼດອັດຕະໂນມັດສຳລັບສະມາຊິກທີ່ບໍ່ມີຄ່າເລີ່ມຕົ້ນສ່ວນຕົວ.", + "setAsDefault": "ຕັ້ງເປັນຄ່າເລີ່ມຕົ້ນ", + "removeDefault": "ເອົາອອກເປັນຄ່າເລີ່ມຕົ້ນ", + "saveFailed": "ບໍ່ສາມາດບັນທຶກໂປຣໄຟລ໌ໄດ້", + "deleteTitle": "ລຶບໂປຣໄຟລ໌", + "deleteMessage": "ນີ້ຈະເປັນການລຶບໂປຣໄຟລ໌ ແລະການຕັ້ງຄ່າຂອງມັນອອກຢ່າງຖາວອນ. ອັນນີ້ບໍ່ສາມາດຍົກເລີກໄດ້.", + "deleteFailed": "ບໍ່ສາມາດລຶບໂປຣໄຟລ໌ໄດ້", + "deleteBlocked": "ໂປຣໄຟລ໌ນີ້ຖືກໃຊ້ໂດຍຕົວກະຕຸ້ນ webhook. ເອົາມັນອອກຈາກ triggers ເຫຼົ່ານັ້ນກ່ອນ.", + "deletedToast": "ລຶບໂປຣໄຟລ໌ \"{{name}}\" ແລ້ວ", + "variablesTitle": "ຕົວແປສະພາບແວດລ້ອມ", + "addVariable": "ເພີ່ມຕົວແປ", + "keyLabel": "ກະແຈ", + "keyTooLong": "ກະແຈຕ້ອງມີ 256 ຕົວອັກສອນ ຫຼື ໜ້ອຍກວ່າ", + "keyDuplicate": "ກະແຈນີ້ມີຢູ່ໃນໂປຣໄຟລ໌ແລ້ວ", + "valueLabel": "ມູນຄ່າ", + "secretLabel": "ຄວາມລັບ", + "secrets": { + "markSecret": "ໝາຍເປັນຄວາມລັບ", + "masked": "ຄ່າທີ່ເຊື່ອງໄວ້", + "reveal": "ເປີດເຜີຍມູນຄ່າ", + "newValuePlaceholder": "ປ້ອນມູນຄ່າຄວາມລັບໃໝ່", + "valuePlaceholder": "ມູນຄ່າຄວາມລັບ (ເຂົ້າລະຫັດແລ້ວ)" + }, + "variablesEmpty": "ບໍ່ມີຕົວແປເທື່ອ", + "variablesSaveFailed": "ບໍ່ສາມາດບັນທຶກຕົວແປໄດ້", + "commandsTitle": "ຕັ້ງຄ່າຄໍາສັ່ງ", + "addCommand": "ເພີ່ມຄໍາສັ່ງ", + "commandLabel": "ຄໍາສັ່ງ", + "commandPlaceholder": "e.g. pnpm install", + "commandsEmpty": "ບໍ່ມີຄຳສັ່ງຕັ້ງຄ່າເທື່ອ", + "commandsSaveFailed": "ບໍ່ສາມາດບັນທຶກຄຳສັ່ງຕັ້ງຄ່າໄດ້", + "moveUp": "ຍ້າຍຂຶ້ນ", + "moveDown": "ຍ້າຍລົງ", + "skillsTitle": "ທັກສະ", + "addSkill": "ເພີ່ມທັກສະ", + "skillNameLabel": "ຊື່ທັກສະ", + "skillMarkdownLabel": "ເນື້ອໃນທັກສະ", + "skillsEmpty": "ບໍ່ມີທັກສະເທື່ອ", + "skillNameRequired": "ໃສ່ຊື່ທັກສະ", + "skillNameInvalid": "ຊື່ທັກສະຕ້ອງເລີ່ມຕົ້ນດ້ວຍຕົວອັກສອນ ຫຼືຕົວເລກ ແລະໃຊ້ແຕ່ຕົວພິມນ້ອຍ, ຕົວເລກ, ແລະເຄື່ອງໝາຍຂີດ", + "skillContentRequired": "ເນື້ອຫາທັກສະແມ່ນຕ້ອງການ", + "skillSaveFailed": "ບໍ່ສາມາດບັນທຶກທັກສະໄດ້", + "skillsHint": "ວາງ SKILL.md ແລະຊື່ຂອງມັນຕື່ມໃສ່; ເພີ່ມທັກສະຈາກໂທລະສັບຂອງທ່ານໂດຍບໍ່ຕ້ອງລາກແລະວາງ.", + "skillEditTitle": "ແກ້ໄຂທັກສະ", + "mcp": { + "title": "ເຊີບເວີ MCP", + "add": "ເພີ່ມເຊີບເວີ MCP", + "empty": "ບໍ່ມີເຊີບເວີ MCP ເທື່ອ", + "name": "ຊື່ເຊີບເວີ", + "type": "ປະເພດເຊີບເວີ", + "localType": "ທ້ອງຖິ່ນ", + "remoteType": "ໄລຍະໄກ", + "command": "ເສັ້ນຄໍາສັ່ງ", + "url": "URL", + "environment": "ສະພາບແວດລ້ອມ (JSON)", + "headers": "ສ່ວນຫົວ (JSON)", + "timeout": "ໝົດເວລາ", + "secretCount": "ການນັບລັບ", + "namePlaceholder": "my-server", + "commandPlaceholder": "npx @example/mcp-server", + "jsonPlaceholder": "{\n \"API_KEY\": \"sk-...\"\n}", + "nameRequired": "ໃສ່ຊື່ເຊີບເວີ", + "nameInvalid": "ຊື່ສາມາດໃຊ້ພຽງແຕ່ຕົວອັກສອນ, ຕົວເລກ, ຂີດກ້ອງ, ແລະເຄື່ອງໝາຍຂີດ", + "commandRequired": "ໃສ່ຄໍາສັ່ງ", + "commandTooLong": "ໃຊ້ອາກູເມັນສູງສຸດ 50 ອັນ ອັນລະ 500 ຕົວອັກສອນ", + "urlRequired": "ໃສ່ URL", + "urlInvalid": "ໃສ່ URL ທີ່ຖືກຕ້ອງ", + "urlTooLong": "URL ຕ້ອງມີ 2048 ຕົວອັກສອນ ຫຼື ໜ້ອຍກວ່າ", + "jsonInvalid": "ໃສ່ JSON ທີ່ຖືກຕ້ອງ — ວັດຖຸຂອງຄ່າສະຕຣິງ", + "jsonTooLarge": "ໃຊ້ລາຍການສູງສຸດ 50 ລາຍການ ກະແຈສູງສຸດ 128 ແລະ ຄ່າສູງສຸດ 4096 ຕົວອັກສອນ", + "timeoutInvalid": "ໝົດເວລາຕ້ອງເປັນຈຳນວນທັງໝົດຂອງມິນລີວິນາທີ", + "edit": "ແກ້ໄຂເຊີບເວີ MCP", + "saveFailed": "ບໍ່ສາມາດບັນທຶກເຊີບເວີ MCP ໄດ້" + }, + "agents": { + "title": "ຕົວແທນໂປຣໄຟລ໌", + "add": "ເພີ່ມຕົວແທນ", + "empty": "ບໍ່ມີຕົວແທນເທື່ອ", + "name": "ຊື່ຕົວແທນ", + "slug": "ຕົວແທນ slug", + "description": "ລາຍລະອຽດຕົວແທນ", + "model": "ຍົກເລີກຕົວແບບ", + "modelPlaceholder": "ໃຊ້ຄ່າເລີ່ມຕົ້ນຂອງເຊດຊັນ", + "prompt": "ການເຕືອນລະບົບ", + "steps": "ຂັ້ນຕອນສູງສຸດ", + "stepsPlaceholder": "ຕົວຢ່າງ: 50", + "temperature": "ອຸນຫະພູມ", + "temperaturePlaceholder": "ຕົວຢ່າງ: 0.2", + "topP": "top_p", + "topPPlaceholder": "ຕົວຢ່າງ: 0.95", + "slugPlaceholder": "my-agent", + "namePlaceholder": "ຕົວແທນຂອງຂ້ອຍ", + "promptPlaceholder": "ເຈົ້າເປັນ...", + "tools": "ເຄື່ອງມື", + "visibility": "ການເບິ່ງເຫັນ", + "visibilityPrimary": "ປະຖົມ — ສະແດງໃຫ້ເຫັນຢູ່ໃນຕົວເລືອກ", + "visibilitySubagent": "Subagent — ພຽງແຕ່ສາມາດໂທໄດ້ຈາກຄົນອື່ນ", + "visibilityAll": "ທັງຫມົດ - ທັງຫມົດ", + "saveFailed": "ບໍ່ສາມາດບັນທຶກຕົວແທນໄດ້", + "nameRequired": "ໃສ່ຊື່ຕົວແທນ", + "slugRequired": "ໃສ່ຕົວແທນ slug", + "slugInvalid": "Slug ຈະຕ້ອງເລີ່ມຕົ້ນດ້ວຍຕົວອັກສອນ ແລະໃຊ້ແຕ່ຕົວພິມນ້ອຍ, ຕົວເລກ, ແລະ dashes", + "slugConflict": "slug ນັ້ນຖືກສະຫງວນໄວ້ໂດຍຕົວແທນທີ່ສ້າງຂຶ້ນ", + "edit": "ແກ້ໄຂຕົວແທນ" + }, + "slashCommands": { + "title": "ຄຳສັ່ງ Slash", + "add": "ເພີ່ມຄໍາສັ່ງ slash", + "empty": "ຍັງບໍ່ມີຄຳສັ່ງສະໄລ້ເທື່ອ", + "name": "ຊື່ຄໍາສັ່ງ", + "description": "ຄໍາອະທິບາຍຄໍາສັ່ງ", + "template": "ແມ່ແບບ", + "namePlaceholder": "explain", + "templatePlaceholder": "ອະທິບາຍ $ARGUMENTS", + "agent": "ຕົວແທນ", + "subtask": "ດໍາເນີນການເປັນວຽກຍ່ອຍ", + "edit": "ແກ້ໄຂຄໍາສັ່ງ slash", + "saveFailed": "ບໍ່ສາມາດບັນທຶກຄຳສັ່ງໄດ້", + "nameRequired": "ໃສ່ຊື່ຄໍາສັ່ງ", + "nameInvalid": "ຊື່ຕ້ອງຂຶ້ນຕົ້ນດ້ວຍຕົວພິມນ້ອຍ ແລະ ໃຊ້ແຕ່ຕົວພິມນ້ອຍ, ຕົວເລກ ແລະ ຂີດ", + "templateRequired": "ໃສ່ແມ່ແບບຄໍາສັ່ງ", + "nameConflict": "ຊື່ນັ້ນຖືກສະຫງວນໄວ້ໂດຍຄໍາສັ່ງໃນຕົວ" + }, + "repoBindings": { + "title": "ໂປຣໄຟລ໌ເລີ່ມຕົ້ນສຳລັບ Repos", + "add": "ເພີ່ມຄ່າເລີ່ມຕົ້ນ", + "empty": "ບໍ່ມີຄ່າເລີ່ມຕົ້ນທີ່ຖືກຕັ້ງຄ່າ", + "emptyDescription": "ເພີ່ມຄ່າເລີ່ມຕົ້ນເພື່ອນຳໃຊ້ໂປຣໄຟລ໌ໂດຍອັດຕະໂນມັດເມື່ອເລີ່ມເຊດຊັນສຳລັບ repo.", + "repo": "ເລືອກບ່ອນເກັບມ້ຽນ...", + "profile": "ເລືອກໂປຣໄຟລ໌", + "remove": "ຍົກເລີກການຜູກມັດ", + "private": "ສ່ວນຕົວ", + "search": "ຊອກຫາບ່ອນເກັບມ້ຽນ...", + "addedToast": "ຕັ້ງໂປຣໄຟລ໌ເລີ່ມຕົ້ນສຳລັບ \"{{repo}}\" ແລ້ວ", + "addFailed": "ບໍ່ສາມາດຕັ້ງໂປຣໄຟລ໌ເລີ່ມຕົ້ນໄດ້", + "removedToast": "ລຶບໂປຣໄຟລ໌ເລີ່ມຕົ້ນສຳລັບ \"{{repo}}\" ແລ້ວ", + "removeFailed": "ບໍ່ສາມາດລຶບໂປຣໄຟລ໌ເລີ່ມຕົ້ນໄດ້", + "pinnedTitle": "ປັກໝຸດໃສ່ບ່ອນເກັບມ້ຽນ", + "pinRepo": "ປັກໝຸດ repo", + "pinnedEmpty": "ບໍ່ໄດ້ປັກໝຸດໃສ່ບ່ອນເກັບມ້ຽນໃດໆ.", + "pinnedDescription": "ໂຫຼດອັດຕະໂນມັດທຸກຄັ້ງທີ່ເຊດຊັນຕັ້ງເປົ້າໝາຍໃສ່ໜຶ່ງໃນບ່ອນເກັບມ້ຽນເຫຼົ່ານີ້.", + "pinnedToast": "ປັກໝຸດໃສ່ \"{{repo}}\" ແລ້ວ", + "pinFailed": "ບໍ່ສາມາດປັກໝຸດ repo ໄດ້", + "unboundToast": "ຍົກເລີກການຜູກມັດ repo ແລ້ວ", + "unbindFailed": "ບໍ່ສາມາດຍົກເລີກການຜູກມັດ repo ໄດ້" + } } } diff --git a/apps/mobile/src/i18n/locales/lt.json b/apps/mobile/src/i18n/locales/lt.json index e3bfd358855..2233a973f38 100644 --- a/apps/mobile/src/i18n/locales/lt.json +++ b/apps/mobile/src/i18n/locales/lt.json @@ -2247,7 +2247,33 @@ "continueFrom": "Tęsiama sesija „{{title}}“.", "importingSession": "Importuojama sesija", "cliCannotContinue": "Šioje CLI sesijos tęsti negalima. Pasirinkite „Cloud Agent“ arba kitą CLI.", - "importFailed": "Nepavyko importuoti šios sesijos." + "importFailed": "Nepavyko importuoti šios sesijos.", + "pickProfile": "Pasirinkite profilį", + "noProfile": "Nėra profilio", + "manageProfiles": "Tvarkyti profilius", + "noProfiles": "Dar nėra profilių. Sukurkite vieną, kad pridėtumėte aplinkos kintamuosius, MCP serverius ir įgūdžius.", + "advancedConfig": "Išplėstinė konfigūracija", + "advancedConfigDescription": "Pasirinkite profilį, kad įkeltumėte išsaugotas konfigūracijas, ir rankiniu būdu sukonfigūruokite papildomus aplinkos kintamuosius bei sąrankos komandas", + "saveAsProfile": "Išsaugoti kaip profilį", + "saveProfileDescription": "Išsaugokite esamus aplinkos kintamuosius ir sąrankos komandas kaip pakartotinai naudojamą profilį.", + "profileSummary": "{{vars}} aplinkos kintamieji · {{commands}} sąrankos komandos", + "profileSaved": "Profilis „{{name}}“ išsaugotas", + "organizationProfiles": "Organizacijos profiliai", + "personalProfiles": "Asmeniniai profiliai", + "yourProfiles": "Jūsų profiliai", + "failedToLoadProfiles": "Nepavyko įkelti profilių", + "nameTooLong": "Profilio pavadinimas per ilgas", + "descriptionTooLong": "Aprašymas per ilgas", + "secretsEncrypted": "Paslaptys užšifruojamos prieš išsaugant.", + "profileActive": "Profilis aktyvus", + "profilesActive": "Profiliai aktyvūs", + "customConfigActive": "Pasirinktinė konfigūracija aktyvi", + "configNeedsAttention": "Konfigūracijai reikia dėmesio", + "repoProfile": "Repo profilis", + "selectedProfile": "Pasirinktas profilis", + "manualOverrides": "Rankiniai nepaisymai", + "openSettingsToReview": "Norėdami peržiūrėti, atidarykite nustatymus", + "profileSelection": "Profilio pasirinkimas" }, "partRenderer": { "updatedFileCount_one": "Atnaujintas {{displayCount}} failas.", @@ -3310,5 +3336,180 @@ "multiplierError": "Įvesk daugiklį, lygų 1 arba didesnį", "permissionDenied": "Neturite leidimo valdyti išlaidų įspėjimų.", "pushOffByCategory": "Išjungta pranešimuose" + }, + "profiles": { + "title": "Profiliai", + "entrySubtitle": "Pakartotinai naudokite aplinkos nustatymus per seansus", + "newProfile": "Naujas profilis", + "emptyTitle": "Dar nėra profilių", + "emptyDescription": "Sukurkite profilį, kad seansų metu pakartotinai naudotumėte aplinkos kintamuosius, sąrankos komandas ir įgūdžius.", + "loadFailed": "Nepavyko įkelti profilių", + "counts": { + "vars": "{{count}} kintamųjų", + "mcp": "{{count}} MCP", + "skills": "{{count}} įgūdžių", + "commands": "{{count}} komandų", + "varsShort": "{{count}}k", + "skillsShort": "{{count}}į", + "commandsShort": "{{count}}kom" + }, + "nameLabel": "Profilio pavadinimas", + "namePlaceholder": "pvz. Backend derinimas", + "descriptionLabel": "Profilio aprašymas", + "descriptionPlaceholder": "Neprivalomas aprašymas", + "ownerLabel": "Profilio savininkas", + "nameRequired": "Įveskite profilio pavadinimą", + "createAction": "Sukurti profilį", + "createFailed": "Nepavyko sukurti profilio", + "createdToast": "Profilis „{{name}}“ sukurtas", + "settingsSectionTitle": "Konfigūracija", + "defaultSectionTitle": "Numatytasis profilis", + "personalDefaultDescription": "Automatiškai įkeliama, kai jokia saugykla neturi prisegto profilio.", + "organizationDefaultDescription": "Automatiškai įkeliama nariams, kurie neturi asmeninių įsipareigojimų.", + "setAsDefault": "Nustatyti kaip numatytąjį", + "removeDefault": "Pašalinti kaip numatytąjį", + "saveFailed": "Nepavyko išsaugoti profilio", + "deleteTitle": "Ištrinti profilį", + "deleteMessage": "Tai visam laikui pašalins profilį ir jo nustatymus. To negalima anuliuoti.", + "deleteFailed": "Nepavyko ištrinti profilio", + "deleteBlocked": "Šį profilį naudoja „Webhook“ aktyviklis. Pirmiausia pašalinkite jį iš tų trigerių.", + "deletedToast": "Profilis „{{name}}“ ištrintas", + "variablesTitle": "Aplinkos kintamieji", + "addVariable": "Pridėti kintamąjį", + "keyLabel": "Raktas", + "keyTooLong": "Raktas gali būti ne ilgesnis kaip 256 simboliai", + "keyDuplicate": "Šis raktas jau yra profilyje", + "valueLabel": "Vertė", + "secretLabel": "Paslaptis", + "secrets": { + "markSecret": "Pažymėti kaip paslaptį", + "masked": "Paslėpta vertė", + "reveal": "Atskleiskite vertę", + "newValuePlaceholder": "Įveskite naują slaptą reikšmę", + "valuePlaceholder": "Slapta reikšmė (užšifruota)" + }, + "variablesEmpty": "Dar nėra kintamųjų", + "variablesSaveFailed": "Nepavyko išsaugoti kintamojo", + "commandsTitle": "Sąrankos komandos", + "addCommand": "Pridėti komandą", + "commandLabel": "komandą", + "commandPlaceholder": "e.g. pnpm install", + "commandsEmpty": "Dar nėra sąrankos komandų", + "commandsSaveFailed": "Nepavyko išsaugoti sąrankos komandų", + "moveUp": "Judėti aukštyn", + "moveDown": "Judėti žemyn", + "skillsTitle": "Įgūdžiai", + "addSkill": "Pridėkite įgūdžių", + "skillNameLabel": "Įgūdžio pavadinimas", + "skillMarkdownLabel": "Įgūdžių turinys", + "skillsEmpty": "Įgūdžių dar nėra", + "skillNameRequired": "Įveskite įgūdžių pavadinimą", + "skillNameInvalid": "Įgūdžio pavadinimas turi prasidėti raide arba skaitmeniu ir naudoti tik mažąsias raides, skaitmenis ir brūkšnelius", + "skillContentRequired": "Reikalingas įgūdžių turinys", + "skillSaveFailed": "Nepavyko išsaugoti įgūdžių", + "skillsHint": "Įklijuokite SKILL.md ir užpildysite jo pavadinimą; pridėkite įgūdžių iš savo telefono nevilkdami ir numesdami.", + "skillEditTitle": "Redaguoti įgūdžius", + "mcp": { + "title": "MCP serveriai", + "add": "Pridėti MCP serverį", + "empty": "Dar nėra MCP serverių", + "name": "Serverio pavadinimas", + "type": "Serverio tipas", + "localType": "Vietinis", + "remoteType": "Nuotolinis", + "command": "Komandinė eilutė", + "url": "URL", + "environment": "Aplinka (JSON)", + "headers": "Antraštės (JSON)", + "timeout": "Skirtasis laikas", + "secretCount": "Slaptas skaičius", + "namePlaceholder": "my-server", + "commandPlaceholder": "npx @example/mcp-server", + "jsonPlaceholder": "{\n \"API_KEY\": \"sk-...\"\n}", + "nameRequired": "Įveskite serverio pavadinimą", + "nameInvalid": "Varde gali būti naudojamos tik raidės, skaičiai, apatiniai brūkšniai ir brūkšniai", + "commandRequired": "Įveskite komandą", + "commandTooLong": "Naudokite ne daugiau kaip 50 argumentų, kurių kiekvienas 500 simbolių", + "urlRequired": "Įveskite URL", + "urlInvalid": "Įveskite tinkamą URL", + "urlTooLong": "URL gali būti ne ilgesnis kaip 2048 simboliai", + "jsonInvalid": "Įveskite galiojantį JSON – eilutės reikšmių objektą", + "jsonTooLarge": "Naudokite ne daugiau kaip 50 įrašų, raktai iki 128 ir reikšmės iki 4096 simbolių", + "timeoutInvalid": "Skirtasis laikas turi būti sveikas milisekundžių skaičius", + "edit": "Redaguoti MCP serverį", + "saveFailed": "Nepavyko išsaugoti MCP serverio" + }, + "agents": { + "title": "Profilio agentai", + "add": "Pridėti agentą", + "empty": "Agentų dar nėra", + "name": "Agento vardas", + "slug": "Agentinis šliužas", + "description": "Agento aprašymas", + "model": "Modelio nepaisymas", + "modelPlaceholder": "Naudoti numatytuosius seansus", + "prompt": "Sistemos raginimas", + "steps": "Maks. žingsniai", + "stepsPlaceholder": "pvz. 50", + "temperature": "Temperatūra", + "temperaturePlaceholder": "pvz. 0.2", + "topP": "top_p", + "topPPlaceholder": "pvz. 0.95", + "slugPlaceholder": "my-agent", + "namePlaceholder": "Mano agentas", + "promptPlaceholder": "Tu esi...", + "tools": "Įrankiai", + "visibility": "Matomumas", + "visibilityPrimary": "Pagrindinis – rodomas rinkiklyje", + "visibilitySubagent": "Subagentas – skambinama tik iš kitų", + "visibilityAll": "Visi - abu", + "saveFailed": "Nepavyko išsaugoti agento", + "nameRequired": "Įveskite agento pavadinimą", + "slugRequired": "Įveskite agento šliužą", + "slugInvalid": "Slug turi prasidėti raide ir naudoti tik mažąsias raides, skaitmenis ir brūkšnius", + "slugConflict": "Tą šliužą rezervuoja įmontuotas agentas", + "edit": "Redaguoti agentą" + }, + "slashCommands": { + "title": "Pasvirosios komandos", + "add": "Pridėti pasvirojo brūkšnio komandą", + "empty": "Dar nėra pasvirojo brūkšnio komandų", + "name": "Komandos pavadinimas", + "description": "Komandos aprašymas", + "template": "Šablonas", + "namePlaceholder": "explain", + "templatePlaceholder": "Paaiškinkite $ARGUMENTS", + "agent": "Agentas", + "subtask": "Vykdyti kaip papildomą užduotį", + "edit": "Redaguoti pasvirojo brūkšnio komandą", + "saveFailed": "Nepavyko išsaugoti komandos", + "nameRequired": "Įveskite komandos pavadinimą", + "nameInvalid": "Pavadinimas turi prasidėti mažosiomis raidėmis ir naudoti tik mažąsias raides, skaitmenis ir brūkšnius", + "templateRequired": "Įveskite komandos šabloną", + "nameConflict": "Šį pavadinimą rezervuoja įmontuota komanda" + }, + "repoBindings": { + "title": "Numatytieji atpirkimo sandorių profiliai", + "add": "Pridėti numatytąjį", + "empty": "Jokių numatytųjų sukonfigūruotų", + "emptyDescription": "Pridėkite numatytąjį nustatymą, kad profilis būtų automatiškai taikomas pradedant atpirkimo seansus.", + "repo": "Pasirinkite saugyklą…", + "profile": "Pasirinkite profilį", + "remove": "Atsieti", + "private": "Privatus", + "search": "Ieškoti saugyklų…", + "addedToast": "Numatytasis profilis nustatytas „{{repo}}“", + "addFailed": "Nepavyko nustatyti numatytojo profilio", + "removedToast": "Numatytasis „{{repo}}“ profilis pašalintas", + "removeFailed": "Nepavyko pašalinti numatytojo profilio", + "pinnedTitle": "Prisegta prie saugyklų", + "pinRepo": "Prisegti atpirkimo sandorį", + "pinnedEmpty": "Neprisegta prie jokių saugyklų.", + "pinnedDescription": "Automatiškai įkeliama, kai seansas nukreipiamas į vieną iš šių saugyklų.", + "pinnedToast": "Prisegta prie „{{repo}}“", + "pinFailed": "Nepavyko prisegti atpirkimo sandorio", + "unboundToast": "Atpirkimas neapribotas", + "unbindFailed": "Nepavyko atsieti atpirkimo sandorio" + } } } diff --git a/apps/mobile/src/i18n/locales/lv.json b/apps/mobile/src/i18n/locales/lv.json index abf779e3147..b2d409c335a 100644 --- a/apps/mobile/src/i18n/locales/lv.json +++ b/apps/mobile/src/i18n/locales/lv.json @@ -2219,7 +2219,33 @@ "continueFrom": "Tiek turpināta sesija: {{title}}.", "importingSession": "Importē sesiju…", "cliCannotContinue": "Šis CLI nevar turpināt sesiju. Izvēlies Cloud Agent vai citu CLI.", - "importFailed": "Neizdevās importēt šo sesiju." + "importFailed": "Neizdevās importēt šo sesiju.", + "pickProfile": "Izvēlieties profilu", + "noProfile": "Nav profila", + "manageProfiles": "Pārvaldīt profilus", + "noProfiles": "Vēl nav neviena profila. Izveidojiet vienu, lai pievienotu vides mainīgos, MCP serverus un prasmes.", + "advancedConfig": "Papildu konfigurācija", + "advancedConfigDescription": "Atlasiet profilu, lai ielādētu saglabātās konfigurācijas, un manuāli konfigurējiet papildu vides mainīgos un iestatīšanas komandas", + "saveAsProfile": "Saglabāt kā profilu", + "saveProfileDescription": "Saglabājiet pašreizējos vides mainīgos un iestatīšanas komandas kā atkārtoti lietojamu profilu.", + "profileSummary": "{{vars}} vides mainīgie · {{commands}} iestatīšanas komandas", + "profileSaved": "Profils \"{{name}}\" ir saglabāts", + "organizationProfiles": "Organizācijas profili", + "personalProfiles": "Personiskie profili", + "yourProfiles": "Jūsu profili", + "failedToLoadProfiles": "Neizdevās ielādēt profilus", + "nameTooLong": "Profila nosaukums ir pārāk garš", + "descriptionTooLong": "Apraksts ir pārāk garš", + "secretsEncrypted": "Pirms uzglabāšanas noslēpumi tiek šifrēti.", + "profileActive": "Profils aktīvs", + "profilesActive": "Profili ir aktīvi", + "customConfigActive": "Pielāgota konfigurācija ir aktīva", + "configNeedsAttention": "Konfigurācijai jāpievērš uzmanība", + "repoProfile": "Repo profils", + "selectedProfile": "Izvēlētais profils", + "manualOverrides": "Manuāla ignorēšana", + "openSettingsToReview": "Lai pārskatītu, atveriet iestatījumus", + "profileSelection": "Profila izvēle" }, "partRenderer": { "updatedFileCount_one": "Atjaunināts {{displayCount}} fails.", @@ -3267,5 +3293,180 @@ "multiplierError": "Ievadi reizinātāju, kas ir 1 vai lielāks", "permissionDenied": "Tev nav atļaujas pārvaldīt izdevumu brīdinājumus.", "pushOffByCategory": "Izslēgts paziņojumos" + }, + "profiles": { + "title": "Profili", + "entrySubtitle": "Atkārtoti izmantojiet vides iestatījumus sesijās", + "newProfile": "Jauns profils", + "emptyTitle": "Vēl nav neviena profila", + "emptyDescription": "Izveidojiet profilu, lai sesijās atkārtoti izmantotu vides mainīgos, iestatīšanas komandas un prasmes.", + "loadFailed": "Nevarēja ielādēt profilus", + "counts": { + "vars": "{{count}} mainīgo", + "mcp": "{{count}} MCP", + "skills": "{{count}} prasmju", + "commands": "{{count}} komandu", + "varsShort": "{{count}}m", + "skillsShort": "{{count}}p", + "commandsShort": "{{count}}k" + }, + "nameLabel": "Profila nosaukums", + "namePlaceholder": "piem. Aizmugursistēmas atkļūdošana", + "descriptionLabel": "Profila apraksts", + "descriptionPlaceholder": "Neobligāts apraksts", + "ownerLabel": "Profila īpašnieks", + "nameRequired": "Ievadiet profila nosaukumu", + "createAction": "Izveidot profilu", + "createFailed": "Nevarēja izveidot profilu", + "createdToast": "Profils \"{{name}}\" izveidots", + "settingsSectionTitle": "Konfigurācija", + "defaultSectionTitle": "Noklusējuma profils", + "personalDefaultDescription": "Automātiski ielādēts, ja nevienai krātuvei nav piesprausta profila.", + "organizationDefaultDescription": "Automātiski ielādēts dalībniekiem, kuriem nav personisku noklusējuma.", + "setAsDefault": "Iestatīt kā noklusējumu", + "removeDefault": "Noņemt kā noklusējumu", + "saveFailed": "Nevarēja saglabāt profilu", + "deleteTitle": "Dzēst profilu", + "deleteMessage": "Tādējādi profils un tā iestatījumi tiek neatgriezeniski noņemti. Šo darbību nevar atsaukt.", + "deleteFailed": "Nevarēja izdzēst profilu", + "deleteBlocked": "Šo profilu izmanto tīmekļa aizķeres aktivizētājs. Vispirms noņemiet to no šiem aktivizētājiem.", + "deletedToast": "Profils \"{{name}}\" ir izdzēsts", + "variablesTitle": "Vides mainīgie", + "addVariable": "Pievienot mainīgo", + "keyLabel": "Atslēga", + "keyTooLong": "Atslēgai jābūt 256 rakstzīmēm vai mazāk", + "keyDuplicate": "Šī atslēga jau ir profilā", + "valueLabel": "Vērtība", + "secretLabel": "Noslēpums", + "secrets": { + "markSecret": "Atzīmēt kā noslēpumu", + "masked": "Slēptā vērtība", + "reveal": "Atklāj vērtību", + "newValuePlaceholder": "Ievadiet jaunu slepenu vērtību", + "valuePlaceholder": "Slepena vērtība (šifrēta)" + }, + "variablesEmpty": "Vēl nav mainīgo", + "variablesSaveFailed": "Nevarēja saglabāt mainīgo", + "commandsTitle": "Iestatīšanas komandas", + "addCommand": "Pievienot komandu", + "commandLabel": "Pavēli", + "commandPlaceholder": "e.g. pnpm install", + "commandsEmpty": "Vēl nav nevienas iestatīšanas komandas", + "commandsSaveFailed": "Nevarēja saglabāt iestatīšanas komandas", + "moveUp": "Pārvietojieties uz augšu", + "moveDown": "Pārvietojieties uz leju", + "skillsTitle": "Prasmes", + "addSkill": "Pievienojiet prasmi", + "skillNameLabel": "Prasmes nosaukums", + "skillMarkdownLabel": "Prasmju saturs", + "skillsEmpty": "Vēl nav prasmju", + "skillNameRequired": "Ievadiet prasmes nosaukumu", + "skillNameInvalid": "Prasmes nosaukumam jāsākas ar burtu vai ciparu, un tajā jāizmanto tikai mazie burti, cipari un domuzīmes.", + "skillContentRequired": "Nepieciešams prasmju saturs", + "skillSaveFailed": "Nevarēja saglabāt prasmi", + "skillsHint": "Ielīmējiet failu SKILL.md, un tā nosaukums tiks aizpildīts; pievienojiet prasmi no sava tālruņa bez vilkšanas un nomešanas.", + "skillEditTitle": "Rediģēt prasmi", + "mcp": { + "title": "MCP serveri", + "add": "Pievienojiet MCP serveri", + "empty": "Vēl nav MCP serveru", + "name": "Servera nosaukums", + "type": "Servera tips", + "localType": "Vietējais", + "remoteType": "Tālvadības pults", + "command": "Komandrinda", + "url": "URL", + "environment": "Vide (JSON)", + "headers": "Galvenes (JSON)", + "timeout": "Taimauts", + "secretCount": "Slepenais skaitījums", + "namePlaceholder": "my-server", + "commandPlaceholder": "npx @example/mcp-server", + "jsonPlaceholder": "{\n \"API_KEY\": \"sk-...\"\n}", + "nameRequired": "Ievadiet servera nosaukumu", + "nameInvalid": "Nosaukumā var izmantot tikai burtus, ciparus, pasvītras un domuzīmes", + "commandRequired": "Ievadiet komandu", + "commandTooLong": "Izmantojiet ne vairāk kā 50 argumentus, katrs 500 rakstzīmes", + "urlRequired": "Ievadiet URL", + "urlInvalid": "Ievadiet derīgu URL", + "urlTooLong": "URL nedrīkst pārsniegt 2048 rakstzīmes", + "jsonInvalid": "Ievadiet derīgu JSON — virknes vērtību objektu", + "jsonTooLarge": "Izmantojiet ne vairāk kā 50 ierakstus, atslēgas līdz 128 un vērtības līdz 4096 rakstzīmēm", + "timeoutInvalid": "Taimautai ir jābūt veselam milisekunžu skaitam", + "edit": "Rediģēt MCP serveri", + "saveFailed": "Nevarēja saglabāt MCP serveri" + }, + "agents": { + "title": "Profila aģenti", + "add": "Pievienojiet aģentu", + "empty": "Vēl nav aģentu", + "name": "Aģenta vārds", + "slug": "Aģentu gliemežnīca", + "description": "Aģenta apraksts", + "model": "Modeļa ignorēšana", + "modelPlaceholder": "Izmantot sesijas noklusējumu", + "prompt": "Sistēmas uzvedne", + "steps": "Maks. soļi", + "stepsPlaceholder": "piem. 50", + "temperature": "Temperatūra", + "temperaturePlaceholder": "piem. 0.2", + "topP": "top_p", + "topPPlaceholder": "piem. 0.95", + "slugPlaceholder": "my-agent", + "namePlaceholder": "Mans aģents", + "promptPlaceholder": "Tu esi...", + "tools": "Rīki", + "visibility": "Redzamība", + "visibilityPrimary": "Primārais — parādīts atlasītājā", + "visibilitySubagent": "Subaģents — izsaucams tikai no citiem", + "visibilityAll": "Visi - abi", + "saveFailed": "Nevarēja saglabāt aģentu", + "nameRequired": "Ievadiet aģenta nosaukumu", + "slugRequired": "Ievadiet aģenta lodes", + "slugInvalid": "Slug jāsākas ar burtu un jāizmanto tikai mazie burti, cipari un domuzīmes", + "slugConflict": "Šo sliņķi rezervē iebūvētais aģents", + "edit": "Rediģēt aģentu" + }, + "slashCommands": { + "title": "Svītru komandas", + "add": "Pievienot slīpsvītras komandu", + "empty": "Vēl nav slīpsvītru komandu", + "name": "Komandas nosaukums", + "description": "Komandas apraksts", + "template": "Veidne", + "namePlaceholder": "explain", + "templatePlaceholder": "Izskaidrojiet $ARGUMENTS", + "agent": "Aģents", + "subtask": "Palaist kā apakšuzdevumu", + "edit": "Rediģēt slīpsvītras komandu", + "saveFailed": "Nevarēja saglabāt komandu", + "nameRequired": "Ievadiet komandas nosaukumu", + "nameInvalid": "Nosaukumam ir jāsākas ar mazo burtu un jāizmanto tikai mazie burti, cipari un domuzīmes", + "templateRequired": "Ievadiet komandas veidni", + "nameConflict": "Šo nosaukumu rezervē iebūvēta komanda" + }, + "repoBindings": { + "title": "Atpirkšanas darījumu noklusējuma profili", + "add": "Pievienot noklusējumu", + "empty": "Nav konfigurēti noklusējuma iestatījumi", + "emptyDescription": "Pievienojiet noklusējuma iestatījumu, lai automātiski lietotu profilu, sākot repo sesijas.", + "repo": "Atlasīt repozitoriju…", + "profile": "Izvēlieties profilu", + "remove": "Atsaistīt", + "private": "Privāts", + "search": "Meklēt repozitorijās…", + "addedToast": "Noklusējuma profils iestatīts uz \"{{repo}}\"", + "addFailed": "Nevarēja iestatīt noklusējuma profilu", + "removedToast": "Noklusējuma profils ir noņemts \"{{repo}}\"", + "removeFailed": "Nevarēja noņemt noklusējuma profilu", + "pinnedTitle": "Piesprausts krātuvēm", + "pinRepo": "Piespraust repo", + "pinnedEmpty": "Nav piespraustas nevienai krātuvei.", + "pinnedDescription": "Automātiski tiek ielādēts ikreiz, kad sesijas mērķauditorija ir kāda no šīm krātuvēm.", + "pinnedToast": "Piesprausts \"{{repo}}\"", + "pinFailed": "Nevarēja piespraust repo", + "unboundToast": "Repo bez ierobežojumiem", + "unbindFailed": "Nevarēja atsaistīt repo" + } } } diff --git a/apps/mobile/src/i18n/locales/mg.json b/apps/mobile/src/i18n/locales/mg.json index 54d64fbbcfb..e568adad32d 100644 --- a/apps/mobile/src/i18n/locales/mg.json +++ b/apps/mobile/src/i18n/locales/mg.json @@ -2191,7 +2191,33 @@ "continueFrom": "Mitohy ny {{title}}.", "importingSession": "Manafatra fotoam-piasana", "cliCannotContinue": "Tsy afaka manohy fotoam-piasana ity CLI ity. Safidio ny Cloud Agent na CLI hafa.", - "importFailed": "Tsy nahomby ny fanafarana ity fotoam-piasana ity." + "importFailed": "Tsy nahomby ny fanafarana ity fotoam-piasana ity.", + "pickProfile": "Mifidiana profil", + "noProfile": "Tsy misy mombamomba", + "manageProfiles": "Tantano ny mombamomba", + "noProfiles": "Tsy mbola misy mombamomba. Mamorona iray hanampiana ireo fari-piainan'ny tontolo iainana, mpizara MCP ary fahaiza-manao.", + "advancedConfig": "Fandrindrana mandroso", + "advancedConfigDescription": "Misafidiana piraofilina hametahana ny tefy voatahiry, ary amboary tanana ny fari-piainan'ny tontolo iainana fanampiny sy baiko fanamboarana", + "saveAsProfile": "Tehirizo ho Profile", + "saveProfileDescription": "Tehirizo ho profil azo ampiasaina indray ny fari-piainan'ny tontolo iainana ankehitriny sy ny baiko fanamboarana.", + "profileSummary": "{{vars}} fari-piainan'ny tontolo iainana · {{commands}} baiko fanamboarana", + "profileSaved": "Voatahiry ny mombamomba \"{{name}}\".", + "organizationProfiles": "Organisation Profiles", + "personalProfiles": "Profil manokana", + "yourProfiles": "Ny mombamomba anao", + "failedToLoadProfiles": "Tsy nahomby ny fampidirana ny mombamomba azy", + "nameTooLong": "Lava loatra ny anaran'ny mombamomba azy", + "descriptionTooLong": "Lava loatra ny famaritana", + "secretsEncrypted": "Ny tsiambaratelo dia miafina alohan'ny fitahirizana.", + "profileActive": "Profile mavitrika", + "profilesActive": "Mandeha ny mombamomba", + "customConfigActive": "Mandeha ny fandrindrana manokana", + "configNeedsAttention": "Mila fiheverana ny config", + "repoProfile": "Repo profil", + "selectedProfile": "mombamomba voafantina", + "manualOverrides": "Manoloana ny manual", + "openSettingsToReview": "Sokafy ny Settings handinihana", + "profileSelection": "Fifidianana mombamomba" }, "partRenderer": { "updatedFileCount_one": "Nohavaozina ny rakitra {{displayCount}}.", @@ -3224,5 +3250,180 @@ "multiplierError": "Ampidiro mpamaro 1 na mihoatra", "permissionDenied": "Tsy manana alalana hitantana ny fampitandremana momba ny fandaniana ianao.", "pushOffByCategory": "Mihidy ao amin'ny Fampandrenesana" + }, + "profiles": { + "title": "mombamomba", + "entrySubtitle": "Ampiasao indray ny firafitry ny tontolo iainana mandritra ny fotoam-pivoriana", + "newProfile": "Profil vaovao", + "emptyTitle": "Tsy mbola misy mombamomba", + "emptyDescription": "Mamorona mombamomba hampiasaina indray ny fari-piainan'ny tontolo iainana, baiko fanamboarana ary fahaiza-manao mandritra ny fotoam-pivoriana.", + "loadFailed": "Tsy afaka mampiditra ny mombamomba", + "counts": { + "vars": "{{count}} miovaova", + "mcp": "{{count}} MCP", + "skills": "{{count}} fahaiza-manao", + "commands": "{{count}} baiko", + "varsShort": "{{count}}m", + "skillsShort": "{{count}}f", + "commandsShort": "{{count}}b" + }, + "nameLabel": "Anaran'ny mombamomba", + "namePlaceholder": "oh. Backend debugging", + "descriptionLabel": "Famaritana ny mombamomba", + "descriptionPlaceholder": "Famaritana azo atao", + "ownerLabel": "Tompon'ny mombamomba", + "nameRequired": "Ampidiro anarana profil", + "createAction": "Mamorona mombamomba", + "createFailed": "Tsy afaka namorona piraofilina", + "createdToast": "Noforonina ny mombamomba \"{{name}}\".", + "settingsSectionTitle": "fanahafana", + "defaultSectionTitle": "Profile default", + "personalDefaultDescription": "Ampidiro mandeha ho azy rehefa tsy misy tahiry misy piraofilina voafantina.", + "organizationDefaultDescription": "Auto-loaded ho an'ny mpikambana tsy manana default manokana.", + "setAsDefault": "Ataovy ho default", + "removeDefault": "Esory ho default", + "saveFailed": "Tsy afaka mitahiry piraofilina", + "deleteTitle": "Fafao ny mombamomba", + "deleteMessage": "Izany dia manala tanteraka ny mombamomba sy ny firafiny. Tsy azo averina izany.", + "deleteFailed": "Tsy afaka namafa ny mombamomba", + "deleteBlocked": "Ity profil ity dia ampiasain'ny trigger webhook. Esory aloha ireo trigger ireo.", + "deletedToast": "Voafafa ny mombamomba \"{{name}}\".", + "variablesTitle": "Tontolo iainana miovaova", + "addVariable": "Ampio miovaova", + "keyLabel": "Fanalahidy", + "keyTooLong": "Tokony tsy mihoatra ny 256 tarehintsoratra ny fanalahidy", + "keyDuplicate": "Efa ao amin'ny profil ity fanalahidy ity", + "valueLabel": "Sanda", + "secretLabel": "zava-miafina", + "secrets": { + "markSecret": "Mariho ho tsiambaratelo", + "masked": "Sanda miafina", + "reveal": "Asehoy ny sandany", + "newValuePlaceholder": "Ampidiro ny sandan-tsiambaratelo vaovao", + "valuePlaceholder": "Sanda tsiambaratelo (voaaro)" + }, + "variablesEmpty": "Tsy mbola misy variables", + "variablesSaveFailed": "Tsy voatahiry ny fari-piainana", + "commandsTitle": "Mametraka baiko", + "addCommand": "Ampio baiko", + "commandLabel": "didy", + "commandPlaceholder": "e.g. pnpm install", + "commandsEmpty": "Tsy mbola misy baiko fanamboarana", + "commandsSaveFailed": "Tsy afaka mitahiry baiko fanamboarana", + "moveUp": "Miakatra", + "moveDown": "Mandrosoa midina", + "skillsTitle": "fahaiza-manao", + "addSkill": "Ampio fahaizana", + "skillNameLabel": "Anarana fahaizana", + "skillMarkdownLabel": "Votoatin'ny fahaiza-manao", + "skillsEmpty": "Tsy mbola misy fahaizana", + "skillNameRequired": "Ampidiro anarana fahaizana", + "skillNameInvalid": "Ny anaran'ny fahaiza-manao dia tsy maintsy manomboka amin'ny litera na tarehimarika ary tsy mampiasa afa-tsy litera kely, isa ary tsipiho", + "skillContentRequired": "Ilaina ny votoatin'ny fahaiza-manao", + "skillSaveFailed": "Tsy nahavonjy fahaizana", + "skillsHint": "Apetaho ny SKILL.md ary fenoy ny anarany; ampio fahaiza-manao avy amin'ny findainao tsy misy drag and drop.", + "skillEditTitle": "Ahitsio fahaizana", + "mcp": { + "title": "Mpizara MCP", + "add": "Ampio mpizara MCP", + "empty": "Tsy mbola misy mpizara MCP", + "name": "Anaran'ny mpizara", + "type": "Karazana mpizara", + "localType": "EO AN-TOERANA", + "remoteType": "mitokana", + "command": "Fibaikoana", + "url": "URL", + "environment": "Tontolo iainana (JSON)", + "headers": "Lohateny (JSON)", + "timeout": "Fotoana dila", + "secretCount": "Ny isa miafina", + "namePlaceholder": "my-server", + "commandPlaceholder": "npx @example/mcp-server", + "jsonPlaceholder": "{\n \"API_KEY\": \"sk-...\"\n}", + "nameRequired": "Ampidiro anarana mpizara", + "nameInvalid": "Ny anarana dia tsy afaka mampiasa afa-tsy litera, isa, tsipiho, ary tsipiho", + "commandRequired": "Ampidiro baiko", + "commandTooLong": "Ampiasao farafahabetsany 50 tohan-kevitra, 500 tarehintsoratra avy", + "urlRequired": "Ampidiro URL", + "urlInvalid": "Ampidiro URL manan-kery", + "urlTooLong": "Tsy mihoatra ny 2048 tarehintsoratra ny URL", + "jsonInvalid": "Ampidiro JSON manan-kery - zavatra misy soatoavin'ny tady", + "jsonTooLarge": "Ampiasao farafahabetsany 50 fidirana, fanalahidy hatramin'ny 128 ary sanda hatramin'ny 4096 tarehintsoratra", + "timeoutInvalid": "Tokony ho milisegondra iray manontolo ny fotoana fiatoana", + "edit": "Ahitsio MCP server", + "saveFailed": "Tsy afaka nitahiry mpizara MCP" + }, + "agents": { + "title": "Mpampiasa mombamomba", + "add": "Ampio mpandraharaha", + "empty": "Tsy mbola misy masoivoho", + "name": "Anaran'ny mpandraharaha", + "slug": "Slug mpandraharaha", + "description": "Famariparitana ny mpandraharaha", + "model": "Modely override", + "modelPlaceholder": "Ampiasao ny default session", + "prompt": "Baiko rafitra", + "steps": "Dingana farany ambony", + "stepsPlaceholder": "oh. 50", + "temperature": "Maripana", + "temperaturePlaceholder": "oh. 0.2", + "topP": "top_p", + "topPPlaceholder": "oh. 0.95", + "slugPlaceholder": "my-agent", + "namePlaceholder": "Ny mpandraharahako", + "promptPlaceholder": "Ianao dia...", + "tools": "Fitaovana", + "visibility": "Fahitana", + "visibilityPrimary": "Kilonga - aseho ao amin'ny mpanangona", + "visibilitySubagent": "Subagent - azo antsoina amin'ny hafa ihany", + "visibilityAll": "Rehetra - izy roa", + "saveFailed": "Tsy afaka namonjy mpandraharaha", + "nameRequired": "Ampidiro anarana mpandraharaha", + "slugRequired": "Ampidiro ny slug mpandraharaha", + "slugInvalid": "Ny slug dia tsy maintsy manomboka amin'ny litera ary tsy mampiasa afa-tsy litera kely, isa ary tsipiho", + "slugConflict": "Io slug io dia natokana ho an'ny mpandraharaha iray", + "edit": "Ahitsio mpandraharaha" + }, + "slashCommands": { + "title": "baiko slash", + "add": "Ampio baiko slash", + "empty": "Tsy mbola misy baiko slash", + "name": "Anaran'ny baiko", + "description": "Famaritana ny baiko", + "template": "Endrika", + "namePlaceholder": "explain", + "templatePlaceholder": "Hazavao ny $ARGUMENTS", + "agent": "mpandraharaha", + "subtask": "Mihazakazaka toy ny subtask", + "edit": "Ahitsio slash command", + "saveFailed": "Tsy afaka mitahiry baiko", + "nameRequired": "Ampidiro anarana baiko", + "nameInvalid": "Ny anarana dia tsy maintsy manomboka amin'ny litera kely ary tsy mampiasa afa-tsy litera kely, isa ary tsipiho", + "templateRequired": "Ampidiro môdely baiko", + "nameConflict": "Io anarana io dia voatokana amin'ny baiko naorina" + }, + "repoBindings": { + "title": "Profiles Default ho an'ny Repos", + "add": "Ampio ny default", + "empty": "Tsy misy default voarindra", + "emptyDescription": "Manampia default hampihatra ho azy ny mombamomba rehefa manomboka fivoriana ho an'ny repo.", + "repo": "Safidio ny tahiry…", + "profile": "Fidio ny mombamomba", + "remove": "Esory", + "private": "TSY MIANKINA", + "search": "Hikaroka tahiry…", + "addedToast": "piraofilina voafantina ho an'ny \"{{repo}}\"", + "addFailed": "Tsy afaka nametraka ny mombamomba azy", + "removedToast": "Nesorina ny mombamomba ny \"{{repo}}\"", + "removeFailed": "Tsy nesorina ny mombamomba azy", + "pinnedTitle": "Hiverina any amin'ny repositorio", + "pinRepo": "Fatoro ny repo", + "pinnedEmpty": "Tsy voapetaka amin'ny repository rehetra.", + "pinnedDescription": "Auto-loaded isaky ny misy session mikendry ny iray amin'ireo tahiry ireo.", + "pinnedToast": "Voafandrika amin'ny \"{{repo}}\"", + "pinFailed": "Tsy afaka nametaka repo", + "unboundToast": "Repo tsy voafatotra", + "unbindFailed": "Tsy afaka namaha ny repo" + } } } diff --git a/apps/mobile/src/i18n/locales/mi.json b/apps/mobile/src/i18n/locales/mi.json index ffc67f83a30..ed5fed88841 100644 --- a/apps/mobile/src/i18n/locales/mi.json +++ b/apps/mobile/src/i18n/locales/mi.json @@ -2191,7 +2191,33 @@ "continueFrom": "Kei te haere tonu te wātū {{title}}.", "importingSession": "Kei te kawemai i te wātū", "cliCannotContinue": "Kāore e taea e tēnei CLI te haere tonu i tētahi wātū. Kōwhiria a Cloud Agent, tētahi atu CLI rānei.", - "importFailed": "Kāore i taea te kawemai i tēnei wātū." + "importFailed": "Kāore i taea te kawemai i tēnei wātū.", + "pickProfile": "Tīpakohia he kōtaha", + "noProfile": "Kāore he kōtaha", + "manageProfiles": "Whakahaere pūkete", + "noProfiles": "Karekau he pūkete. Waihangahia tetahi hei taapiri i nga taurangi taiao, nga tūmau MCP, me nga pukenga.", + "advancedConfig": "Whirihoranga Arā Atu Anō", + "advancedConfigDescription": "Tīpakohia he kōtaha hei uta i ngā whirihoranga kua tiakina, me te whirihora ā-ringa i etahi atu taurangi taiao me nga whakahau tatūnga", + "saveAsProfile": "Tiaki hei Kōtaha", + "saveProfileDescription": "Tiakina nga taurangi taiao o naianei me nga whakahau tatūnga hei kōtaha ka taea te whakamahi.", + "profileSummary": "{{vars}} taurangi taiao · {{commands}} whakahau tatūnga", + "profileSaved": "Kōtaha \"{{name}}\" kua tiakina", + "organizationProfiles": "Ngā Kōtaha Whakahaere", + "personalProfiles": "Whaiaro Whaiaro", + "yourProfiles": "Ō Kōtaha", + "failedToLoadProfiles": "I rahua te uta i nga korero", + "nameTooLong": "He roa rawa te ingoa kōtaha", + "descriptionTooLong": "He roa rawa te whakaahuatanga", + "secretsEncrypted": "Ka whakamunatia nga mea ngaro i mua i te rokiroki.", + "profileActive": "Kōtaha hohe", + "profilesActive": "Kei te hohe nga korero", + "customConfigActive": "Hohe whirihora ritenga", + "configNeedsAttention": "Me aro te whirihora", + "repoProfile": "Kōtaha repo", + "selectedProfile": "Kotaha kua tohua", + "manualOverrides": "Whakakore a-ringa", + "openSettingsToReview": "Whakatuwherahia nga Tautuhinga hei arotake", + "profileSelection": "Kōwhiringa kōtaha" }, "partRenderer": { "updatedFileCount_one": "Kua whakahoutia {{displayCount}} kōnae.", @@ -3224,5 +3250,180 @@ "multiplierError": "Whakaurua he whakarea 1, nui ake rānei", "permissionDenied": "Kāore ō whakaaetanga ki te whakahaere i ngā matohi whakapaunga.", "pushOffByCategory": "Weto i roto i Ngā whakamōhiotanga" + }, + "profiles": { + "title": "Kōtaha", + "entrySubtitle": "Whakamahia ano nga tautuhinga taiao puta noa i nga waahi", + "newProfile": "Kōtaha hou", + "emptyTitle": "Karekau he pūkete", + "emptyDescription": "Waihangatia he kōtaha hei whakamahi ano i nga taurangi taiao, nga whakahau tatūnga, me nga pukenga puta noa i nga waahi.", + "loadFailed": "Kāore i taea te uta kōtaha", + "counts": { + "vars": "{{count}} taurangi", + "mcp": "{{count}} MCP", + "skills": "{{count}} pukenga", + "commands": "{{count}} whakahau", + "varsShort": "{{count}}t", + "skillsShort": "{{count}}p", + "commandsShort": "{{count}}w" + }, + "nameLabel": "Ingoa kōtaha", + "namePlaceholder": "e.g. Ko te patuiro o muri", + "descriptionLabel": "Whakaahuatanga pūkete", + "descriptionPlaceholder": "Whakaahuatanga whiriwhiri", + "ownerLabel": "Kaipupuri pūkete", + "nameRequired": "Whakauruhia he ingoa kōtaha", + "createAction": "Waihanga kōtaha", + "createFailed": "Kāore i taea te waihanga kōtaha", + "createdToast": "Ko te kōtaha \"{{name}}\" i hangaia", + "settingsSectionTitle": "Whirihoranga", + "defaultSectionTitle": "Kōtaha taunoa", + "personalDefaultDescription": "Ka utaina-aunoa ina karekau he putunga he kotaha titi.", + "organizationDefaultDescription": "Ka utaina aunoa mo nga mema karekau he taunoa.", + "setAsDefault": "Tautuhi hei taunoa", + "removeDefault": "Tango hei taunoa", + "saveFailed": "Kāore i taea te tiaki kōtaha", + "deleteTitle": "Mukua te kōtaha", + "deleteMessage": "Ka tangohia tonutia te kōtaha me ona tautuhinga. Kaore e taea te whakakore i tenei.", + "deleteFailed": "Kāore i taea te muku kōtaha", + "deleteBlocked": "Ka whakamahia tenei kōtaha e te keu tukutuku. Tango i te reira i aua keu tuatahi.", + "deletedToast": "Kōtaha \"{{name}}\" kua mukua", + "variablesTitle": "Taurangi taiao", + "addVariable": "Tāpiri taurangi", + "keyLabel": "Kī", + "keyTooLong": "Kia 256 pūāhua iti iho rānei te kī", + "keyDuplicate": "Kua noho kē tēnei kī i te kōtaha", + "valueLabel": "Uara", + "secretLabel": "Mea ngaro", + "secrets": { + "markSecret": "Tohua hei mea ngaro", + "masked": "Uara huna", + "reveal": "Whakakitea te uara", + "newValuePlaceholder": "Whakauruhia he uara ngaro hou", + "valuePlaceholder": "Uara ngaro (kua whakamunatia)" + }, + "variablesEmpty": "Kaore ano kia rereke", + "variablesSaveFailed": "Kāore i taea te tiaki taurangi", + "commandsTitle": "Tatūnga whakahau", + "addCommand": "Tāpiri whakahau", + "commandLabel": "Whakahau", + "commandPlaceholder": "e.g. pnpm install", + "commandsEmpty": "Kare ano he tono tatūnga", + "commandsSaveFailed": "Kāore i taea te tiaki i ngā tono tatūnga", + "moveUp": "Neke ki runga", + "moveDown": "Nuku ki raro", + "skillsTitle": "Pukenga", + "addSkill": "Tāpiri pūkenga", + "skillNameLabel": "Ingoa Pūkenga", + "skillMarkdownLabel": "Te ihirangi pukenga", + "skillsEmpty": "Kaore ano he pukenga", + "skillNameRequired": "Whakauruhia he ingoa pukenga", + "skillNameInvalid": "Me timata te ingoa pukenga ki te reta, mati ranei me te whakamahi i nga reta iti, mati, me nga tohu", + "skillContentRequired": "Kei te hiahiatia nga ihirangi pukenga", + "skillSaveFailed": "Kaore i taea te penapena pukenga", + "skillsHint": "Whakapiria he SKILL.md ka whakakiia tona ingoa; taapirihia he pukenga mai i to waea me te kore toia me te maturuturu.", + "skillEditTitle": "Pukenga whakatika", + "mcp": { + "title": "MCP tūmau", + "add": "Tāpiri tūmau MCP", + "empty": "Karekau he tūmau MCP", + "name": "Ingoa tūmau", + "type": "Momo tūmau", + "localType": "rohe", + "remoteType": "Mamao", + "command": "Raina whakahau", + "url": "URL", + "environment": "Taiao (JSON)", + "headers": "Pane (JSON)", + "timeout": "Wā wā", + "secretCount": "Tatau ngaro", + "namePlaceholder": "my-server", + "commandPlaceholder": "npx @example/mcp-server", + "jsonPlaceholder": "{\n \"API_KEY\": \"sk-...\"\n}", + "nameRequired": "Whakauruhia he ingoa tūmau", + "nameInvalid": "Ka taea e te ingoa te whakamahi i nga reta, nga nama, nga tohu, me nga tohu", + "commandRequired": "Whakauruhia he whakahau", + "commandTooLong": "Whakamahia kia 50 ngā tautohe, 500 pūāhua ia", + "urlRequired": "Whakauruhia he URL", + "urlInvalid": "Whakauruhia he URL whaimana", + "urlTooLong": "Kia 2048 pūāhua iti iho rānei te URL", + "jsonInvalid": "Whakauruhia JSON whaimana - he ahanoa o nga uara aho", + "jsonTooLarge": "Whakamahia kia 50 ngā tāurunga, kī tae atu ki 128 me ngā uara tae atu ki 4096 pūāhua", + "timeoutInvalid": "Ko te wa poto me he maha katoa o nga manomano", + "edit": "Whakatika tūmau MCP", + "saveFailed": "Kāore i taea te tiaki i te tūmau MCP" + }, + "agents": { + "title": "Ngā māngai", + "add": "Tāpiri māngai", + "empty": "Kāore anō he māngai", + "name": "Ingoa māngai", + "slug": "Slug māngai", + "description": "Whakaahuatanga māngai", + "model": "Tauira whakakore", + "modelPlaceholder": "Whakamahia te wātū taunoa", + "prompt": "Te tere o te punaha", + "steps": "Ngā hipanga mōrahi", + "stepsPlaceholder": "hei tauira 50", + "temperature": "Pāmahana", + "temperaturePlaceholder": "hei tauira 0.2", + "topP": "top_p", + "topPPlaceholder": "hei tauira 0.95", + "slugPlaceholder": "my-agent", + "namePlaceholder": "Taku Kaihoko", + "promptPlaceholder": "He...", + "tools": "Utauta", + "visibility": "Tirohanga", + "visibilityPrimary": "Tuatahi — ka whakaatuhia ki te kaikokoti", + "visibilitySubagent": "Subagent — ka taea anake te waea mai i etahi atu", + "visibilityAll": "Katoa — e rua", + "saveFailed": "Kāore i taea te tiaki māngai", + "nameRequired": "Whakauruhia he ingoa māngai", + "slugRequired": "Whakauruhia he slug māngai", + "slugInvalid": "Me timata te Slug ki te reta me te whakamahi i nga reta iti, nga mati me nga tohu", + "slugConflict": "Ka rahuitia taua maaka e tetahi māngai whakauru", + "edit": "Whakatikatika māngai" + }, + "slashCommands": { + "title": "Nga whakahau rītaha", + "add": "Tāpiri whakahau rītaha", + "empty": "Kare ano he whakahau rītaha", + "name": "Ingoa whakahau", + "description": "Whakaahuatanga whakahau", + "template": "tauira", + "namePlaceholder": "explain", + "templatePlaceholder": "Whakamaramahia $ARGUMENTS", + "agent": "Kaihoko", + "subtask": "Whakahaerehia hei mahi iti", + "edit": "Whakatika whakahau rītaha", + "saveFailed": "Kāore i taea te tiaki tono", + "nameRequired": "Whakauruhia he ingoa whakahau", + "nameInvalid": "Me timata te ingoa ki te reta iti me te whakamahi i nga reta iti, nga mati me nga tohu", + "templateRequired": "Whakauruhia he tauira whakahau", + "nameConflict": "Ko taua ingoa kua rahuitia e tetahi tono whakauru" + }, + "repoBindings": { + "title": "Kōtaha Taunoa mō Repos", + "add": "Tāpiri taunoa", + "empty": "Karekau he taunoa i whirihora", + "emptyDescription": "Tāpirihia he taunoa ki te tono aunoa i tetahi kōtaha i te wa e tiimata ana nga huihuinga mo te repo.", + "repo": "Tīpakohia te rokiroki…", + "profile": "Tīpakohia te kōtaha", + "remove": "Wewetehia", + "private": "Tūmataiti", + "search": "Rapua nga putunga…", + "addedToast": "Kotaha taunoa kua tautuhia mo \"{{repo}}\"", + "addFailed": "Kāore i taea te tautuhi kōtaha taunoa", + "removedToast": "Kua tangohia te kōtaha taunoa mō \"{{repo}}\"", + "removeFailed": "Kāore i taea te tango kōtaha taunoa", + "pinnedTitle": "Kua titi ki nga putunga", + "pinRepo": "Titi i tētahi repo", + "pinnedEmpty": "Karekau i titi ki tetahi putunga.", + "pinnedDescription": "Ka utaina-aunoa i nga wa katoa ka arohia e tetahi huihuinga tetahi o enei putunga.", + "pinnedToast": "I titi ki \"{{repo}}\"", + "pinFailed": "Kāore i taea te titi repo", + "unboundToast": "Repo kore here", + "unbindFailed": "Kaore i taea te wete i te repo" + } } } diff --git a/apps/mobile/src/i18n/locales/mk.json b/apps/mobile/src/i18n/locales/mk.json index fe9c235c8ad..cf2bb4b93f5 100644 --- a/apps/mobile/src/i18n/locales/mk.json +++ b/apps/mobile/src/i18n/locales/mk.json @@ -2191,7 +2191,33 @@ "continueFrom": "Се продолжува сесијата {{title}}.", "importingSession": "Увезување на сесијата", "cliCannotContinue": "Овој CLI не може да продолжи сесија. Избери Cloud Agent или друг CLI.", - "importFailed": "Не успеа увезувањето на оваа сесија." + "importFailed": "Не успеа увезувањето на оваа сесија.", + "pickProfile": "Изберете профил", + "noProfile": "Нема профил", + "manageProfiles": "Управувајте со профилите", + "noProfiles": "Сè уште нема профили. Создадете една за да додадете променливи на животната средина, MCP сервери и вештини.", + "advancedConfig": "Напредна конфигурација", + "advancedConfigDescription": "Изберете профил за да ги вчитате зачуваните конфигурации и рачно конфигурирајте дополнителни променливи на околината и команди за поставување", + "saveAsProfile": "Зачувај како профил", + "saveProfileDescription": "Зачувајте ги тековните променливи на околината и командите за поставување како профил за повеќекратна употреба.", + "profileSummary": "{{vars}} променливи на околината · {{commands}} наредби за поставување", + "profileSaved": "Профилот „{{name}}“ е зачуван", + "organizationProfiles": "Профили на организацијата", + "personalProfiles": "Лични профили", + "yourProfiles": "Вашите профили", + "failedToLoadProfiles": "Не успеа да се вчитаат профилите", + "nameTooLong": "Името на профилот е предолго", + "descriptionTooLong": "Описот е предолг", + "secretsEncrypted": "Тајните се шифрираат пред складирањето.", + "profileActive": "Профилот е активен", + "profilesActive": "Профилите се активни", + "customConfigActive": "Прилагодена конфигурација е активна", + "configNeedsAttention": "Конфигурацијата бара внимание", + "repoProfile": "Репо профил", + "selectedProfile": "Избран профил", + "manualOverrides": "Рачно отфрлања", + "openSettingsToReview": "Отворете Поставки за прегледување", + "profileSelection": "Избор на профил" }, "partRenderer": { "updatedFileCount_one": "Ажурирана е {{displayCount}} датотека.", @@ -3224,5 +3250,180 @@ "multiplierError": "Внеси множител од 1 или повеќе", "permissionDenied": "Немаш дозвола да управуваш со предупредувањата за трошење.", "pushOffByCategory": "Исклучено во Известувања" + }, + "profiles": { + "title": "Профили", + "entrySubtitle": "Повторно користете ги поставките на околината низ сесиите", + "newProfile": "Нов профил", + "emptyTitle": "Сè уште нема профили", + "emptyDescription": "Создадете профил за повторна употреба на променливи на околината, команди за поставување и вештини низ сесиите.", + "loadFailed": "Не може да се вчитаат профилите", + "counts": { + "vars": "{{count}} променливи", + "mcp": "{{count}} MCP", + "skills": "{{count}} вештини", + "commands": "{{count}} команди", + "varsShort": "{{count}}п", + "skillsShort": "{{count}}в", + "commandsShort": "{{count}}к" + }, + "nameLabel": "Име на профилот", + "namePlaceholder": "на пр. Дебагирање на задниот дел", + "descriptionLabel": "Опис на профилот", + "descriptionPlaceholder": "Изборен опис", + "ownerLabel": "Сопственик на профил", + "nameRequired": "Внесете име на профилот", + "createAction": "Креирај профил", + "createFailed": "Не може да се создаде профил", + "createdToast": "Создаден е профил „{{name}}“.", + "settingsSectionTitle": "Конфигурација", + "defaultSectionTitle": "Стандарден профил", + "personalDefaultDescription": "Автоматски се вчитува кога ниту едно складиште нема закачен профил.", + "organizationDefaultDescription": "Автоматски се вчита за членови кои немаат личен стандард.", + "setAsDefault": "Поставете како стандардно", + "removeDefault": "Отстрани како стандардно", + "saveFailed": "Не може да се зачува профилот", + "deleteTitle": "Избришете го профилот", + "deleteMessage": "Ова трајно ги отстранува профилот и неговите поставки. Ова не може да се врати.", + "deleteFailed": "Не може да се избрише профилот", + "deleteBlocked": "Овој профил се користи од активирач на веб-кука. Отстранете го прво од тие предизвикувачи.", + "deletedToast": "Профилот „{{name}}“ е избришан", + "variablesTitle": "Променливи на животната средина", + "addVariable": "Додадете променлива", + "keyLabel": "Клуч", + "keyTooLong": "Клучот мора да има 256 знаци или помалку", + "keyDuplicate": "Овој клуч веќе постои во профилот", + "valueLabel": "Вредност", + "secretLabel": "Тајна", + "secrets": { + "markSecret": "Означи како тајна", + "masked": "Скриена вредност", + "reveal": "Откријте вредност", + "newValuePlaceholder": "Внесете нова тајна вредност", + "valuePlaceholder": "Тајна вредност (шифрирана)" + }, + "variablesEmpty": "Сè уште нема променливи", + "variablesSaveFailed": "Не може да се зачува променливата", + "commandsTitle": "Команди за поставување", + "addCommand": "Додадете команда", + "commandLabel": "Команда", + "commandPlaceholder": "e.g. pnpm install", + "commandsEmpty": "Сè уште нема команди за поставување", + "commandsSaveFailed": "Не може да се зачуваат командите за поставување", + "moveUp": "Движете се нагоре", + "moveDown": "Движете се надолу", + "skillsTitle": "Вештини", + "addSkill": "Додадете вештина", + "skillNameLabel": "Име на вештина", + "skillMarkdownLabel": "Содржина на вештини", + "skillsEmpty": "Сè уште нема вештини", + "skillNameRequired": "Внесете име на вештина", + "skillNameInvalid": "Името на вештината мора да започнува со буква или цифра и да користи само мали букви, цифри и цртички", + "skillContentRequired": "Потребна е содржина на вештини", + "skillSaveFailed": "Не може да се спаси вештината", + "skillsHint": "Залепете SKILL.md и неговото име се пополнува; додадете вештина од вашиот телефон без влечење и пуштање.", + "skillEditTitle": "Вештина за уредување", + "mcp": { + "title": "MCP сервери", + "add": "Додадете MCP сервер", + "empty": "Сè уште нема MCP сервери", + "name": "Име на серверот", + "type": "Тип на сервер", + "localType": "Локално", + "remoteType": "Далечински", + "command": "Командна линија", + "url": "URL", + "environment": "Животна средина (JSON)", + "headers": "Заглавија (JSON)", + "timeout": "Тајмаут", + "secretCount": "Тајно броење", + "namePlaceholder": "my-server", + "commandPlaceholder": "npx @example/mcp-server", + "jsonPlaceholder": "{\n \"API_KEY\": \"sk-...\"\n}", + "nameRequired": "Внесете име на серверот", + "nameInvalid": "Името може да користи само букви, бројки, долни цртички и цртички", + "commandRequired": "Внесете команда", + "commandTooLong": "Користете најмногу 50 аргументи од по 500 знаци", + "urlRequired": "Внесете URL-адреса", + "urlInvalid": "Внесете важечка URL-адреса", + "urlTooLong": "URL-то мора да има 2048 знаци или помалку", + "jsonInvalid": "Внесете важечки JSON - објект со вредности на низа", + "jsonTooLarge": "Користете најмногу 50 записи, клучеви до 128 и вредности до 4096 знаци", + "timeoutInvalid": "Истекот на времето мора да биде цел број од милисекунди", + "edit": "Уреди MCP сервер", + "saveFailed": "Не може да се зачува MCP серверот" + }, + "agents": { + "title": "Агенти за профили", + "add": "Додадете агент", + "empty": "Сè уште нема агенти", + "name": "Име на агентот", + "slug": "Агент голтка", + "description": "Опис на агентот", + "model": "Прескокнување на моделот", + "modelPlaceholder": "Користете стандардна сесија", + "prompt": "Системско известување", + "steps": "Макс. чекори", + "stepsPlaceholder": "пр. 50", + "temperature": "Температура", + "temperaturePlaceholder": "пр. 0.2", + "topP": "top_p", + "topPPlaceholder": "пр. 0.95", + "slugPlaceholder": "my-agent", + "namePlaceholder": "Мојот агент", + "promptPlaceholder": "Вие сте еден ...", + "tools": "Алатки", + "visibility": "Видливост", + "visibilityPrimary": "Примарно - прикажано во избирачот", + "visibilitySubagent": "Субагент - може да се повика само од други", + "visibilityAll": "Сите - и двете", + "saveFailed": "Не може да се зачува агентот", + "nameRequired": "Внесете име на агент", + "slugRequired": "Внесете голтка агент", + "slugInvalid": "Slug мора да започнува со буква и да користи само мали букви, цифри и цртички", + "slugConflict": "Таа голтка е резервирана од вграден агент", + "edit": "Уредување агент" + }, + "slashCommands": { + "title": "Коси команди", + "add": "Додадете команда за коса црта", + "empty": "Сè уште нема команди за коса црта", + "name": "Име на командата", + "description": "Опис на командата", + "template": "Шаблон", + "namePlaceholder": "explain", + "templatePlaceholder": "Објасни $ARGUMENTS", + "agent": "Агент", + "subtask": "Стартувај како подзадача", + "edit": "Уреди команда коса црта", + "saveFailed": "Не може да се зачува командата", + "nameRequired": "Внесете име на команда", + "nameInvalid": "Името мора да започнува со мала буква и да користи само мали букви, цифри и цртички", + "templateRequired": "Внесете образец за команда", + "nameConflict": "Тоа име е резервирано со вградена команда" + }, + "repoBindings": { + "title": "Стандардни профили за репо", + "add": "Додадете стандардно", + "empty": "Нема конфигурирани стандардни поставки", + "emptyDescription": "Додајте стандардна за автоматска примена на профил кога започнувате сесии за репо.", + "repo": "Изберете складиште…", + "profile": "Изберете профил", + "remove": "Одврзи", + "private": "Приватен", + "search": "Пребарајте складишта…", + "addedToast": "Стандарден профил поставен за „{{repo}}“", + "addFailed": "Не може да се постави стандарден профил", + "removedToast": "Стандардниот профил е отстранет за „{{repo}}“", + "removeFailed": "Не може да се отстрани стандардниот профил", + "pinnedTitle": "Закачени во складишта", + "pinRepo": "Закачете репо", + "pinnedEmpty": "Не е прикачен на ниту едно складиште.", + "pinnedDescription": "Автоматски се вчитува секогаш кога сесијата е насочена кон едно од овие складишта.", + "pinnedToast": "Закачено на „{{repo}}“", + "pinFailed": "Не можеше да се закачи репо", + "unboundToast": "Репото не е поврзано", + "unbindFailed": "Не можеше да се одврзе репото" + } } } diff --git a/apps/mobile/src/i18n/locales/ml.json b/apps/mobile/src/i18n/locales/ml.json index 564d4a345fa..c186d68e345 100644 --- a/apps/mobile/src/i18n/locales/ml.json +++ b/apps/mobile/src/i18n/locales/ml.json @@ -2191,7 +2191,33 @@ "continueFrom": "{{title}} തുടരുന്നു.", "importingSession": "സെഷൻ ഇംപോർട്ട് ചെയ്യുന്നു", "cliCannotContinue": "ഈ CLI-യ്ക്ക് ഒരു സെഷൻ തുടരാൻ കഴിയില്ല. Cloud Agent അല്ലെങ്കിൽ മറ്റൊരു CLI തിരഞ്ഞെടുക്കുക.", - "importFailed": "ഈ സെഷൻ ഇംപോർട്ട് ചെയ്യാൻ കഴിഞ്ഞില്ല." + "importFailed": "ഈ സെഷൻ ഇംപോർട്ട് ചെയ്യാൻ കഴിഞ്ഞില്ല.", + "pickProfile": "ഒരു പ്രൊഫൈൽ തിരഞ്ഞെടുക്കുക", + "noProfile": "പ്രൊഫൈൽ ഇല്ല", + "manageProfiles": "പ്രൊഫൈലുകൾ കൈകാര്യം ചെയ്യുക", + "noProfiles": "ഇതുവരെ പ്രൊഫൈലുകളൊന്നുമില്ല. പരിസ്ഥിതി വേരിയബിളുകൾ, MCP സെർവറുകൾ, കഴിവുകൾ എന്നിവ ചേർക്കാൻ ഒരെണ്ണം സൃഷ്‌ടിക്കുക.", + "advancedConfig": "വിപുലമായ കോൺഫിഗറേഷൻ", + "advancedConfigDescription": "സംരക്ഷിച്ച കോൺഫിഗറേഷനുകൾ ലോഡുചെയ്യുന്നതിന് ഒരു പ്രൊഫൈൽ തിരഞ്ഞെടുക്കുക, കൂടാതെ അധിക എൻവയോൺമെൻ്റ് വേരിയബിളുകളും സജ്ജീകരണ കമാൻഡുകളും സ്വമേധയാ കോൺഫിഗർ ചെയ്യുക", + "saveAsProfile": "പ്രൊഫൈലായി സംരക്ഷിക്കുക", + "saveProfileDescription": "നിലവിലെ എൻവയോൺമെൻ്റ് വേരിയബിളുകളും സെറ്റപ്പ് കമാൻഡുകളും വീണ്ടും ഉപയോഗിക്കാവുന്ന പ്രൊഫൈലായി സംരക്ഷിക്കുക.", + "profileSummary": "{{vars}} പരിസ്ഥിതി വേരിയബിളുകൾ · {{commands}} സജ്ജീകരണ കമാൻഡുകൾ", + "profileSaved": "പ്രൊഫൈൽ \"{{name}}\" സംരക്ഷിച്ചു", + "organizationProfiles": "ഓർഗനൈസേഷൻ പ്രൊഫൈലുകൾ", + "personalProfiles": "വ്യക്തിഗത പ്രൊഫൈലുകൾ", + "yourProfiles": "നിങ്ങളുടെ പ്രൊഫൈലുകൾ", + "failedToLoadProfiles": "പ്രൊഫൈലുകൾ ലോഡ് ചെയ്യുന്നതിൽ പരാജയപ്പെട്ടു", + "nameTooLong": "പ്രൊഫൈൽ പേര് ദൈർഘ്യമേറിയതാണ്", + "descriptionTooLong": "വിവരണം വളരെ നീണ്ടതാണ്", + "secretsEncrypted": "സംഭരണത്തിന് മുമ്പ് രഹസ്യങ്ങൾ എൻക്രിപ്റ്റ് ചെയ്യപ്പെടുന്നു.", + "profileActive": "പ്രൊഫൈൽ സജീവമാണ്", + "profilesActive": "പ്രൊഫൈലുകൾ സജീവമാണ്", + "customConfigActive": "ഇഷ്‌ടാനുസൃത കോൺഫിഗറേഷൻ സജീവമാണ്", + "configNeedsAttention": "കോൺഫിഗറിനു ശ്രദ്ധ ആവശ്യമാണ്", + "repoProfile": "റിപ്പോ പ്രൊഫൈൽ", + "selectedProfile": "തിരഞ്ഞെടുത്ത പ്രൊഫൈൽ", + "manualOverrides": "മാനുവൽ ഓവർറൈഡുകൾ", + "openSettingsToReview": "അവലോകനം ചെയ്യാൻ ക്രമീകരണം തുറക്കുക", + "profileSelection": "പ്രൊഫൈൽ തിരഞ്ഞെടുക്കൽ" }, "partRenderer": { "updatedFileCount_one": "{{displayCount}} ഫയൽ അപ്ഡേറ്റ് ചെയ്തു.", @@ -3224,5 +3250,180 @@ "multiplierError": "1 അല്ലെങ്കിൽ അതിൽ കൂടുതലുള്ള ഗുണിതം നൽകുക", "permissionDenied": "ചെലവ് മുന്നറിയിപ്പുകൾ നിയന്ത്രിക്കാൻ നിങ്ങൾക്ക് അനുമതിയില്ല.", "pushOffByCategory": "അറിയിപ്പുകളിൽ ഓഫ്" + }, + "profiles": { + "title": "പ്രൊഫൈലുകൾ", + "entrySubtitle": "സെഷനുകളിലുടനീളം പരിസ്ഥിതി ക്രമീകരണങ്ങൾ വീണ്ടും ഉപയോഗിക്കുക", + "newProfile": "പുതിയ പ്രൊഫൈൽ", + "emptyTitle": "ഇതുവരെ പ്രൊഫൈലുകളൊന്നുമില്ല", + "emptyDescription": "സെഷനുകളിലുടനീളമുള്ള എൻവയോൺമെൻ്റ് വേരിയബിളുകൾ, സജ്ജീകരണ കമാൻഡുകൾ, കഴിവുകൾ എന്നിവ പുനരുപയോഗിക്കാൻ ഒരു പ്രൊഫൈൽ സൃഷ്‌ടിക്കുക.", + "loadFailed": "പ്രൊഫൈലുകൾ ലോഡ് ചെയ്യാനായില്ല", + "counts": { + "vars": "{{count}} വേരിയബിളുകൾ", + "mcp": "{{count}} MCP", + "skills": "{{count}} കഴിവുകൾ", + "commands": "{{count}} കമാൻഡുകൾ", + "varsShort": "{{count}}വ", + "skillsShort": "{{count}}ക", + "commandsShort": "{{count}}കമ" + }, + "nameLabel": "പ്രൊഫൈൽ പേര്", + "namePlaceholder": "ഉദാ. ബാക്കെൻഡ് ഡീബഗ്ഗിംഗ്", + "descriptionLabel": "പ്രൊഫൈൽ വിവരണം", + "descriptionPlaceholder": "ഓപ്ഷണൽ വിവരണം", + "ownerLabel": "പ്രൊഫൈൽ ഉടമ", + "nameRequired": "ഒരു പ്രൊഫൈൽ പേര് നൽകുക", + "createAction": "പ്രൊഫൈൽ സൃഷ്ടിക്കുക", + "createFailed": "പ്രൊഫൈൽ സൃഷ്‌ടിക്കാനായില്ല", + "createdToast": "പ്രൊഫൈൽ \"{{name}}\" സൃഷ്ടിച്ചു", + "settingsSectionTitle": "കോൺഫിഗറേഷൻ", + "defaultSectionTitle": "ഡിഫോൾട്ട് പ്രൊഫൈൽ", + "personalDefaultDescription": "ഒരു റിപ്പോസിറ്ററിയിലും പിൻ ചെയ്‌ത പ്രൊഫൈൽ ഇല്ലാത്തപ്പോൾ സ്വയമേവ ലോഡ് ചെയ്‌തു.", + "organizationDefaultDescription": "വ്യക്തിഗത ഡിഫോൾട്ട് ഇല്ലാത്ത അംഗങ്ങൾക്കായി സ്വയമേവ ലോഡ് ചെയ്തു.", + "setAsDefault": "സ്ഥിരസ്ഥിതിയായി സജ്ജമാക്കുക", + "removeDefault": "സ്ഥിരസ്ഥിതിയായി നീക്കം ചെയ്യുക", + "saveFailed": "പ്രൊഫൈൽ സംരക്ഷിക്കാനായില്ല", + "deleteTitle": "പ്രൊഫൈൽ ഇല്ലാതാക്കുക", + "deleteMessage": "ഇത് പ്രൊഫൈലും അതിൻ്റെ ക്രമീകരണങ്ങളും ശാശ്വതമായി നീക്കംചെയ്യുന്നു. ഇത് പഴയപടിയാക്കാനാകില്ല.", + "deleteFailed": "പ്രൊഫൈൽ ഇല്ലാതാക്കാൻ കഴിഞ്ഞില്ല", + "deleteBlocked": "ഈ പ്രൊഫൈൽ ഒരു webhook ട്രിഗർ ഉപയോഗിക്കുന്നു. ആദ്യം ആ ട്രിഗറുകളിൽ നിന്ന് അത് നീക്കം ചെയ്യുക.", + "deletedToast": "പ്രൊഫൈൽ \"{{name}}\" ഇല്ലാതാക്കി", + "variablesTitle": "പരിസ്ഥിതി വേരിയബിളുകൾ", + "addVariable": "വേരിയബിൾ ചേർക്കുക", + "keyLabel": "താക്കോൽ", + "keyTooLong": "കീയിൽ 256 അക്ഷരങ്ങളോ അതിൽ കുറവോ ഉണ്ടായിരിക്കണം", + "keyDuplicate": "ഈ കീ ഇതിനകം പ്രൊഫൈലിൽ ഉണ്ട്", + "valueLabel": "മൂല്യം", + "secretLabel": "രഹസ്യം", + "secrets": { + "markSecret": "രഹസ്യമായി അടയാളപ്പെടുത്തുക", + "masked": "മറഞ്ഞിരിക്കുന്ന മൂല്യം", + "reveal": "മൂല്യം വെളിപ്പെടുത്തുക", + "newValuePlaceholder": "പുതിയ രഹസ്യ മൂല്യം നൽകുക", + "valuePlaceholder": "രഹസ്യ മൂല്യം (എൻക്രിപ്റ്റ് ചെയ്തത്)" + }, + "variablesEmpty": "ഇതുവരെ വേരിയബിളുകളൊന്നുമില്ല", + "variablesSaveFailed": "വേരിയബിൾ സംരക്ഷിക്കാൻ കഴിഞ്ഞില്ല", + "commandsTitle": "കമാൻഡുകൾ സജ്ജമാക്കുക", + "addCommand": "കമാൻഡ് ചേർക്കുക", + "commandLabel": "കമാൻഡ്", + "commandPlaceholder": "e.g. pnpm install", + "commandsEmpty": "ഇതുവരെ സജ്ജീകരണ കമാൻഡുകൾ ഒന്നുമില്ല", + "commandsSaveFailed": "സജ്ജീകരണ കമാൻഡുകൾ സംരക്ഷിക്കാനായില്ല", + "moveUp": "മുകളിലേക്ക് നീങ്ങുക", + "moveDown": "താഴേക്ക് നീങ്ങുക", + "skillsTitle": "കഴിവുകൾ", + "addSkill": "വൈദഗ്ദ്ധ്യം ചേർക്കുക", + "skillNameLabel": "കഴിവിൻ്റെ പേര്", + "skillMarkdownLabel": "നൈപുണ്യ ഉള്ളടക്കം", + "skillsEmpty": "ഇതുവരെ കഴിവുകളൊന്നുമില്ല", + "skillNameRequired": "ഒരു നൈപുണ്യ നാമം നൽകുക", + "skillNameInvalid": "നൈപുണ്യ നാമം ഒരു അക്ഷരത്തിലോ അക്കത്തിലോ ആരംഭിക്കുകയും ചെറിയക്ഷരങ്ങൾ, അക്കങ്ങൾ, ഡാഷുകൾ എന്നിവ മാത്രം ഉപയോഗിക്കുകയും വേണം", + "skillContentRequired": "നൈപുണ്യ ഉള്ളടക്കം ആവശ്യമാണ്", + "skillSaveFailed": "വൈദഗ്ധ്യം സംരക്ഷിക്കാനായില്ല", + "skillsHint": "ഒരു SKILL.md ഒട്ടിക്കുക, അതിൻ്റെ പേര് പൂരിപ്പിക്കുന്നു; വലിച്ചിടാതെ നിങ്ങളുടെ ഫോണിൽ നിന്ന് ഒരു വൈദഗ്ദ്ധ്യം ചേർക്കുക.", + "skillEditTitle": "എഡിറ്റ് വൈദഗ്ദ്ധ്യം", + "mcp": { + "title": "MCP സെർവറുകൾ", + "add": "MCP സെർവർ ചേർക്കുക", + "empty": "ഇതുവരെ MCP സെർവറുകളൊന്നുമില്ല", + "name": "സെർവറിൻ്റെ പേര്", + "type": "സെർവർ തരം", + "localType": "പ്രാദേശിക", + "remoteType": "റിമോട്ട്", + "command": "കമാൻഡ് ലൈൻ", + "url": "URL", + "environment": "പരിസ്ഥിതി (JSON)", + "headers": "തലക്കെട്ടുകൾ (JSON)", + "timeout": "ടൈം ഔട്ട്", + "secretCount": "രഹസ്യ കണക്ക്", + "namePlaceholder": "my-server", + "commandPlaceholder": "npx @example/mcp-server", + "jsonPlaceholder": "{\n \"API_KEY\": \"sk-...\"\n}", + "nameRequired": "ഒരു സെർവർ പേര് നൽകുക", + "nameInvalid": "പേരിന് അക്ഷരങ്ങൾ, അക്കങ്ങൾ, അടിവരകൾ, ഡാഷുകൾ എന്നിവ മാത്രമേ ഉപയോഗിക്കാനാകൂ", + "commandRequired": "ഒരു കമാൻഡ് നൽകുക", + "commandTooLong": "പരമാവധി 50 ആർഗ്യുമെന്റുകൾ ഉപയോഗിക്കുക, ഓരോന്നും 500 അക്ഷരങ്ങൾ", + "urlRequired": "ഒരു URL നൽകുക", + "urlInvalid": "സാധുവായ ഒരു URL നൽകുക", + "urlTooLong": "URL-ന് 2048 അക്ഷരങ്ങളോ അതിൽ കുറവോ ഉണ്ടായിരിക്കണം", + "jsonInvalid": "സ്ട്രിംഗ് മൂല്യങ്ങളുടെ ഒരു ഒബ്ജക്റ്റ് - സാധുവായ JSON നൽകുക", + "jsonTooLarge": "പരമാവധി 50 എൻട്രികൾ ഉപയോഗിക്കുക, കീകൾ 128 വരെയും മൂല്യങ്ങൾ 4096 അക്ഷരങ്ങൾ വരെയും", + "timeoutInvalid": "ടൈംഔട്ട് മില്ലിസെക്കൻഡുകളുടെ പൂർണ്ണസംഖ്യ ആയിരിക്കണം", + "edit": "MCP സെർവർ എഡിറ്റ് ചെയ്യുക", + "saveFailed": "MCP സെർവർ സംരക്ഷിക്കാനായില്ല" + }, + "agents": { + "title": "പ്രൊഫൈൽ ഏജൻ്റുകൾ", + "add": "ഏജൻ്റ് ചേർക്കുക", + "empty": "ഇതുവരെ ഏജൻ്റുമാരില്ല", + "name": "ഏജൻ്റിൻ്റെ പേര്", + "slug": "ഏജൻ്റ് സ്ലഗ്", + "description": "ഏജൻ്റ് വിവരണം", + "model": "മോഡൽ ഓവർറൈഡ്", + "modelPlaceholder": "സെഷൻ ഡിഫോൾട്ട് ഉപയോഗിക്കുക", + "prompt": "സിസ്റ്റം പ്രോംപ്റ്റ്", + "steps": "പരമാവധി ഘട്ടങ്ങൾ", + "stepsPlaceholder": "ഉദാ. 50", + "temperature": "താപനില", + "temperaturePlaceholder": "ഉദാ. 0.2", + "topP": "top_p", + "topPPlaceholder": "ഉദാ. 0.95", + "slugPlaceholder": "my-agent", + "namePlaceholder": "എൻ്റെ ഏജൻ്റ്", + "promptPlaceholder": "നിങ്ങൾ ഒരു...", + "tools": "ഉപകരണങ്ങൾ", + "visibility": "ദൃശ്യപരത", + "visibilityPrimary": "പ്രാഥമികം - പിക്കറിൽ കാണിച്ചിരിക്കുന്നു", + "visibilitySubagent": "ഉപജൻ്റ് - മറ്റുള്ളവരിൽ നിന്ന് മാത്രമേ വിളിക്കാനാവൂ", + "visibilityAll": "എല്ലാം - രണ്ടും", + "saveFailed": "ഏജൻ്റിനെ സംരക്ഷിക്കാനായില്ല", + "nameRequired": "ഒരു ഏജൻ്റിൻ്റെ പേര് നൽകുക", + "slugRequired": "ഒരു ഏജൻ്റ് സ്ലഗ് നൽകുക", + "slugInvalid": "സ്ലഗ് ഒരു അക്ഷരത്തിൽ ആരംഭിക്കുകയും ചെറിയക്ഷരങ്ങൾ, അക്കങ്ങൾ, ഡാഷുകൾ എന്നിവ മാത്രം ഉപയോഗിക്കുകയും വേണം", + "slugConflict": "ആ സ്ലഗ് ഒരു ബിൽറ്റ്-ഇൻ ഏജൻ്റ് റിസർവ് ചെയ്തതാണ്", + "edit": "എഡിറ്റ് ഏജൻ്റ്" + }, + "slashCommands": { + "title": "സ്ലാഷ് കമാൻഡുകൾ", + "add": "സ്ലാഷ് കമാൻഡ് ചേർക്കുക", + "empty": "ഇതുവരെ സ്ലാഷ് കമാൻഡുകൾ ഒന്നുമില്ല", + "name": "കമാൻഡിൻ്റെ പേര്", + "description": "കമാൻഡ് വിവരണം", + "template": "ടെംപ്ലേറ്റ്", + "namePlaceholder": "explain", + "templatePlaceholder": "$ARGUMENTS വിശദീകരിക്കുക", + "agent": "ഏജൻ്റ്", + "subtask": "ഉപടാസ്‌കായി പ്രവർത്തിപ്പിക്കുക", + "edit": "സ്ലാഷ് കമാൻഡ് എഡിറ്റ് ചെയ്യുക", + "saveFailed": "കമാൻഡ് സംരക്ഷിക്കാനായില്ല", + "nameRequired": "ഒരു കമാൻഡ് നാമം നൽകുക", + "nameInvalid": "പേര് ഒരു ചെറിയ അക്ഷരത്തിൽ ആരംഭിക്കുകയും ചെറിയ അക്ഷരങ്ങൾ, അക്കങ്ങൾ, ഡാഷുകൾ എന്നിവ മാത്രം ഉപയോഗിക്കുകയും വേണം", + "templateRequired": "ഒരു കമാൻഡ് ടെംപ്ലേറ്റ് നൽകുക", + "nameConflict": "ആ പേര് ഒരു ബിൽറ്റ്-ഇൻ കമാൻഡ് മുഖേന സംവരണം ചെയ്തിരിക്കുന്നു" + }, + "repoBindings": { + "title": "റിപ്പോസിനുള്ള ഡിഫോൾട്ട് പ്രൊഫൈലുകൾ", + "add": "സ്ഥിരസ്ഥിതി ചേർക്കുക", + "empty": "സ്ഥിരസ്ഥിതികളൊന്നും ക്രമീകരിച്ചിട്ടില്ല", + "emptyDescription": "റിപ്പോയ്‌ക്കായി സെഷനുകൾ ആരംഭിക്കുമ്പോൾ ഒരു പ്രൊഫൈൽ സ്വയമേവ പ്രയോഗിക്കാൻ ഒരു ഡിഫോൾട്ട് ചേർക്കുക.", + "repo": "ശേഖരം തിരഞ്ഞെടുക്കുക...", + "profile": "പ്രൊഫൈൽ തിരഞ്ഞെടുക്കുക", + "remove": "അൺബൈൻഡ്", + "private": "സ്വകാര്യം", + "search": "ശേഖരണങ്ങൾ തിരയുക...", + "addedToast": "\"{{repo}}\" എന്നതിനായി ഡിഫോൾട്ട് പ്രൊഫൈൽ സജ്ജമാക്കി", + "addFailed": "ഡിഫോൾട്ട് പ്രൊഫൈൽ സജ്ജീകരിക്കാനായില്ല", + "removedToast": "\"{{repo}}\" എന്നതിനായി ഡിഫോൾട്ട് പ്രൊഫൈൽ നീക്കം ചെയ്തു", + "removeFailed": "ഡിഫോൾട്ട് പ്രൊഫൈൽ നീക്കം ചെയ്യാനായില്ല", + "pinnedTitle": "റിപ്പോസിറ്ററികളിൽ പിൻ ചെയ്‌തു", + "pinRepo": "ഒരു റിപ്പോ പിൻ ചെയ്യുക", + "pinnedEmpty": "ഒരു റിപ്പോസിറ്ററികളിലേക്കും പിൻ ചെയ്‌തിട്ടില്ല.", + "pinnedDescription": "ഒരു സെഷൻ ഈ ശേഖരണങ്ങളിലൊന്ന് ടാർഗെറ്റുചെയ്യുമ്പോഴെല്ലാം സ്വയമേവ ലോഡ് ചെയ്യുന്നു.", + "pinnedToast": "\"{{repo}}\" എന്നതിലേക്ക് പിൻ ചെയ്‌തു", + "pinFailed": "റിപ്പോ പിൻ ചെയ്യാനായില്ല", + "unboundToast": "റിപ്പോ അൺബൗണ്ട്", + "unbindFailed": "റിപ്പോ അൺബൈൻഡ് ചെയ്യാനായില്ല" + } } } diff --git a/apps/mobile/src/i18n/locales/mn.json b/apps/mobile/src/i18n/locales/mn.json index 5767da01652..f87c718d5c3 100644 --- a/apps/mobile/src/i18n/locales/mn.json +++ b/apps/mobile/src/i18n/locales/mn.json @@ -2191,7 +2191,33 @@ "continueFrom": "«{{title}}» сессийг үргэлжлүүлж байна.", "importingSession": "Сесс импортлож байна", "cliCannotContinue": "Энэ CLI сессийг үргэлжлүүлэх боломжгүй. Cloud Agent эсвэл өөр CLI сонгоно уу.", - "importFailed": "Энэ сессийг импортолж чадсангүй." + "importFailed": "Энэ сессийг импортолж чадсангүй.", + "pickProfile": "Профайл сонгоно уу", + "noProfile": "Профайл байхгүй", + "manageProfiles": "Профайлуудыг удирдах", + "noProfiles": "Одоогоор профайл байхгүй байна. Орчны хувьсагч, MCP сервер, ур чадвар нэмэхийн тулд нэгийг үүсгэ.", + "advancedConfig": "Нарийвчилсан тохиргоо", + "advancedConfigDescription": "Хадгалсан тохиргоог ачаалахын тулд профайлыг сонгож, нэмэлт орчны хувьсагч болон тохиргооны командуудыг гараар тохируулна уу.", + "saveAsProfile": "Профайл болгон хадгалах", + "saveProfileDescription": "Одоогийн орчны хувьсагч болон тохируулах командуудыг дахин ашиглах боломжтой профайл болгон хадгална уу.", + "profileSummary": "{{vars}} орчны хувьсагч · {{commands}} тохируулах командууд", + "profileSaved": "\"{{name}}\" профайлыг хадгалсан", + "organizationProfiles": "Байгууллагын профайл", + "personalProfiles": "Хувийн профайл", + "yourProfiles": "Таны профайлууд", + "failedToLoadProfiles": "Профайлыг ачаалж чадсангүй", + "nameTooLong": "Профайлын нэр хэт урт байна", + "descriptionTooLong": "Тайлбар хэтэрхий урт байна", + "secretsEncrypted": "Хадгалахаасаа өмнө нууцыг шифрлэдэг.", + "profileActive": "Профайл идэвхтэй байна", + "profilesActive": "Профайл идэвхтэй байна", + "customConfigActive": "Тусгай тохиргоо идэвхтэй", + "configNeedsAttention": "Тохиргоог анхаарах хэрэгтэй", + "repoProfile": "Репо профайл", + "selectedProfile": "Сонгосон профайл", + "manualOverrides": "Гараар хүчингүй болгох", + "openSettingsToReview": "Хянахын тулд Тохиргоог нээнэ үү", + "profileSelection": "Профайлын сонголт" }, "partRenderer": { "updatedFileCount_one": "{{displayCount}} файлыг шинэчиллээ.", @@ -3224,5 +3250,180 @@ "multiplierError": "1 буюу түүнээс их үржүүлэгч оруул", "permissionDenied": "Зарцуулалтын анхааруулгыг удирдах эрх танд байхгүй.", "pushOffByCategory": "Мэдэгдэлд унтарсан" + }, + "profiles": { + "title": "Профайлууд", + "entrySubtitle": "Орчны тохиргоог сешнүүдэд дахин ашиглах", + "newProfile": "Шинэ профайл", + "emptyTitle": "Одоогоор профайл байхгүй байна", + "emptyDescription": "Хүрээлэн буй орчны хувьсагч, тохируулах команд, ур чадварыг сешнүүдэд дахин ашиглахын тулд профайл үүсгээрэй.", + "loadFailed": "Профайлуудыг ачаалж чадсангүй", + "counts": { + "vars": "{{count}} хувьсагч", + "mcp": "{{count}} MCP", + "skills": "{{count}} ур чадвар", + "commands": "{{count}} команд", + "varsShort": "{{count}}х", + "skillsShort": "{{count}}у", + "commandsShort": "{{count}}к" + }, + "nameLabel": "Профайлын нэр", + "namePlaceholder": "жишээ нь Ар талд дибаг хийх", + "descriptionLabel": "Профайлын тайлбар", + "descriptionPlaceholder": "Нэмэлт тайлбар", + "ownerLabel": "Профайл эзэмшигч", + "nameRequired": "Профайл нэр оруулна уу", + "createAction": "Профайл үүсгэх", + "createFailed": "Профайл үүсгэж чадсангүй", + "createdToast": "\"{{name}}\" профайл үүсгэсэн", + "settingsSectionTitle": "Тохиргоо", + "defaultSectionTitle": "Өгөгдмөл профайл", + "personalDefaultDescription": "Хадгалах газарт бэхлэгдсэн профайл байхгүй үед автоматаар ачаалагдана.", + "organizationDefaultDescription": "Хувийн өгөгдмөлгүй гишүүдэд автоматаар ачаалагдсан.", + "setAsDefault": "Өгөгдмөл болгож тохируулах", + "removeDefault": "Өгөгдмөл байдлаар устгана уу", + "saveFailed": "Профайлыг хадгалж чадсангүй", + "deleteTitle": "Профайлыг устгах", + "deleteMessage": "Энэ нь профайл болон түүний тохиргоог бүрмөсөн устгана. Үүнийг буцаах боломжгүй.", + "deleteFailed": "Профайлыг устгаж чадсангүй", + "deleteBlocked": "Энэ профайлыг webhook триггер ашигладаг. Эхлээд эдгээр триггерүүдээс үүнийг устга.", + "deletedToast": "\"{{name}}\" профайлыг устгасан", + "variablesTitle": "Хүрээлэн буй орчны хувьсагчид", + "addVariable": "Хувьсагч нэмэх", + "keyLabel": "Түлхүүр", + "keyTooLong": "Түлхүүр нь 256 тэмдэгт эсвэл түүнээс бага байх ёстой", + "keyDuplicate": "Энэ түлхүүр профайлд аль хэдийн байна", + "valueLabel": "Үнэ цэнэ", + "secretLabel": "Нууц", + "secrets": { + "markSecret": "Нууц гэж тэмдэглэ", + "masked": "Нуугдсан үнэ цэнэ", + "reveal": "Үнэ цэнийг илчлэх", + "newValuePlaceholder": "Шинэ нууц утгыг оруулна уу", + "valuePlaceholder": "Нууц утга (шифрлэгдсэн)" + }, + "variablesEmpty": "Одоогоор хувьсагч алга", + "variablesSaveFailed": "Хувьсагчийг хадгалж чадсангүй", + "commandsTitle": "Тохируулах командууд", + "addCommand": "Команд нэмэх", + "commandLabel": "Тушаал", + "commandPlaceholder": "e.g. pnpm install", + "commandsEmpty": "Одоогоор тохируулах команд алга", + "commandsSaveFailed": "Тохируулгын командуудыг хадгалж чадсангүй", + "moveUp": "Дээш хөдөл", + "moveDown": "Доош хөдөл", + "skillsTitle": "Ур чадвар", + "addSkill": "Ур чадвар нэмэх", + "skillNameLabel": "Ур чадварын нэр", + "skillMarkdownLabel": "Ур чадварын агуулга", + "skillsEmpty": "Одоогоор ур чадвар алга", + "skillNameRequired": "Ур чадварын нэрийг оруулна уу", + "skillNameInvalid": "Ур чадварын нэр нь үсэг эсвэл цифрээр эхэлж, зөвхөн жижиг үсэг, цифр, зураас ашиглана.", + "skillContentRequired": "Ур чадварын агуулга шаардлагатай", + "skillSaveFailed": "Ур чадвараа хадгалж чадсангүй", + "skillsHint": "SKILL.md-г буулгаж, нэрийг нь бөглөнө; Чирэх, буулгахгүйгээр утаснаасаа ур чадвар нэмнэ үү.", + "skillEditTitle": "Ур чадварыг засах", + "mcp": { + "title": "MCP серверүүд", + "add": "MCP сервер нэмнэ үү", + "empty": "Одоогоор MCP сервер байхгүй байна", + "name": "Серверийн нэр", + "type": "Серверийн төрөл", + "localType": "Орон нутгийн", + "remoteType": "Алсын удирдлага", + "command": "Тушаалын мөр", + "url": "URL", + "environment": "Байгаль орчин (JSON)", + "headers": "Толгой хэсэг (JSON)", + "timeout": "Хугацаа хэтэрсэн", + "secretCount": "Нууц тоо", + "namePlaceholder": "my-server", + "commandPlaceholder": "npx @example/mcp-server", + "jsonPlaceholder": "{\n \"API_KEY\": \"sk-...\"\n}", + "nameRequired": "Серверийн нэрийг оруулна уу", + "nameInvalid": "Нэр нь зөвхөн үсэг, тоо, доогуур зураас, зураас ашиглаж болно", + "commandRequired": "Команд оруулна уу", + "commandTooLong": "Хамгийн ихдээ 50 аргумент ашиглана уу, тус бүр 500 тэмдэгт", + "urlRequired": "URL оруулна уу", + "urlInvalid": "Хүчинтэй URL оруулна уу", + "urlTooLong": "URL нь 2048 тэмдэгт эсвэл түүнээс бага байх ёстой", + "jsonInvalid": "Хүчинтэй JSON - мөрийн утгуудын объектыг оруулна уу", + "jsonTooLarge": "Хамгийн ихдээ 50 оруулга ашиглана уу, түлхүүр 128 хүртэл, утга 4096 тэмдэгт хүртэл", + "timeoutInvalid": "Хугацаа нь бүхэл бүтэн миллисекунд байх ёстой", + "edit": "MCP серверийг засах", + "saveFailed": "MCP серверийг хадгалж чадсангүй" + }, + "agents": { + "title": "Профайл агентууд", + "add": "Агент нэмэх", + "empty": "Одоогоор төлөөлөгч байхгүй", + "name": "Агентын нэр", + "slug": "Агент лаг", + "description": "Агентийн тодорхойлолт", + "model": "Загварыг дарах", + "modelPlaceholder": "Үндсэн сессийг ашиглана уу", + "prompt": "Системийн сануулга", + "steps": "Макс алхам", + "stepsPlaceholder": "жишээ нь 50", + "temperature": "Температур", + "temperaturePlaceholder": "жишээ нь 0.2", + "topP": "top_p", + "topPPlaceholder": "жишээ нь 0.95", + "slugPlaceholder": "my-agent", + "namePlaceholder": "Миний агент", + "promptPlaceholder": "Та бол ...", + "tools": "Багаж хэрэгсэл", + "visibility": "Харагдах байдал", + "visibilityPrimary": "Үндсэн - сонгогч дээр харуулав", + "visibilitySubagent": "Дэд агент - зөвхөн бусдаас дуудах боломжтой", + "visibilityAll": "Бүгд - хоёулаа", + "saveFailed": "Агентийг хадгалж чадсангүй", + "nameRequired": "Агентын нэрийг оруулна уу", + "slugRequired": "Агент slug оруулна уу", + "slugInvalid": "Slug үсгээр эхэлж, зөвхөн жижиг үсэг, цифр, зураас ашиглана", + "slugConflict": "Тэр slug-г суулгасан агент нөөцөлсөн", + "edit": "Агентийг засах" + }, + "slashCommands": { + "title": "Slash командууд", + "add": "Ташуу зураас команд нэмнэ", + "empty": "Одоохондоо ташуу зураастай команд байхгүй", + "name": "Тушаалын нэр", + "description": "Тушаалын тайлбар", + "template": "Загвар", + "namePlaceholder": "explain", + "templatePlaceholder": "$ARGUMENTS-г тайлбарла", + "agent": "Агент", + "subtask": "Дэд даалгавар болгон ажиллуул", + "edit": "Талуу зураасыг засах команд", + "saveFailed": "Командыг хадгалж чадсангүй", + "nameRequired": "Тушаалын нэрийг оруулна уу", + "nameInvalid": "Нэр нь жижиг үсгээр эхэлж, зөвхөн жижиг үсэг, цифр, зураас ашиглана", + "templateRequired": "Тушаалын загварыг оруулна уу", + "nameConflict": "Энэ нэрийг суулгасан тушаалаар хадгалдаг" + }, + "repoBindings": { + "title": "Репогийн өгөгдмөл профайл", + "add": "Өгөгдмөл нэмэх", + "empty": "Өгөгдмөл тохиргоо хийгдээгүй", + "emptyDescription": "Репо хийх сессүүдийг эхлүүлэх үед автоматаар профайлыг ашиглах үндсэн тохиргоог нэмнэ үү.", + "repo": "Хадгалах газрыг сонгох...", + "profile": "Профайл сонгоно уу", + "remove": "Тайлах", + "private": "Хувийн", + "search": "Хадгалах газраас хайх...", + "addedToast": "\"{{repo}}\"-д зориулсан өгөгдмөл профайлыг тохируулсан", + "addFailed": "Өгөгдмөл профайлыг тохируулж чадсангүй", + "removedToast": "\"{{repo}}\"-н өгөгдмөл профайлыг устгасан", + "removeFailed": "Өгөгдмөл профайлыг устгаж чадсангүй", + "pinnedTitle": "Хадгалах газруудад бэхлэгдсэн", + "pinRepo": "Репо бэхлэх", + "pinnedEmpty": "Ямар ч хадгалах санд тогтоогдоогүй.", + "pinnedDescription": "Сеанс эдгээр хадгалах газруудын аль нэгийг нь онилсон үед автоматаар ачаалагдана.", + "pinnedToast": "\"{{repo}}\" руу бэхэлсэн", + "pinFailed": "Репо-г тогтоож чадсангүй", + "unboundToast": "Репо хязгааргүй", + "unbindFailed": "Репо-г салгаж чадсангүй" + } } } diff --git a/apps/mobile/src/i18n/locales/mr.json b/apps/mobile/src/i18n/locales/mr.json index 4d7f0277701..c8c7662ee0b 100644 --- a/apps/mobile/src/i18n/locales/mr.json +++ b/apps/mobile/src/i18n/locales/mr.json @@ -2191,7 +2191,33 @@ "continueFrom": "{{title}} हे सत्र पुढे सुरू होत आहे.", "importingSession": "सत्र आयात होत आहे", "cliCannotContinue": "हे CLI सत्र सुरू ठेवू शकत नाही. Cloud Agent किंवा दुसरे CLI निवडा.", - "importFailed": "हे सत्र आयात करता आले नाही." + "importFailed": "हे सत्र आयात करता आले नाही.", + "pickProfile": "प्रोफाइल निवडा", + "noProfile": "प्रोफाइल नाही", + "manageProfiles": "प्रोफाइल व्यवस्थापित करा", + "noProfiles": "अद्याप कोणतेही प्रोफाइल नाहीत. पर्यावरण व्हेरिएबल्स, MCP सर्व्हर आणि कौशल्ये जोडण्यासाठी एक तयार करा.", + "advancedConfig": "प्रगत कॉन्फिगरेशन", + "advancedConfigDescription": "सेव्ह केलेले कॉन्फिगरेशन लोड करण्यासाठी प्रोफाइल निवडा आणि अतिरिक्त पर्यावरण व्हेरिएबल्स आणि सेटअप कमांड मॅन्युअली कॉन्फिगर करा", + "saveAsProfile": "प्रोफाइल म्हणून सेव्ह करा", + "saveProfileDescription": "वर्तमान पर्यावरण व्हेरिएबल्स आणि सेटअप कमांड्स पुन्हा वापरता येण्याजोग्या प्रोफाइल म्हणून जतन करा.", + "profileSummary": "{{vars}} पर्यावरण चल · {{commands}} सेटअप आदेश", + "profileSaved": "प्रोफाइल \"{{name}}\" जतन केले", + "organizationProfiles": "संस्था प्रोफाइल", + "personalProfiles": "वैयक्तिक प्रोफाइल", + "yourProfiles": "तुमचे प्रोफाइल", + "failedToLoadProfiles": "प्रोफाइल लोड करण्यात अयशस्वी", + "nameTooLong": "प्रोफाइल नाव खूप मोठे आहे", + "descriptionTooLong": "वर्णन खूप मोठे आहे", + "secretsEncrypted": "स्टोरेज करण्यापूर्वी रहस्ये कूटबद्ध केली जातात.", + "profileActive": "प्रोफाइल सक्रिय", + "profilesActive": "प्रोफाइल सक्रिय", + "customConfigActive": "सानुकूल कॉन्फिगरेशन सक्रिय", + "configNeedsAttention": "कॉन्फिगला लक्ष देणे आवश्यक आहे", + "repoProfile": "रेपो प्रोफाइल", + "selectedProfile": "प्रोफाइल निवडले", + "manualOverrides": "मॅन्युअल अधिलिखित", + "openSettingsToReview": "पुनरावलोकन करण्यासाठी सेटिंग्ज उघडा", + "profileSelection": "प्रोफाइल निवड" }, "partRenderer": { "updatedFileCount_one": "{{displayCount}} फाइल अद्ययावत केली.", @@ -3224,5 +3250,180 @@ "multiplierError": "1 किंवा त्याहून मोठा गुणक टाका", "permissionDenied": "तुम्हाला खर्चाच्या सूचना व्यवस्थापित करण्याची परवानगी नाही.", "pushOffByCategory": "सूचनांमध्ये बंद" + }, + "profiles": { + "title": "प्रोफाइल", + "entrySubtitle": "सर्व सत्रांमध्ये पर्यावरण सेटिंग्ज पुन्हा वापरा", + "newProfile": "नवीन प्रोफाइल", + "emptyTitle": "अद्याप कोणतेही प्रोफाइल नाहीत", + "emptyDescription": "संपूर्ण सत्रांमध्ये पर्यावरण व्हेरिएबल्स, सेटअप कमांड आणि कौशल्ये पुन्हा वापरण्यासाठी प्रोफाइल तयार करा.", + "loadFailed": "प्रोफाइल लोड करू शकलो नाही", + "counts": { + "vars": "{{count}} चल", + "mcp": "{{count}} MCP", + "skills": "{{count}} कौशल्य", + "commands": "{{count}} कमांड", + "varsShort": "{{count}}च", + "skillsShort": "{{count}}क", + "commandsShort": "{{count}}कम" + }, + "nameLabel": "प्रोफाइल नाव", + "namePlaceholder": "उदा. बॅकएंड डीबगिंग", + "descriptionLabel": "प्रोफाइल वर्णन", + "descriptionPlaceholder": "पर्यायी वर्णन", + "ownerLabel": "प्रोफाइल मालक", + "nameRequired": "प्रोफाइल नाव प्रविष्ट करा", + "createAction": "प्रोफाइल तयार करा", + "createFailed": "प्रोफाइल तयार करू शकलो नाही", + "createdToast": "प्रोफाइल \"{{name}}\" तयार केले", + "settingsSectionTitle": "कॉन्फिगरेशन", + "defaultSectionTitle": "डीफॉल्ट प्रोफाइल", + "personalDefaultDescription": "कोणत्याही रेपॉजिटरीमध्ये पिन केलेले प्रोफाइल नसताना स्वयं-लोड होते.", + "organizationDefaultDescription": "वैयक्तिक डीफॉल्ट नसलेल्या सदस्यांसाठी स्वयं-लोड.", + "setAsDefault": "डीफॉल्ट म्हणून सेट करा", + "removeDefault": "डीफॉल्ट म्हणून काढा", + "saveFailed": "प्रोफाइल सेव्ह करू शकलो नाही", + "deleteTitle": "प्रोफाइल हटवा", + "deleteMessage": "हे प्रोफाइल आणि त्याची सेटिंग्ज कायमची काढून टाकते. हे पूर्ववत केले जाऊ शकत नाही.", + "deleteFailed": "प्रोफाइल हटवू शकलो नाही", + "deleteBlocked": "हे प्रोफाइल वेबहुक ट्रिगरद्वारे वापरले जाते. प्रथम त्या ट्रिगर्समधून काढून टाका.", + "deletedToast": "प्रोफाइल \"{{name}}\" हटवले", + "variablesTitle": "पर्यावरण परिवर्तने", + "addVariable": "व्हेरिएबल जोडा", + "keyLabel": "की", + "keyTooLong": "की 256 अक्षरे किंवा कमी असावी", + "keyDuplicate": "ही की आधीपासून प्रोफाइलमध्ये आहे", + "valueLabel": "मूल्य", + "secretLabel": "गुप्त", + "secrets": { + "markSecret": "गुप्त म्हणून चिन्हांकित करा", + "masked": "लपलेले मूल्य", + "reveal": "मूल्य प्रकट करा", + "newValuePlaceholder": "नवीन गुप्त मूल्य प्रविष्ट करा", + "valuePlaceholder": "गुप्त मूल्य (एन्क्रिप्ट केलेले)" + }, + "variablesEmpty": "अद्याप कोणतेही व्हेरिएबल्स नाहीत", + "variablesSaveFailed": "व्हेरिएबल सेव्ह करू शकलो नाही", + "commandsTitle": "सेटअप कमांड", + "addCommand": "आदेश जोडा", + "commandLabel": "आज्ञा", + "commandPlaceholder": "e.g. pnpm install", + "commandsEmpty": "अद्याप सेटअप आदेश नाहीत", + "commandsSaveFailed": "सेटअप कमांड सेव्ह करू शकलो नाही", + "moveUp": "वर हलवा", + "moveDown": "खाली हलवा", + "skillsTitle": "कौशल्य", + "addSkill": "कौशल्य जोडा", + "skillNameLabel": "कौशल्याचे नाव", + "skillMarkdownLabel": "कौशल्य सामग्री", + "skillsEmpty": "अजून कौशल्य नाही", + "skillNameRequired": "कौशल्याचे नाव एंटर करा", + "skillNameInvalid": "कौशल्याचे नाव अक्षर किंवा अंकाने सुरू होणे आवश्यक आहे आणि फक्त लोअरकेस अक्षरे, अंक आणि डॅश वापरणे आवश्यक आहे", + "skillContentRequired": "कौशल्य सामग्री आवश्यक आहे", + "skillSaveFailed": "कौशल्य वाचवू शकलो नाही", + "skillsHint": "SKILL.md पेस्ट करा आणि त्याचे नाव भरले; ड्रॅग आणि ड्रॉप न करता तुमच्या फोनवरून कौशल्य जोडा.", + "skillEditTitle": "कौशल्य संपादित करा", + "mcp": { + "title": "MCP सर्व्हर", + "add": "MCP सर्व्हर जोडा", + "empty": "अद्याप कोणतेही MCP सर्व्हर नाहीत", + "name": "सर्व्हरचे नाव", + "type": "सर्व्हर प्रकार", + "localType": "स्थानिक", + "remoteType": "रिमोट", + "command": "कमांड लाइन", + "url": "URL", + "environment": "पर्यावरण (JSON)", + "headers": "शीर्षलेख (JSON)", + "timeout": "कालबाह्य", + "secretCount": "गुप्त गणना", + "namePlaceholder": "my-server", + "commandPlaceholder": "npx @example/mcp-server", + "jsonPlaceholder": "{\n \"API_KEY\": \"sk-...\"\n}", + "nameRequired": "सर्व्हरचे नाव एंटर करा", + "nameInvalid": "नाव फक्त अक्षरे, संख्या, अंडरस्कोअर आणि डॅश वापरू शकते", + "commandRequired": "कमांड एंटर करा", + "commandTooLong": "कमाल 50 आर्ग्युमेंट वापरा, प्रत्येकी 500 अक्षरे", + "urlRequired": "URL एंटर करा", + "urlInvalid": "वैध URL एंटर करा", + "urlTooLong": "URL 2048 अक्षरे किंवा कमी असावा", + "jsonInvalid": "वैध JSON एंटर करा — स्ट्रिंग व्हॅल्यूजचा एक ऑब्जेक्ट", + "jsonTooLarge": "कमाल 50 नोंदी वापरा, की 128 पर्यंत आणि मूल्ये 4096 अक्षरांपर्यंत", + "timeoutInvalid": "कालबाह्य पूर्ण संख्या मिलीसेकंद असणे आवश्यक आहे", + "edit": "MCP सर्व्हर संपादित करा", + "saveFailed": "MCP सर्व्हर जतन करू शकलो नाही" + }, + "agents": { + "title": "प्रोफाइल एजंट", + "add": "एजंट जोडा", + "empty": "अद्याप कोणतेही एजंट नाहीत", + "name": "एजंटचे नाव", + "slug": "एजंट स्लग", + "description": "एजंट वर्णन", + "model": "मॉडेल ओव्हरराइड", + "modelPlaceholder": "सत्र डीफॉल्ट वापरा", + "prompt": "सिस्टम प्रॉम्प्ट", + "steps": "कमाल पायऱ्या", + "stepsPlaceholder": "उदा. 50", + "temperature": "तापमान", + "temperaturePlaceholder": "उदा. 0.2", + "topP": "top_p", + "topPPlaceholder": "उदा. 0.95", + "slugPlaceholder": "my-agent", + "namePlaceholder": "माझा एजंट", + "promptPlaceholder": "तुम्ही एक...", + "tools": "साधने", + "visibility": "दृश्यमानता", + "visibilityPrimary": "प्राथमिक — पिकरमध्ये दाखवले आहे", + "visibilitySubagent": "Subagent — फक्त इतरांकडून कॉल करण्यायोग्य", + "visibilityAll": "सर्व - दोन्ही", + "saveFailed": "एजंट जतन करू शकलो नाही", + "nameRequired": "एजंटचे नाव एंटर करा", + "slugRequired": "एजंट स्लग एंटर करा", + "slugInvalid": "स्लग अक्षराने सुरू होणे आवश्यक आहे आणि फक्त लोअरकेस अक्षरे, अंक आणि डॅश वापरणे आवश्यक आहे", + "slugConflict": "तो गोगलगाय अंगभूत एजंटने राखून ठेवला आहे", + "edit": "एजंट संपादित करा" + }, + "slashCommands": { + "title": "स्लॅश आज्ञा", + "add": "स्लॅश कमांड जोडा", + "empty": "अद्याप कोणतेही स्लॅश आदेश नाहीत", + "name": "आदेशाचे नाव", + "description": "आदेश वर्णन", + "template": "साचा", + "namePlaceholder": "explain", + "templatePlaceholder": "$ARGUMENTS स्पष्ट करा", + "agent": "एजंट", + "subtask": "सबटास्क म्हणून चालवा", + "edit": "स्लॅश कमांड संपादित करा", + "saveFailed": "कमांड सेव्ह करू शकलो नाही", + "nameRequired": "कमांडचे नाव एंटर करा", + "nameInvalid": "नाव लोअरकेस अक्षराने सुरू होणे आवश्यक आहे आणि फक्त लोअरकेस अक्षरे, अंक आणि डॅश वापरणे आवश्यक आहे", + "templateRequired": "कमांड टेम्पलेट प्रविष्ट करा", + "nameConflict": "ते नाव अंगभूत कमांडद्वारे राखीव आहे" + }, + "repoBindings": { + "title": "रेपोसाठी डीफॉल्ट प्रोफाइल", + "add": "डीफॉल्ट जोडा", + "empty": "कोणतेही डीफॉल्ट कॉन्फिगर केलेले नाहीत", + "emptyDescription": "रेपोसाठी सत्र सुरू करताना प्रोफाइल स्वयंचलितपणे लागू करण्यासाठी डीफॉल्ट जोडा.", + "repo": "भांडार निवडा...", + "profile": "प्रोफाइल निवडा", + "remove": "अनबाइंड करा", + "private": "खाजगी", + "search": "भांडार शोधा…", + "addedToast": "\"{{repo}}\" साठी डीफॉल्ट प्रोफाइल सेट केले", + "addFailed": "डीफॉल्ट प्रोफाइल सेट करू शकलो नाही", + "removedToast": "\"{{repo}}\" साठी डीफॉल्ट प्रोफाइल काढले", + "removeFailed": "डीफॉल्ट प्रोफाइल काढू शकलो नाही", + "pinnedTitle": "रेपॉजिटरीजमध्ये पिन केले", + "pinRepo": "रेपो पिन करा", + "pinnedEmpty": "कोणत्याही भांडारांमध्ये पिन केलेले नाही.", + "pinnedDescription": "जेव्हा जेव्हा सत्र या भांडारांपैकी एकाला लक्ष्य करते तेव्हा स्वयं-लोड होते.", + "pinnedToast": "\"{{repo}}\" वर पिन केले", + "pinFailed": "रेपो पिन करू शकलो नाही", + "unboundToast": "रेपो अनबाउंड", + "unbindFailed": "रेपो अनबाइंड करू शकलो नाही" + } } } diff --git a/apps/mobile/src/i18n/locales/ms.json b/apps/mobile/src/i18n/locales/ms.json index 26cba394852..2a82eaf1aa1 100644 --- a/apps/mobile/src/i18n/locales/ms.json +++ b/apps/mobile/src/i18n/locales/ms.json @@ -2191,7 +2191,33 @@ "continueFrom": "Sesi {{title}} diteruskan.", "importingSession": "Mengimport sesi", "cliCannotContinue": "CLI ini tidak dapat meneruskan sesi. Pilih Cloud Agent atau CLI lain.", - "importFailed": "Tidak dapat mengimport sesi ini." + "importFailed": "Tidak dapat mengimport sesi ini.", + "pickProfile": "Pilih profil", + "noProfile": "Tiada profil", + "manageProfiles": "Urus profil", + "noProfiles": "Tiada profil lagi. Buat satu untuk menambah pembolehubah persekitaran, pelayan MCP dan kemahiran.", + "advancedConfig": "Konfigurasi Lanjutan", + "advancedConfigDescription": "Pilih profil untuk memuatkan konfigurasi yang disimpan dan konfigurasikan pembolehubah persekitaran tambahan dan arahan persediaan secara manual", + "saveAsProfile": "Simpan sebagai Profil", + "saveProfileDescription": "Simpan pembolehubah persekitaran semasa dan arahan persediaan sebagai profil boleh guna semula.", + "profileSummary": "{{vars}} pembolehubah persekitaran · {{commands}} arahan persediaan", + "profileSaved": "Profil \"{{name}}\" disimpan", + "organizationProfiles": "Profil Organisasi", + "personalProfiles": "Profil Peribadi", + "yourProfiles": "Profil Anda", + "failedToLoadProfiles": "Gagal memuatkan profil", + "nameTooLong": "Nama profil terlalu panjang", + "descriptionTooLong": "Penerangan terlalu panjang", + "secretsEncrypted": "Rahsia disulitkan sebelum penyimpanan.", + "profileActive": "Profil aktif", + "profilesActive": "Profil aktif", + "customConfigActive": "Konfigurasi tersuai aktif", + "configNeedsAttention": "Konfigurasi memerlukan perhatian", + "repoProfile": "Profil Repo", + "selectedProfile": "Profil yang dipilih", + "manualOverrides": "Penggantian manual", + "openSettingsToReview": "Buka Tetapan untuk menyemak", + "profileSelection": "Pemilihan profil" }, "partRenderer": { "updatedFileCount_one": "{{displayCount}} fail telah dikemas kini.", @@ -3224,5 +3250,180 @@ "multiplierError": "Masukkan pengganda 1 atau lebih", "permissionDenied": "Anda tidak mempunyai kebenaran untuk mengurus amaran perbelanjaan.", "pushOffByCategory": "Dimatikan dalam Pemberitahuan" + }, + "profiles": { + "title": "Profil", + "entrySubtitle": "Gunakan semula tetapan persekitaran merentas sesi", + "newProfile": "Profil baharu", + "emptyTitle": "Tiada profil lagi", + "emptyDescription": "Buat profil untuk menggunakan semula pembolehubah persekitaran, arahan persediaan dan kemahiran merentas sesi.", + "loadFailed": "Tidak dapat memuatkan profil", + "counts": { + "vars": "{{count}} pembolehubah", + "mcp": "{{count}} MCP", + "skills": "{{count}} kemahiran", + "commands": "{{count}} perintah", + "varsShort": "{{count}}pb", + "skillsShort": "{{count}}k", + "commandsShort": "{{count}}pr" + }, + "nameLabel": "Nama profil", + "namePlaceholder": "cth. Penyahpepijatan bahagian belakang", + "descriptionLabel": "Perihalan profil", + "descriptionPlaceholder": "Penerangan pilihan", + "ownerLabel": "Pemilik profil", + "nameRequired": "Masukkan nama profil", + "createAction": "Buat profil", + "createFailed": "Tidak dapat membuat profil", + "createdToast": "Profil \"{{name}}\" dibuat", + "settingsSectionTitle": "Konfigurasi", + "defaultSectionTitle": "Profil lalai", + "personalDefaultDescription": "Auto-muat apabila tiada repositori mempunyai profil yang disematkan.", + "organizationDefaultDescription": "Dimuat secara automatik untuk ahli yang tidak mempunyai lalai peribadi.", + "setAsDefault": "Tetapkan sebagai lalai", + "removeDefault": "Alih keluar sebagai lalai", + "saveFailed": "Tidak dapat menyimpan profil", + "deleteTitle": "Padam profil", + "deleteMessage": "Ini mengalih keluar profil dan tetapannya secara kekal. Ini tidak boleh dibuat asal.", + "deleteFailed": "Tidak dapat memadamkan profil", + "deleteBlocked": "Profil ini digunakan oleh pencetus webhook. Keluarkan ia daripada pencetus tersebut dahulu.", + "deletedToast": "Profil \"{{name}}\" dipadamkan", + "variablesTitle": "Pembolehubah persekitaran", + "addVariable": "Tambah pembolehubah", + "keyLabel": "kunci", + "keyTooLong": "Kunci mestilah 256 aksara atau kurang", + "keyDuplicate": "Kunci ini sudah ada dalam profil", + "valueLabel": "Nilai", + "secretLabel": "Rahsia", + "secrets": { + "markSecret": "Tandakan sebagai rahsia", + "masked": "Nilai tersembunyi", + "reveal": "Mendedahkan nilai", + "newValuePlaceholder": "Masukkan nilai rahsia baharu", + "valuePlaceholder": "Nilai rahsia (disulitkan)" + }, + "variablesEmpty": "Tiada pembolehubah lagi", + "variablesSaveFailed": "Tidak dapat menyimpan pembolehubah", + "commandsTitle": "Perintah persediaan", + "addCommand": "Tambah arahan", + "commandLabel": "Perintah", + "commandPlaceholder": "e.g. pnpm install", + "commandsEmpty": "Tiada arahan persediaan lagi", + "commandsSaveFailed": "Tidak dapat menyimpan arahan persediaan", + "moveUp": "Bergerak ke atas", + "moveDown": "Bergerak ke bawah", + "skillsTitle": "Kemahiran", + "addSkill": "Tambah kemahiran", + "skillNameLabel": "Nama kemahiran", + "skillMarkdownLabel": "Kandungan kemahiran", + "skillsEmpty": "Belum ada kemahiran", + "skillNameRequired": "Masukkan nama kemahiran", + "skillNameInvalid": "Nama kemahiran mesti bermula dengan huruf atau digit dan hanya menggunakan huruf kecil, angka dan sempang", + "skillContentRequired": "Kandungan kemahiran diperlukan", + "skillSaveFailed": "Tidak dapat menyelamatkan kemahiran", + "skillsHint": "Tampal SKILL.md dan namanya diisi; tambah kemahiran daripada telefon anda tanpa seret dan lepas.", + "skillEditTitle": "Kemahiran mengedit", + "mcp": { + "title": "pelayan MCP", + "add": "Tambahkan pelayan MCP", + "empty": "Tiada pelayan MCP lagi", + "name": "Nama pelayan", + "type": "Jenis pelayan", + "localType": "Tempatan", + "remoteType": "Jauh", + "command": "Barisan arahan", + "url": "URL", + "environment": "Persekitaran (JSON)", + "headers": "Pengepala (JSON)", + "timeout": "tamat masa", + "secretCount": "Kiraan rahsia", + "namePlaceholder": "my-server", + "commandPlaceholder": "npx @example/mcp-server", + "jsonPlaceholder": "{\n \"API_KEY\": \"sk-...\"\n}", + "nameRequired": "Masukkan nama pelayan", + "nameInvalid": "Nama hanya boleh menggunakan huruf, nombor, garis bawah dan sempang", + "commandRequired": "Masukkan arahan", + "commandTooLong": "Gunakan paling banyak 50 argumen yang setiap satu 500 aksara", + "urlRequired": "Masukkan URL", + "urlInvalid": "Masukkan URL yang sah", + "urlTooLong": "URL mestilah 2048 aksara atau kurang", + "jsonInvalid": "Masukkan JSON yang sah — objek nilai rentetan", + "jsonTooLarge": "Gunakan paling banyak 50 entri, kunci sehingga 128 dan nilai sehingga 4096 aksara", + "timeoutInvalid": "Tamat masa mestilah bilangan keseluruhan milisaat", + "edit": "Edit pelayan MCP", + "saveFailed": "Tidak dapat menyimpan pelayan MCP" + }, + "agents": { + "title": "Ejen profil", + "add": "Tambah ejen", + "empty": "Belum ada ejen", + "name": "Nama ejen", + "slug": "Siput ejen", + "description": "Penerangan ejen", + "model": "Model menimpa", + "modelPlaceholder": "Gunakan lalai sesi", + "prompt": "Gesaan sistem", + "steps": "Langkah maks.", + "stepsPlaceholder": "cth. 50", + "temperature": "Suhu", + "temperaturePlaceholder": "cth. 0.2", + "topP": "top_p", + "topPPlaceholder": "cth. 0.95", + "slugPlaceholder": "my-agent", + "namePlaceholder": "Ejen saya", + "promptPlaceholder": "Anda seorang...", + "tools": "Alatan", + "visibility": "Keterlihatan", + "visibilityPrimary": "Utama — ditunjukkan dalam pemilih", + "visibilitySubagent": "Subagen — hanya boleh dipanggil daripada orang lain", + "visibilityAll": "Semua - kedua-duanya", + "saveFailed": "Tidak dapat menyelamatkan ejen", + "nameRequired": "Masukkan nama ejen", + "slugRequired": "Masukkan slug ejen", + "slugInvalid": "Slug mesti bermula dengan huruf dan hanya menggunakan huruf kecil, digit dan sempang", + "slugConflict": "Slug itu ditempah oleh ejen terbina dalam", + "edit": "Edit ejen" + }, + "slashCommands": { + "title": "Perintah tebas", + "add": "Tambah arahan slash", + "empty": "Tiada arahan slash lagi", + "name": "Nama arahan", + "description": "Penerangan perintah", + "template": "templat", + "namePlaceholder": "explain", + "templatePlaceholder": "Terangkan $ARGUMENTS", + "agent": "ejen", + "subtask": "Jalankan sebagai subtugas", + "edit": "Edit arahan slash", + "saveFailed": "Tidak dapat menyimpan arahan", + "nameRequired": "Masukkan nama arahan", + "nameInvalid": "Nama mesti bermula dengan huruf kecil dan hanya menggunakan huruf kecil, angka dan sempang", + "templateRequired": "Masukkan templat arahan", + "nameConflict": "Nama itu dikhaskan oleh arahan terbina dalam" + }, + "repoBindings": { + "title": "Profil Lalai untuk Repos", + "add": "Tambah lalai", + "empty": "Tiada lalai dikonfigurasikan", + "emptyDescription": "Tambahkan lalai untuk menggunakan profil secara automatik apabila memulakan sesi untuk repo.", + "repo": "Pilih repositori…", + "profile": "Pilih profil", + "remove": "Buka ikatan", + "private": "Persendirian", + "search": "Cari repositori…", + "addedToast": "Profil lalai ditetapkan untuk \"{{repo}}\"", + "addFailed": "Tidak dapat menetapkan profil lalai", + "removedToast": "Profil lalai dialih keluar untuk \"{{repo}}\"", + "removeFailed": "Tidak dapat mengalih keluar profil lalai", + "pinnedTitle": "Disematkan pada repositori", + "pinRepo": "Sematkan repo", + "pinnedEmpty": "Tidak disematkan pada mana-mana repositori.", + "pinnedDescription": "Dimuat secara automatik apabila sesi menyasarkan salah satu repositori ini.", + "pinnedToast": "Disemat pada \"{{repo}}\"", + "pinFailed": "Tidak dapat menyemat repo", + "unboundToast": "Repo tidak terikat", + "unbindFailed": "Tidak dapat menyahikat repo" + } } } diff --git a/apps/mobile/src/i18n/locales/mt.json b/apps/mobile/src/i18n/locales/mt.json index f2afe9fb8e4..9fe513d66d8 100644 --- a/apps/mobile/src/i18n/locales/mt.json +++ b/apps/mobile/src/i18n/locales/mt.json @@ -2275,7 +2275,33 @@ "continueFrom": "Qed tkompli s-sessjoni {{title}}.", "importingSession": "Qed tiġi importata s-sessjoni", "cliCannotContinue": "Dan il-CLI ma jistax ikompli sessjoni. Agħżel Cloud Agent jew CLI ieħor.", - "importFailed": "Ma setgħetx tiġi importata din is-sessjoni." + "importFailed": "Ma setgħetx tiġi importata din is-sessjoni.", + "pickProfile": "Agħżel profil", + "noProfile": "Ebda profil", + "manageProfiles": "Immaniġġja profili", + "noProfiles": "Għadu l-ebda profil. Oħloq wieħed biex iżżid varjabbli ambjentali, servers MCP, u ħiliet.", + "advancedConfig": "Konfigurazzjoni Avvanzata", + "advancedConfigDescription": "Agħżel profil biex tagħbija konfigurazzjonijiet salvati, u kkonfigura manwalment varjabbli ambjentali addizzjonali u kmandi tas-setup", + "saveAsProfile": "Ħlief bħala Profil", + "saveProfileDescription": "Issejvja l-varjabbli ambjentali attwali u l-kmandi tas-setup bħala profil li jista 'jerġa' jintuża.", + "profileSummary": "{{vars}} varjabbli ambjentali · {{commands}} kmandi tas-setup", + "profileSaved": "Profil \"{{name}}\" salvat", + "organizationProfiles": "Profili tal-Organizzazzjoni", + "personalProfiles": "Profili Personali", + "yourProfiles": "Il-Profili Tiegħek", + "failedToLoadProfiles": "Naqas milli jgħabbi l-profili", + "nameTooLong": "L-isem tal-profil huwa twil wisq", + "descriptionTooLong": "Id-deskrizzjoni hija twila wisq", + "secretsEncrypted": "Is-sigrieti huma encrypted qabel il-ħażna.", + "profileActive": "Profil attiv", + "profilesActive": "Profili attivi", + "customConfigActive": "Konfigurazzjoni personalizzata attiva", + "configNeedsAttention": "Il-konfigurazzjoni teħtieġ attenzjoni", + "repoProfile": "Profil Repo", + "selectedProfile": "Profil magħżul", + "manualOverrides": "Manwal overrides", + "openSettingsToReview": "Iftaħ Settings biex tirrevedi", + "profileSelection": "Għażla tal-profil" }, "partRenderer": { "updatedFileCount_one": "Ġie aġġornat {{displayCount}} fajl.", @@ -3353,5 +3379,180 @@ "multiplierError": "Daħħal multiplikatur ta' 1 jew aktar", "permissionDenied": "M'għandekx permess biex timmaniġġja t-twissijiet tal-infiq.", "pushOffByCategory": "Mitfi fin-Notifiki" + }, + "profiles": { + "title": "Profili", + "entrySubtitle": "Uża mill-ġdid is-settings tal-ambjent matul is-sessjonijiet", + "newProfile": "Profil ġdid", + "emptyTitle": "Għadu l-ebda profil", + "emptyDescription": "Oħloq profil biex tuża mill-ġdid il-varjabbli ambjentali, il-kmandi tas-setup, u l-ħiliet matul is-sessjonijiet.", + "loadFailed": "Ma setgħetx tagħbija profili", + "counts": { + "vars": "{{count}} varjabbli", + "mcp": "{{count}} MCP", + "skills": "{{count}} ħiliet", + "commands": "{{count}} kmandi", + "varsShort": "{{count}}v", + "skillsShort": "{{count}}ħ", + "commandsShort": "{{count}}km" + }, + "nameLabel": "Isem tal-profil", + "namePlaceholder": "eż. Debugging backend", + "descriptionLabel": "Deskrizzjoni tal-profil", + "descriptionPlaceholder": "Deskrizzjoni fakultattiva", + "ownerLabel": "Sid il-profil", + "nameRequired": "Daħħal isem profil", + "createAction": "Oħloq profil", + "createFailed": "Ma setgħetx toħloq profil", + "createdToast": "Inħoloq profil \"{{name}}\".", + "settingsSectionTitle": "Konfigurazzjoni", + "defaultSectionTitle": "Profil default", + "personalDefaultDescription": "Imgħabba awtomatikament meta l-ebda repożitorju ma jkollu profil ippinjat.", + "organizationDefaultDescription": "Auto-loaded għal membri li m'għandhom l-ebda default personali.", + "setAsDefault": "Issettja bħala default", + "removeDefault": "Neħħi bħala default", + "saveFailed": "Ma setax isalva l-profil", + "deleteTitle": "Ħassar il-profil", + "deleteMessage": "Dan ineħħi b'mod permanenti l-profil u s-settings tiegħu. Dan ma jistax jitneħħa.", + "deleteFailed": "Ma setax iħassar il-profil", + "deleteBlocked": "Dan il-profil jintuża minn grillu tal-webhook. Neħħiha minn dawk il-kawżi l-ewwel.", + "deletedToast": "Profil \"{{name}}\" imħassar", + "variablesTitle": "Varjabbli ambjentali", + "addVariable": "Żid varjabbli", + "keyLabel": "Ewlenin", + "keyTooLong": "L-ewlenin irid ikun 256 karattru jew inqas", + "keyDuplicate": "Dan l-ewlenin diġà jeżisti fil-profil", + "valueLabel": "Valur", + "secretLabel": "Sigriet", + "secrets": { + "markSecret": "Immarka bħala sigriet", + "masked": "Valur moħbi", + "reveal": "Ikxef il-valur", + "newValuePlaceholder": "Daħħal valur sigriet ġdid", + "valuePlaceholder": "Valur sigriet (inkriptat)" + }, + "variablesEmpty": "S'issa l-ebda varjabbli", + "variablesSaveFailed": "Ma setgħetx tissejvja l-varjabbli", + "commandsTitle": "Setup kmandi", + "addCommand": "Żid kmand", + "commandLabel": "Kmand", + "commandPlaceholder": "e.g. pnpm install", + "commandsEmpty": "S'issa l-ebda kmand tas-setup", + "commandsSaveFailed": "Ma setgħetx tissejvja l-kmandi tas-setup", + "moveUp": "Nimxu 'l fuq", + "moveDown": "Nimxu 'l isfel", + "skillsTitle": "Ħiliet", + "addSkill": "Żid il-ħila", + "skillNameLabel": "Isem tal-ħila", + "skillMarkdownLabel": "Kontenut tal-ħiliet", + "skillsEmpty": "Għadu l-ebda ħiliet", + "skillNameRequired": "Daħħal isem tal-ħila", + "skillNameInvalid": "L-isem tal-ħiliet għandu jibda b'ittra jew ċifra u juża biss ittri żgħar, ċifri u sings", + "skillContentRequired": "Il-kontenut tal-ħiliet huwa meħtieġ", + "skillSaveFailed": "Ma setgħetx tiffranka l-ħiliet", + "skillsHint": "Pasta SKILL.md u isimha jimla; żid ħila mit-telefon tiegħek mingħajr drag and drop.", + "skillEditTitle": "Edit ħiliet", + "mcp": { + "title": "servers MCP", + "add": "Żid server MCP", + "empty": "S'issa m'hemmx servers MCP", + "name": "Isem tas-server", + "type": "Tip ta' server", + "localType": "Lokali", + "remoteType": "Mill-bogħod", + "command": "Linja tal-kmand", + "url": "URL", + "environment": "Ambjent (JSON)", + "headers": "Intestaturi (JSON)", + "timeout": "Timeout", + "secretCount": "Għadd sigriet", + "namePlaceholder": "my-server", + "commandPlaceholder": "npx @example/mcp-server", + "jsonPlaceholder": "{\n \"API_KEY\": \"sk-...\"\n}", + "nameRequired": "Daħħal isem server", + "nameInvalid": "Isem jista 'juża biss ittri, numri, underscore, u sing", + "commandRequired": "Daħħal kmand", + "commandTooLong": "Uża mhux aktar minn 50 argument, kull wieħed 500 karattru", + "urlRequired": "Daħħal URL", + "urlInvalid": "Daħħal URL validu", + "urlTooLong": "L-URL irid ikun 2048 karattru jew inqas", + "jsonInvalid": "Daħħal JSON validu — oġġett ta' valuri ta' string", + "jsonTooLarge": "Uża mhux aktar minn 50 entrata, ewlenin sa 128 u valuri sa 4096 karattru", + "timeoutInvalid": "Timeout għandu jkun numru sħiħ ta' millisekondi", + "edit": "Editja server MCP", + "saveFailed": "Ma setax isalva server MCP" + }, + "agents": { + "title": "Aġenti tal-profil", + "add": "Żid l-aġent", + "empty": "Għadu l-ebda aġenti", + "name": "Isem l-aġent", + "slug": "aġent slug", + "description": "Deskrizzjoni tal-aġent", + "model": "Mudell override", + "modelPlaceholder": "Uża default tas-sessjoni", + "prompt": "Sistema fil-pront", + "steps": "Passi massimi", + "stepsPlaceholder": "eż. 50", + "temperature": "Temperatura", + "temperaturePlaceholder": "eż. 0.2", + "topP": "top_p", + "topPPlaceholder": "eż. 0.95", + "slugPlaceholder": "my-agent", + "namePlaceholder": "L-aġent tiegħi", + "promptPlaceholder": "Int...", + "tools": "Għodda", + "visibility": "Viżibilità", + "visibilityPrimary": "Primarja — muri fil-picker", + "visibilitySubagent": "Subaġent - jissejjaħ biss minn oħrajn", + "visibilityAll": "Kollha - it-tnejn", + "saveFailed": "Ma setax isalva l-aġent", + "nameRequired": "Daħħal isem ta' aġent", + "slugRequired": "Daħħal aġent slug", + "slugInvalid": "Slug għandu jibda b'ittra u juża biss ittri żgħar, ċifri u sings", + "slugConflict": "Dik slug hija riservata minn aġent inkorporat", + "edit": "Editja l-aġent" + }, + "slashCommands": { + "title": "Slash jikkmanda", + "add": "Żid kmand slash", + "empty": "Għadu l-ebda kmand slash", + "name": "Isem tal-kmand", + "description": "Deskrizzjoni tal-kmand", + "template": "Mudell", + "namePlaceholder": "explain", + "templatePlaceholder": "Spjega $ARGUMENTS", + "agent": "Aġent", + "subtask": "Mexxi bħala subtask", + "edit": "Editja kmand slash", + "saveFailed": "Ma setax isalva l-kmand", + "nameRequired": "Daħħal isem tal-kmand", + "nameInvalid": "L-isem irid jibda b'ittra żgħira u juża biss ittri żgħar, ċifri u sings", + "templateRequired": "Daħħal mudell ta' kmand", + "nameConflict": "Dak l-isem huwa riżervat minn kmand inkorporat" + }, + "repoBindings": { + "title": "Profili default għal Repos", + "add": "Żid default", + "empty": "Ebda defaults konfigurati", + "emptyDescription": "Żid default biex tapplika profil awtomatikament meta tibda sessjonijiet għal repo.", + "repo": "Agħżel repożitorju...", + "profile": "Agħżel il-profil", + "remove": "Ħoll", + "private": "Privat", + "search": "Fittex repożitorji...", + "addedToast": "Profil default issettjat għal \"{{repo}}\"", + "addFailed": "Ma setax jissettja l-profil default", + "removedToast": "Profil default tneħħa għal \"{{repo}}\"", + "removeFailed": "Ma setgħetx tneħħi l-profil default", + "pinnedTitle": "Imqabbad mar-repożitorji", + "pinRepo": "Pin repo", + "pinnedEmpty": "Mhux ippinjat ma' xi repożitorji.", + "pinnedDescription": "Awto-loaded kull meta sessjoni timmira wieħed minn dawn ir-repożitorji.", + "pinnedToast": "Imqabbad ma' \"{{repo}}\"", + "pinFailed": "Repo ma setgħetx", + "unboundToast": "Repo mhux marbut", + "unbindFailed": "Ma setgħetx tħoll repo" + } } } diff --git a/apps/mobile/src/i18n/locales/my.json b/apps/mobile/src/i18n/locales/my.json index aa76f283c2f..085337b01a5 100644 --- a/apps/mobile/src/i18n/locales/my.json +++ b/apps/mobile/src/i18n/locales/my.json @@ -2191,7 +2191,33 @@ "continueFrom": "{{title}} ကို ဆက်လုပ်နေပါသည်။", "importingSession": "စက်ရှင်ကို တင်သွင်းနေသည်", "cliCannotContinue": "ဤ CLI ဖြင့် စက်ရှင်ကို ဆက်လုပ်၍မရပါ။ Cloud Agent သို့မဟုတ် အခြား CLI တစ်ခုကို ရွေးပါ။", - "importFailed": "ဤစက်ရှင်ကို တင်သွင်း၍မရပါ။" + "importFailed": "ဤစက်ရှင်ကို တင်သွင်း၍မရပါ။", + "pickProfile": "ပရိုဖိုင်ကို ရွေးပါ။", + "noProfile": "ပရိုဖိုင်မရှိပါ။", + "manageProfiles": "ပရိုဖိုင်များကို စီမံပါ။", + "noProfiles": "ပရိုဖိုင်များ မရှိသေးပါ။ ပတ်ဝန်းကျင်ပြောင်းလဲမှုများ၊ MCP ဆာဗာများနှင့် ကျွမ်းကျင်မှုများကို ထည့်သွင်းရန် တစ်ခုဖန်တီးပါ။", + "advancedConfig": "အဆင့်မြင့်ဖွဲ့စည်းမှု", + "advancedConfigDescription": "သိမ်းဆည်းထားသော ဖွဲ့စည်းမှုပုံစံများကို တင်ရန် ပရိုဖိုင်ကို ရွေးချယ်ပြီး အပိုဆောင်းပတ်ဝန်းကျင် ကိန်းရှင်များနှင့် စနစ်ထည့်သွင်းမှု ညွှန်ကြားချက်များကို ကိုယ်တိုင် စီစဉ်သတ်မှတ်ပါ", + "saveAsProfile": "Profile အဖြစ် သိမ်းဆည်းပါ။", + "saveProfileDescription": "လက်ရှိပတ်ဝန်းကျင် ကိန်းရှင်များနှင့် ပြန်လည်အသုံးပြုနိုင်သော ပရိုဖိုင်အဖြစ် သတ်မှတ်ခြင်းဆိုင်ရာ အမိန့်များကို သိမ်းဆည်းပါ။", + "profileSummary": "{{vars}} ပတ်၀န်းကျင် ကိန်းရှင်များ · {{commands}} စနစ်ထည့်သွင်းမှု အမိန့်များ", + "profileSaved": "ပရိုဖိုင် \"{{name}}\" သိမ်းပြီးပါပြီ။", + "organizationProfiles": "အဖွဲ့အစည်းပရိုဖိုင်များ", + "personalProfiles": "ကိုယ်ရေးကိုယ်တာ ပရိုဖိုင်များ", + "yourProfiles": "သင်၏ပရိုဖိုင်များ", + "failedToLoadProfiles": "ပရိုဖိုင်များကို တင်ရန် မအောင်မြင်ပါ။", + "nameTooLong": "ပရိုဖိုင်အမည်သည် ရှည်လွန်းသည်။", + "descriptionTooLong": "ဖော်ပြချက်သည် ရှည်လွန်းသည်။", + "secretsEncrypted": "လျှို့ဝှက်ချက်များကို သိမ်းဆည်းခြင်းမပြုမီ ကုဒ်ဝှက်ထားသည်။", + "profileActive": "ပရိုဖိုင် တက်ကြွနေပါသည်။", + "profilesActive": "ပရိုဖိုင်များ တက်ကြွနေပါသည်။", + "customConfigActive": "စိတ်ကြိုက် config တက်ကြွနေပါသည်။", + "configNeedsAttention": "Config သည် သတိထားရန် လိုအပ်သည်။", + "repoProfile": "Repo ပရိုဖိုင်", + "selectedProfile": "ရွေးချယ်ထားသော ပရိုဖိုင်", + "manualOverrides": "လူကိုယ်တိုင် အစားထိုးမှုများ", + "openSettingsToReview": "ပြန်လည်သုံးသပ်ရန် ဆက်တင်များကိုဖွင့်ပါ။", + "profileSelection": "ပရိုဖိုင်ရွေးချယ်မှု" }, "partRenderer": { "updatedFileCount_one": "ဖိုင် {{displayCount}} ခုကို ပြင်ဆင်ပြီးပါပြီ။", @@ -3224,5 +3250,180 @@ "multiplierError": "1 သို့မဟုတ် ထိုထက်ကြီးသော အမြှောက်ကို ထည့်ပါ", "permissionDenied": "အသုံးစရိတ် သတိပေးချက်များကို စီမံရန် သင့်တွင် ခွင့်ပြုချက် မရှိပါ။", "pushOffByCategory": "အသိပေးချက်များတွင် ပိတ်ထားသည်" + }, + "profiles": { + "title": "ကိုယ်ရေးအကျဉ်းများ", + "entrySubtitle": "စက်ရှင်များတစ်လျှောက် ပတ်ဝန်းကျင်ဆက်တင်များကို ပြန်သုံးပါ။", + "newProfile": "ပရိုဖိုင်အသစ်", + "emptyTitle": "ပရိုဖိုင်များ မရှိသေးပါ။", + "emptyDescription": "စက်ရှင်များတစ်လျှောက် ပတ်ဝန်းကျင် variable များ၊ စနစ်ထည့်သွင်းရန် ညွှန်ကြားချက်များနှင့် ကျွမ်းကျင်မှုများကို ပြန်လည်အသုံးပြုရန် ပရိုဖိုင်တစ်ခု ဖန်တီးပါ။", + "loadFailed": "ပရိုဖိုင်များကို တင်၍မရပါ။", + "counts": { + "vars": "{{count}} ကိန်းရှင်", + "mcp": "{{count}} MCP", + "skills": "{{count}} ကျွမ်းကျင်မှု", + "commands": "{{count}} အမိန့်", + "varsShort": "{{count}}က", + "skillsShort": "{{count}}ကျ", + "commandsShort": "{{count}}အ" + }, + "nameLabel": "ပရိုဖိုင်အမည်", + "namePlaceholder": "ဥပမာ နောက်ကွယ်မှ အမှားရှာပြင်ခြင်း။", + "descriptionLabel": "ပရိုဖိုင်ဖော်ပြချက်", + "descriptionPlaceholder": "ရွေးချယ်နိုင်သော ဖော်ပြချက်", + "ownerLabel": "ပရိုဖိုင်ပိုင်ရှင်", + "nameRequired": "ပရိုဖိုင်အမည်ကို ထည့်ပါ။", + "createAction": "ပရိုဖိုင်ဖန်တီးပါ။", + "createFailed": "ပရိုဖိုင်ကို ဖန်တီး၍မရပါ။", + "createdToast": "ပရိုဖိုင် \"{{name}}\" ဖန်တီးပြီးပါပြီ။", + "settingsSectionTitle": "ဖွဲ့စည်းမှု", + "defaultSectionTitle": "မူရင်းပရိုဖိုင်", + "personalDefaultDescription": "သိုလှောင်မှုတွင် ပင်ထိုးထားသော ပရိုဖိုင်တစ်ခုမရှိသည့်အခါ အလိုအလျောက်တင်သည်။", + "organizationDefaultDescription": "ကိုယ်ရေးကိုယ်တာ ပုံသေမရှိသော အဖွဲ့ဝင်များအတွက် အလိုအလျောက်တင်သည်။", + "setAsDefault": "မူရင်းအဖြစ် သတ်မှတ်ပါ။", + "removeDefault": "မူရင်းအတိုင်း ဖယ်ရှားပါ။", + "saveFailed": "ပရိုဖိုင်ကို သိမ်းဆည်း၍မရပါ။", + "deleteTitle": "ပရိုဖိုင်ကိုဖျက်ပါ။", + "deleteMessage": "၎င်းသည် ပရိုဖိုင်နှင့် ၎င်း၏ဆက်တင်များကို အပြီးအပိုင် ဖယ်ရှားသည်။ ဒါကို ပြန်ပြင်လို့ မရပါဘူး။", + "deleteFailed": "ပရိုဖိုင်ကို ဖျက်၍မရပါ။", + "deleteBlocked": "ဤပရိုဖိုင်ကို webhook အစပျိုးခြင်းဖြင့် အသုံးပြုသည်။ ၎င်းကို အစပျိုးမှုများမှ ဦးစွာဖယ်ရှားပါ။", + "deletedToast": "ပရိုဖိုင် \"{{name}}\" ဖျက်ပြီးပါပြီ။", + "variablesTitle": "ပတ်ဝန်းကျင်ပြောင်းလဲမှုများ", + "addVariable": "ကိန်းရှင်ကိုထည့်ပါ။", + "keyLabel": "သော့", + "keyTooLong": "သော့သည် စာလုံး 256 လုံး သို့မဟုတ် ထိုထက်နည်းရမည်", + "keyDuplicate": "ဤသော့သည် ပရိုဖိုင်တွင် ရှိပြီးဖြစ်သည်", + "valueLabel": "တန်ဖိုး", + "secretLabel": "လျှို့ဝှက်ချက်", + "secrets": { + "markSecret": "လျှို့ဝှက်ချက်အဖြစ် အမှတ်အသားပြုပါ။", + "masked": "ဝှက်ထားသောတန်ဖိုး", + "reveal": "တန်ဖိုးကိုထုတ်ဖော်ပါ။", + "newValuePlaceholder": "လျှို့ဝှက်တန်ဖိုးအသစ် ထည့်ပါ", + "valuePlaceholder": "လျှို့ဝှက်တန်ဖိုး (စာဝှက်ထားသည်)" + }, + "variablesEmpty": "ပြောင်းလွဲမှုများမရှိသေးပါ။", + "variablesSaveFailed": "ကိန်းရှင်ကို သိမ်းဆည်း၍မရပါ။", + "commandsTitle": "သတ်မှတ်အမိန့်များ", + "addCommand": "အမိန့်ကိုထည့်ပါ။", + "commandLabel": "အမိန့်ပေးသည်။", + "commandPlaceholder": "e.g. pnpm install", + "commandsEmpty": "စနစ်ထည့်သွင်းရန် အမိန့်များမရှိသေးပါ။", + "commandsSaveFailed": "စနစ်ထည့်သွင်းမှု အမိန့်များကို မသိမ်းဆည်းနိုင်ခဲ့ပါ။", + "moveUp": "အပေါ်ကိုရွှေ့ပါ။", + "moveDown": "အောက်သို့ရွှေ့ပါ။", + "skillsTitle": "ကျွမ်းကျင်မှု", + "addSkill": "ကျွမ်းကျင်မှုထည့်ပါ။", + "skillNameLabel": "အရည်အချင်းနာမည်", + "skillMarkdownLabel": "ကျွမ်းကျင်မှုအကြောင်းအရာ", + "skillsEmpty": "ကျွမ်းကျင်မှုမရှိသေးပါ။", + "skillNameRequired": "ကျွမ်းကျင်မှုအမည်ကို ထည့်သွင်းပါ။", + "skillNameInvalid": "ကျွမ်းကျင်မှုအမည်သည် အက္ခရာ သို့မဟုတ် ဂဏန်းဖြင့် စတင်ပြီး စာလုံးသေးများ၊ ဂဏန်းများနှင့် ဒက်ရှ်များကိုသာ အသုံးပြုရပါမည်။", + "skillContentRequired": "ကျွမ်းကျင်မှုအကြောင်းအရာ လိုအပ်ပါသည်။", + "skillSaveFailed": "ကျွမ်းကျင်မှုကို မသိမ်းဆည်းနိုင်ခဲ့ပါ။", + "skillsHint": "SKILL.md ကို ကူးထည့်လိုက်ပြီး ၎င်း၏အမည်ကို ဖြည့်စွက်ပါ။ ဆွဲချ၍မရဘဲ သင့်ဖုန်းမှကျွမ်းကျင်မှုတစ်ခုကို ထည့်ပါ။", + "skillEditTitle": "ကျွမ်းကျင်မှုကို တည်းဖြတ်ပါ။", + "mcp": { + "title": "MCP ဆာဗာများ", + "add": "MCP ဆာဗာကို ထည့်ပါ။", + "empty": "MCP ဆာဗာများ မရှိသေးပါ။", + "name": "ဆာဗာအမည်", + "type": "ဆာဗာအမျိုးအစား", + "localType": "ပြည်တွင်း", + "remoteType": "အဝေး", + "command": "ကွန်မန့်လိုင်း", + "url": "URL", + "environment": "ပတ်ဝန်းကျင် (JSON)", + "headers": "ခေါင်းစီးများ (JSON)", + "timeout": "ခဏပွဲရပ်ခြင်း", + "secretCount": "လျှို့ဝှက်ချက်", + "namePlaceholder": "my-server", + "commandPlaceholder": "npx @example/mcp-server", + "jsonPlaceholder": "{\n \"API_KEY\": \"sk-...\"\n}", + "nameRequired": "ဆာဗာအမည်ကို ထည့်သွင်းပါ။", + "nameInvalid": "အမည်သည် စာလုံးများ၊ နံပါတ်များ၊ အောက်ခံများနှင့် ဒက်ရှ်များကိုသာ အသုံးပြုနိုင်သည်။", + "commandRequired": "အမိန့်တစ်ခုထည့်ပါ။", + "commandTooLong": "အများဆုံး argument 50 ခုကို အသုံးပြုပါ၊ တစ်ခုလျှင် စာလုံး 500 လုံး", + "urlRequired": "URL တစ်ခုထည့်ပါ။", + "urlInvalid": "မှန်ကန်သော URL ကိုထည့်ပါ။", + "urlTooLong": "URL သည် စာလုံး 2048 လုံး သို့မဟုတ် ထိုထက်နည်း ဖြစ်ရမည်", + "jsonInvalid": "မှန်ကန်သော JSON ကို ထည့်ပါ — စာကြောင်းတန်ဖိုးများ၏ အရာဝတ္ထုတစ်ခု", + "jsonTooLarge": "အများဆုံး entry 50 ခုကို အသုံးပြုပါ၊ သော့များ 128 အထိနှင့် တန်ဖိုးများ စာလုံး 4096 အထိ", + "timeoutInvalid": "အချိန်ကုန်ဆုံးမှုသည် မီလီစက္ကန့်အရေအတွက်တစ်ခုလုံး ဖြစ်ရပါမည်။", + "edit": "MCP ဆာဗာကို တည်းဖြတ်ပါ။", + "saveFailed": "MCP ဆာဗာကို မသိမ်းဆည်းနိုင်ခဲ့ပါ။" + }, + "agents": { + "title": "ပရိုဖိုင် အေးဂျင့်များ", + "add": "ကိုယ်စားလှယ်ထည့်ပါ။", + "empty": "အေးဂျင့်မရှိသေးပါ။", + "name": "ကိုယ်စားလှယ်အမည်", + "slug": "အေးဂျင့် ပက်ကျိ", + "description": "ကိုယ်စားလှယ်ဖော်ပြချက်", + "model": "မော်ဒယ် ထပ်ရေးပါ။", + "modelPlaceholder": "စက်ရှင် ပုံသေကို သုံးပါ။", + "prompt": "စနစ်အချက်ပြ", + "steps": "အများဆုံး အဆင့်များ", + "stepsPlaceholder": "ဥပမာ 50", + "temperature": "အပူချိန်", + "temperaturePlaceholder": "ဥပမာ 0.2", + "topP": "top_p", + "topPPlaceholder": "ဥပမာ 0.95", + "slugPlaceholder": "my-agent", + "namePlaceholder": "ကျွန်ုပ်၏ အေးဂျင့်", + "promptPlaceholder": "မင်းဟာ...", + "tools": "ကိရိယာများ", + "visibility": "မြင်နိုင်စွမ်း", + "visibilityPrimary": "မူလတန်း—ရွေးချယ်မှုတွင် ပြထားသည်။", + "visibilitySubagent": "Subagent — အခြားသူများထံမှသာ ခေါ်ဆိုနိုင်သည်။", + "visibilityAll": "အားလုံး - နှစ်ခုလုံး", + "saveFailed": "ကိုယ်စားလှယ်ကို သိမ်းဆည်း၍မရပါ။", + "nameRequired": "ကိုယ်စားလှယ်အမည်ကို ထည့်ပါ။", + "slugRequired": "အေးဂျင့်ပက်ကျိထည့်ပါ။", + "slugInvalid": "Slug သည် အက္ခရာဖြင့် စတင်ရမည်ဖြစ်ပြီး စာလုံးသေးများ၊ ဂဏန်းများနှင့် ဒက်ရှ်များကိုသာ အသုံးပြုရပါမည်။", + "slugConflict": "ထိုမျှော့ကို တပ်ဆင်ထားသော အေးဂျင့်က သိမ်းဆည်းထားသည်။", + "edit": "အေးဂျင့်ကို တည်းဖြတ်ပါ။" + }, + "slashCommands": { + "title": "မျဉ်းစောင်းအမိန့်များ", + "add": "မျဥ်းစောင်းအမိန့်ကို ထည့်ပါ။", + "empty": "မျဥ်းစောင်းအမိန့်များ မရှိသေးပါ။", + "name": "အမိန့်အမည်", + "description": "Command ဖော်ပြချက်", + "template": "ပုံစံခွက်", + "namePlaceholder": "explain", + "templatePlaceholder": "$ARGUMENTS ကိုရှင်းပြပါ။", + "agent": "အေးဂျင့်", + "subtask": "လုပ်ငန်းခွဲအဖြစ် လုပ်ဆောင်ပါ။", + "edit": "မျဥ်းစောင်းအမိန့်ကို တည်းဖြတ်ပါ။", + "saveFailed": "အမိန့်ကို သိမ်းဆည်း၍မရပါ။", + "nameRequired": "အမိန့်အမည်တစ်ခုထည့်ပါ။", + "nameInvalid": "အမည်သည် စာလုံးအသေးဖြင့် စတင်ရမည်ဖြစ်ပြီး စာလုံးသေးများ၊ ဂဏန်းများနှင့် ဒက်ရှ်များကိုသာ အသုံးပြုရပါမည်။", + "templateRequired": "အမိန့်ပေးပုံစံကို ထည့်သွင်းပါ။", + "nameConflict": "ထိုအမည်ကို built-in command ဖြင့် သိမ်းဆည်းထားသည်။" + }, + "repoBindings": { + "title": "Repos အတွက် မူရင်းပရိုဖိုင်များ", + "add": "ပုံသေထည့်ပါ။", + "empty": "ပုံသေသတ်မှတ်မှု မရှိပါ။", + "emptyDescription": "repo အတွက် ဆက်ရှင်များ စတင်သောအခါ ပရိုဖိုင်တစ်ခု အလိုအလျောက် အသုံးချရန် ပုံသေကို ထည့်ပါ။", + "repo": "သိုလှောင်မှုကို ရွေးပါ...", + "profile": "ပရိုဖိုင်ကို ရွေးပါ။", + "remove": "စည်းဖြုတ်ပါ။", + "private": "သီးသန့်", + "search": "သိုလှောင်မှုများကို ရှာဖွေပါ...", + "addedToast": "\"{{repo}}\" အတွက် မူရင်းပရိုဖိုင် သတ်မှတ်ပြီးပါပြီ။", + "addFailed": "မူရင်းပရိုဖိုင်ကို သတ်မှတ်၍မရပါ။", + "removedToast": "\"{{repo}}\" အတွက် မူရင်းပရိုဖိုင် ဖယ်ရှားပြီးပါပြီ။", + "removeFailed": "မူရင်းပရိုဖိုင်ကို ဖယ်ရှား၍မရပါ။", + "pinnedTitle": "သိုလှောင်ရုံများသို့ ပင်ထိုးထားသည်။", + "pinRepo": "repo ကို ပင်ထိုးပါ။", + "pinnedEmpty": "မည်သည့် သိုလှောင်ရုံများတွင် ပင်မတွဲထားပါ။", + "pinnedDescription": "စက်ရှင်တစ်ခုသည် ဤသိုလှောင်ရာနေရာများထဲမှ တစ်ခုကို ပစ်မှတ်ထားသည့်အခါတိုင်း အလိုအလျောက်တင်သည်။", + "pinnedToast": "\"{{repo}}\" သို့ ပင်ထိုးပြီးပါပြီ။", + "pinFailed": "repo ကို ပင်ထိုး၍မရပါ။", + "unboundToast": "Repo ချိတ်ဆက်မှုကို ဖြုတ်ပြီးပါပြီ။", + "unbindFailed": "repo ကို ပြန်တွဲ၍ မရပါ။" + } } } diff --git a/apps/mobile/src/i18n/locales/nb.json b/apps/mobile/src/i18n/locales/nb.json index 74b064c83da..9b2a71a904b 100644 --- a/apps/mobile/src/i18n/locales/nb.json +++ b/apps/mobile/src/i18n/locales/nb.json @@ -2191,7 +2191,33 @@ "continueFrom": "Fortsetter økten {{title}}.", "importingSession": "Importerer økten", "cliCannotContinue": "Denne CLI-en kan ikke fortsette en økt. Velg Cloud Agent eller en annen CLI.", - "importFailed": "Kunne ikke importere denne økten." + "importFailed": "Kunne ikke importere denne økten.", + "pickProfile": "Velg en profil", + "noProfile": "Ingen profil", + "manageProfiles": "Administrer profiler", + "noProfiles": "Ingen profiler ennå. Lag en for å legge til miljøvariabler, MCP-servere og ferdigheter.", + "advancedConfig": "Avansert konfigurasjon", + "advancedConfigDescription": "Velg en profil for å laste lagrede konfigurasjoner, og konfigurer ytterligere miljøvariabler og oppsettkommandoer manuelt", + "saveAsProfile": "Lagre som profil", + "saveProfileDescription": "Lagre gjeldende miljøvariabler og oppsettkommandoer som en gjenbrukbar profil.", + "profileSummary": "{{vars}} miljøvariabler · {{commands}} oppsettkommandoer", + "profileSaved": "Profilen «{{name}}» er lagret", + "organizationProfiles": "Organisasjonsprofiler", + "personalProfiles": "Personlige profiler", + "yourProfiles": "Dine profiler", + "failedToLoadProfiles": "Kunne ikke laste inn profiler", + "nameTooLong": "Profilnavnet er for langt", + "descriptionTooLong": "Beskrivelsen er for lang", + "secretsEncrypted": "Hemmeligheter krypteres før lagring.", + "profileActive": "Profilen er aktiv", + "profilesActive": "Profiler aktive", + "customConfigActive": "Egendefinert konfigurasjon aktiv", + "configNeedsAttention": "Konfigurasjon trenger oppmerksomhet", + "repoProfile": "Repo-profil", + "selectedProfile": "Valgt profil", + "manualOverrides": "Manuelle overstyringer", + "openSettingsToReview": "Åpne Innstillinger for å se gjennom", + "profileSelection": "Valg av profil" }, "partRenderer": { "updatedFileCount_one": "{{displayCount}} fil er oppdatert.", @@ -3224,5 +3250,180 @@ "multiplierError": "Angi en multiplikator på 1 eller mer", "permissionDenied": "Du har ikke tillatelse til å administrere forbruksvarsler.", "pushOffByCategory": "Av i Varsler" + }, + "profiles": { + "title": "Profiler", + "entrySubtitle": "Gjenbruk miljøinnstillinger på tvers av økter", + "newProfile": "Ny profil", + "emptyTitle": "Ingen profiler ennå", + "emptyDescription": "Opprett en profil for å gjenbruke miljøvariabler, oppsettkommandoer og ferdigheter på tvers av økter.", + "loadFailed": "Kunne ikke laste inn profiler", + "counts": { + "vars": "{{count}} variabler", + "mcp": "{{count}} MCP", + "skills": "{{count}} ferdigheter", + "commands": "{{count}} kommandoer", + "varsShort": "{{count}}var", + "skillsShort": "{{count}}fer", + "commandsShort": "{{count}}kom" + }, + "nameLabel": "Profilnavn", + "namePlaceholder": "f.eks. Backend-feilsøking", + "descriptionLabel": "Profilbeskrivelse", + "descriptionPlaceholder": "Valgfri beskrivelse", + "ownerLabel": "Profileier", + "nameRequired": "Skriv inn et profilnavn", + "createAction": "Opprett profil", + "createFailed": "Kunne ikke opprette profil", + "createdToast": "Profilen «{{name}}» er opprettet", + "settingsSectionTitle": "Konfigurasjon", + "defaultSectionTitle": "Standard profil", + "personalDefaultDescription": "Lastes automatisk når ingen repository har en festet profil.", + "organizationDefaultDescription": "Lastes automatisk for medlemmer som ikke har noen personlig standard.", + "setAsDefault": "Sett som standard", + "removeDefault": "Fjern som standard", + "saveFailed": "Kunne ikke lagre profilen", + "deleteTitle": "Slett profil", + "deleteMessage": "Dette fjerner profilen og dens innstillinger permanent. Dette kan ikke angres.", + "deleteFailed": "Kunne ikke slette profilen", + "deleteBlocked": "Denne profilen brukes av en webhook-utløser. Fjern den fra disse utløserne først.", + "deletedToast": "Profilen «{{name}}» er slettet", + "variablesTitle": "Miljøvariabler", + "addVariable": "Legg til variabel", + "keyLabel": "Nøkkel", + "keyTooLong": "Nøkkelen kan ha høyst 256 tegn", + "keyDuplicate": "Denne nøkkelen finnes allerede i profilen", + "valueLabel": "Verdi", + "secretLabel": "Hemmelig", + "secrets": { + "markSecret": "Merk som hemmelig", + "masked": "Skjult verdi", + "reveal": "Avslør verdi", + "newValuePlaceholder": "Skriv inn ny hemmelig verdi", + "valuePlaceholder": "Hemmelig verdi (kryptert)" + }, + "variablesEmpty": "Ingen variabler ennå", + "variablesSaveFailed": "Kunne ikke lagre variabelen", + "commandsTitle": "Oppsettkommandoer", + "addCommand": "Legg til kommando", + "commandLabel": "Kommando", + "commandPlaceholder": "e.g. pnpm install", + "commandsEmpty": "Ingen oppsettkommandoer ennå", + "commandsSaveFailed": "Kunne ikke lagre oppsettkommandoer", + "moveUp": "Flytt opp", + "moveDown": "Flytt ned", + "skillsTitle": "Ferdigheter", + "addSkill": "Legg til ferdigheter", + "skillNameLabel": "Ferdighetsnavn", + "skillMarkdownLabel": "Ferdighetsinnhold", + "skillsEmpty": "Ingen ferdigheter ennå", + "skillNameRequired": "Skriv inn et ferdighetsnavn", + "skillNameInvalid": "Ferdighetsnavn må starte med en bokstav eller et siffer og kun bruke små bokstaver, sifre og bindestreker", + "skillContentRequired": "Ferdighetsinnhold kreves", + "skillSaveFailed": "Kunne ikke lagre ferdigheter", + "skillsHint": "Lim inn en SKILL.md og navnet fylles ut; legg til en ferdighet fra telefonen din uten å dra og slipp.", + "skillEditTitle": "Rediger ferdighet", + "mcp": { + "title": "MCP-servere", + "add": "Legg til MCP-server", + "empty": "Ingen MCP-servere ennå", + "name": "Servernavn", + "type": "Servertype", + "localType": "Lokalt", + "remoteType": "Fjernkontroll", + "command": "Kommandolinje", + "url": "URL", + "environment": "Miljø (JSON)", + "headers": "Overskrifter (JSON)", + "timeout": "Tidsavbrudd", + "secretCount": "Hemmelig telling", + "namePlaceholder": "my-server", + "commandPlaceholder": "npx @example/mcp-server", + "jsonPlaceholder": "{\n \"API_KEY\": \"sk-...\"\n}", + "nameRequired": "Skriv inn et servernavn", + "nameInvalid": "Navn kan bare bruke bokstaver, tall, understreker og bindestreker", + "commandRequired": "Skriv inn en kommando", + "commandTooLong": "Bruk maks 50 argumenter på 500 tegn hver", + "urlRequired": "Skriv inn en URL", + "urlInvalid": "Skriv inn en gyldig URL", + "urlTooLong": "URL-en kan ha høyst 2048 tegn", + "jsonInvalid": "Skriv inn gyldig JSON - et objekt med strengverdier", + "jsonTooLarge": "Bruk maks 50 oppføringer, nøkler opptil 128 og verdier opptil 4096 tegn", + "timeoutInvalid": "Tidsavbrudd må være et helt antall millisekunder", + "edit": "Rediger MCP-server", + "saveFailed": "Kunne ikke lagre MCP-serveren" + }, + "agents": { + "title": "Profil agenter", + "add": "Legg til agent", + "empty": "Ingen agenter ennå", + "name": "Agentnavn", + "slug": "Agentsnegl", + "description": "Agentbeskrivelse", + "model": "Modelloverstyring", + "modelPlaceholder": "Bruk standard sesjon", + "prompt": "Systemmelding", + "steps": "Maks. trinn", + "stepsPlaceholder": "f.eks. 50", + "temperature": "Temperatur", + "temperaturePlaceholder": "f.eks. 0.2", + "topP": "top_p", + "topPPlaceholder": "f.eks. 0.95", + "slugPlaceholder": "my-agent", + "namePlaceholder": "Agenten min", + "promptPlaceholder": "Du er en...", + "tools": "Verktøy", + "visibility": "Synlighet", + "visibilityPrimary": "Primær – vist i velgeren", + "visibilitySubagent": "Subagent — kan kun ringes opp fra andre", + "visibilityAll": "Alle - begge", + "saveFailed": "Kunne ikke lagre agenten", + "nameRequired": "Skriv inn et agentnavn", + "slugRequired": "Skriv inn en agentsnegl", + "slugInvalid": "Slug må starte med en bokstav og kun bruke små bokstaver, sifre og bindestreker", + "slugConflict": "Den sneglen er reservert av en innebygd agent", + "edit": "Rediger agent" + }, + "slashCommands": { + "title": "Slash-kommandoer", + "add": "Legg til skråstrek-kommando", + "empty": "Ingen skråstrekkommandoer ennå", + "name": "Kommandonavn", + "description": "Kommandobeskrivelse", + "template": "Mal", + "namePlaceholder": "explain", + "templatePlaceholder": "Forklar $ARGUMENTS", + "agent": "Agent", + "subtask": "Kjør som deloppgave", + "edit": "Rediger skråstrek-kommando", + "saveFailed": "Kunne ikke lagre kommandoen", + "nameRequired": "Skriv inn et kommandonavn", + "nameInvalid": "Navnet må begynne med en liten bokstav og kun bruke små bokstaver, sifre og bindestreker", + "templateRequired": "Skriv inn en kommandomal", + "nameConflict": "Dette navnet er reservert av en innebygd kommando" + }, + "repoBindings": { + "title": "Standardprofiler for Repos", + "add": "Legg til standard", + "empty": "Ingen standardinnstillinger er konfigurert", + "emptyDescription": "Legg til en standard for automatisk å bruke en profil når du starter økter for en repo.", + "repo": "Velg depot...", + "profile": "Velg profil", + "remove": "Avbind", + "private": "Privat", + "search": "Søk i depoter...", + "addedToast": "Standard profil satt for «{{repo}}»", + "addFailed": "Kunne ikke angi standardprofil", + "removedToast": "Standardprofilen er fjernet for «{{repo}}»", + "removeFailed": "Kunne ikke fjerne standardprofilen", + "pinnedTitle": "Festet til repositorier", + "pinRepo": "Fest en repo", + "pinnedEmpty": "Ikke festet til noen repositorier.", + "pinnedDescription": "Lastes automatisk når en økt målretter mot et av disse depotene.", + "pinnedToast": "Festet til «{{repo}}»", + "pinFailed": "Kunne ikke feste repo", + "unboundToast": "Repo ubundet", + "unbindFailed": "Kunne ikke løsne repoen" + } } } diff --git a/apps/mobile/src/i18n/locales/ne.json b/apps/mobile/src/i18n/locales/ne.json index a336d337efd..14b42117939 100644 --- a/apps/mobile/src/i18n/locales/ne.json +++ b/apps/mobile/src/i18n/locales/ne.json @@ -2191,7 +2191,33 @@ "continueFrom": "{{title}} जारी राखिँदैछ।", "importingSession": "सत्र आयात गर्दै", "cliCannotContinue": "यो CLI ले सत्र जारी राख्न सक्दैन। Cloud Agent वा अर्को CLI छान्नुहोस्।", - "importFailed": "यो सत्र आयात गर्न सकिएन।" + "importFailed": "यो सत्र आयात गर्न सकिएन।", + "pickProfile": "प्रोफाइल छान्नुहोस्", + "noProfile": "प्रोफाइल छैन", + "manageProfiles": "प्रोफाइलहरू प्रबन्ध गर्नुहोस्", + "noProfiles": "अहिलेसम्म कुनै प्रोफाइल छैन। वातावरणीय चरहरू, MCP सर्भरहरू, र सीपहरू थप्न एउटा सिर्जना गर्नुहोस्।", + "advancedConfig": "उन्नत कन्फिगरेसन", + "advancedConfigDescription": "बचत गरिएका कन्फिगरेसनहरू लोड गर्न प्रोफाइल चयन गर्नुहोस्, र अतिरिक्त वातावरण चरहरू र सेटअप आदेशहरू म्यानुअल रूपमा कन्फिगर गर्नुहोस्।", + "saveAsProfile": "प्रोफाइलको रूपमा बचत गर्नुहोस्", + "saveProfileDescription": "पुन: प्रयोज्य प्रोफाइलको रूपमा हालको वातावरण चर र सेटअप आदेशहरू बचत गर्नुहोस्।", + "profileSummary": "{{vars}} वातावरण चर · {{commands}} सेटअप आदेशहरू", + "profileSaved": "प्रोफाइल \"{{name}}\" बचत गरियो", + "organizationProfiles": "संगठन प्रोफाइल", + "personalProfiles": "व्यक्तिगत प्रोफाइलहरू", + "yourProfiles": "तपाईंको प्रोफाइलहरू", + "failedToLoadProfiles": "प्रोफाइलहरू लोड गर्न असफल भयो", + "nameTooLong": "प्रोफाइल नाम धेरै लामो छ", + "descriptionTooLong": "वर्णन धेरै लामो छ", + "secretsEncrypted": "गोप्यहरू भण्डारण गर्नु अघि गुप्तिकरण गरिएका छन्।", + "profileActive": "प्रोफाइल सक्रिय", + "profilesActive": "सक्रिय प्रोफाइलहरू", + "customConfigActive": "अनुकूलन कन्फिगरेसन सक्रिय", + "configNeedsAttention": "कन्फिगरेसनलाई ध्यान चाहिन्छ", + "repoProfile": "Repo प्रोफाइल", + "selectedProfile": "चयन गरिएको प्रोफाइल", + "manualOverrides": "म्यानुअल ओभरराइडहरू", + "openSettingsToReview": "समीक्षा गर्न सेटिङहरू खोल्नुहोस्", + "profileSelection": "प्रोफाइल चयन" }, "partRenderer": { "updatedFileCount_one": "{{displayCount}} फाइल अद्यावधिक गरियो।", @@ -3224,5 +3250,180 @@ "multiplierError": "1 वा त्यसभन्दा बढीको गुणक लेख्नुहोस्", "permissionDenied": "तपाईंसँग खर्चका चेतावनीहरू व्यवस्थापन गर्ने अनुमति छैन।", "pushOffByCategory": "सूचनाहरूमा बन्द" + }, + "profiles": { + "title": "प्रोफाइलहरू", + "entrySubtitle": "सत्रहरूमा वातावरण सेटिङहरू पुन: प्रयोग गर्नुहोस्", + "newProfile": "नयाँ प्रोफाइल", + "emptyTitle": "अहिलेसम्म कुनै प्रोफाइल छैन", + "emptyDescription": "वातावरण चरहरू, सेटअप आदेशहरू, र सत्रहरूमा सीपहरू पुन: प्रयोग गर्न प्रोफाइल सिर्जना गर्नुहोस्।", + "loadFailed": "प्रोफाइलहरू लोड गर्न सकिएन", + "counts": { + "vars": "{{count}} चर", + "mcp": "{{count}} MCP", + "skills": "{{count}} सीप", + "commands": "{{count}} आदेश", + "varsShort": "{{count}}च", + "skillsShort": "{{count}}स", + "commandsShort": "{{count}}आ" + }, + "nameLabel": "प्रोफाइल नाम", + "namePlaceholder": "जस्तै ब्याकएन्ड डिबगिङ", + "descriptionLabel": "प्रोफाइल विवरण", + "descriptionPlaceholder": "वैकल्पिक विवरण", + "ownerLabel": "प्रोफाइल मालिक", + "nameRequired": "प्रोफाइल नाम प्रविष्ट गर्नुहोस्", + "createAction": "प्रोफाइल बनाउनुहोस्", + "createFailed": "प्रोफाइल सिर्जना गर्न सकिएन", + "createdToast": "प्रोफाइल \"{{name}}\" सिर्जना गरियो", + "settingsSectionTitle": "कन्फिगरेसन", + "defaultSectionTitle": "पूर्वनिर्धारित प्रोफाइल", + "personalDefaultDescription": "कुनै भण्डारमा पिन गरिएको प्रोफाइल नभएको बेला स्वतः लोड हुन्छ।", + "organizationDefaultDescription": "कुनै व्यक्तिगत पूर्वनिर्धारित सदस्यहरूको लागि स्वत: लोड।", + "setAsDefault": "पूर्वनिर्धारित रूपमा सेट गर्नुहोस्", + "removeDefault": "पूर्वनिर्धारित रूपमा हटाउनुहोस्", + "saveFailed": "प्रोफाइल सुरक्षित गर्न सकिएन", + "deleteTitle": "प्रोफाइल मेटाउनुहोस्", + "deleteMessage": "यसले स्थायी रूपमा प्रोफाइल र यसको सेटिङहरू हटाउँछ। यसलाई अन्डू गर्न सकिँदैन।", + "deleteFailed": "प्रोफाइल मेटाउन सकिएन", + "deleteBlocked": "यो प्रोफाइल वेबहुक ट्रिगर द्वारा प्रयोग गरिन्छ। यसलाई पहिले ती ट्रिगरहरूबाट हटाउनुहोस्।", + "deletedToast": "प्रोफाइल \"{{name}}\" मेटाइयो", + "variablesTitle": "वातावरण चर", + "addVariable": "चर थप्नुहोस्", + "keyLabel": "कुञ्जी", + "keyTooLong": "कुञ्जी 256 अक्षर वा कम हुनुपर्छ", + "keyDuplicate": "यो कुञ्जी पहिले नै प्रोफाइलमा छ", + "valueLabel": "मूल्य", + "secretLabel": "गोप्य", + "secrets": { + "markSecret": "गोप्य रूपमा चिन्ह लगाउनुहोस्", + "masked": "लुकेको मूल्य", + "reveal": "मूल्य प्रकट गर्नुहोस्", + "newValuePlaceholder": "नयाँ गोप्य मान प्रविष्ट गर्नुहोस्", + "valuePlaceholder": "गोप्य मान (इन्क्रिप्ट गरिएको)" + }, + "variablesEmpty": "अहिलेसम्म कुनै चर छैन", + "variablesSaveFailed": "चर बचत गर्न सकिएन", + "commandsTitle": "आदेशहरू सेटअप गर्नुहोस्", + "addCommand": "आदेश थप्नुहोस्", + "commandLabel": "आदेश", + "commandPlaceholder": "e.g. pnpm install", + "commandsEmpty": "अहिलेसम्म कुनै सेटअप आदेशहरू छैनन्", + "commandsSaveFailed": "सेटअप आदेशहरू बचत गर्न सकिएन", + "moveUp": "माथि सार्नुहोस्", + "moveDown": "तल सार्नुहोस्", + "skillsTitle": "सीपहरू", + "addSkill": "कौशल थप्नुहोस्", + "skillNameLabel": "कौशल नाम", + "skillMarkdownLabel": "कौशल सामग्री", + "skillsEmpty": "अझै सीप छैन", + "skillNameRequired": "कौशल नाम प्रविष्ट गर्नुहोस्", + "skillNameInvalid": "सीपको नाम अक्षर वा अंकबाट सुरु हुनुपर्छ र सानो अक्षर, अंक र ड्यास मात्र प्रयोग गर्नुपर्छ।", + "skillContentRequired": "सीप सामग्री आवश्यक छ", + "skillSaveFailed": "कौशल बचाउन सकेन", + "skillsHint": "SKILL.md टाँस्नुहोस् र यसको नाम भरिन्छ; ड्र्याग र ड्रप बिना आफ्नो फोनबाट कौशल थप्नुहोस्।", + "skillEditTitle": "कौशल सम्पादन गर्नुहोस्", + "mcp": { + "title": "MCP सर्भरहरू", + "add": "MCP सर्भर थप्नुहोस्", + "empty": "अहिलेसम्म कुनै MCP सर्भरहरू छैनन्", + "name": "सर्भर नाम", + "type": "सर्भर प्रकार", + "localType": "स्थानीय", + "remoteType": "रिमोट", + "command": "कमाण्ड लाइन", + "url": "URL", + "environment": "वातावरण (JSON)", + "headers": "हेडर (JSON)", + "timeout": "टाइमआउट", + "secretCount": "गोप्य गणना", + "namePlaceholder": "my-server", + "commandPlaceholder": "npx @example/mcp-server", + "jsonPlaceholder": "{\n \"API_KEY\": \"sk-...\"\n}", + "nameRequired": "सर्भर नाम प्रविष्ट गर्नुहोस्", + "nameInvalid": "नामले अक्षर, संख्या, अन्डरस्कोर र ड्यास मात्र प्रयोग गर्न सक्छ", + "commandRequired": "आदेश प्रविष्ट गर्नुहोस्", + "commandTooLong": "बढीमा 50 आर्गुमेन्ट प्रयोग गर्नुहोस्, प्रत्येक 500 वर्ण", + "urlRequired": "URL प्रविष्ट गर्नुहोस्", + "urlInvalid": "मान्य URL प्रविष्ट गर्नुहोस्", + "urlTooLong": "URL 2048 वर्ण वा कम हुनुपर्छ", + "jsonInvalid": "मान्य JSON प्रविष्ट गर्नुहोस् — स्ट्रिङ मानहरूको वस्तु", + "jsonTooLarge": "बढीमा 50 प्रविष्टि प्रयोग गर्नुहोस्, कुञ्जीहरू 128 सम्म र मानहरू 4096 वर्णसम्म", + "timeoutInvalid": "टाइमआउट मिलिसेकेन्डको पूर्ण संख्या हुनुपर्छ", + "edit": "MCP सर्भर सम्पादन गर्नुहोस्", + "saveFailed": "MCP सर्भर सुरक्षित गर्न सकिएन" + }, + "agents": { + "title": "प्रोफाइल एजेन्टहरू", + "add": "एजेन्ट थप्नुहोस्", + "empty": "अहिलेसम्म कुनै एजेन्टहरू छैनन्", + "name": "एजेन्ट नाम", + "slug": "एजेन्ट स्लग", + "description": "एजेन्ट विवरण", + "model": "मोडेल ओभरराइड", + "modelPlaceholder": "पूर्वनिर्धारित सत्र प्रयोग गर्नुहोस्", + "prompt": "प्रणाली प्रम्प्ट", + "steps": "अधिकतम चरणहरू", + "stepsPlaceholder": "जस्तै 50", + "temperature": "तापक्रम", + "temperaturePlaceholder": "जस्तै 0.2", + "topP": "top_p", + "topPPlaceholder": "जस्तै 0.95", + "slugPlaceholder": "my-agent", + "namePlaceholder": "मेरो एजेन्ट", + "promptPlaceholder": "तिमी एक...", + "tools": "उपकरणहरू", + "visibility": "दृश्यता", + "visibilityPrimary": "प्राथमिक — पिकरमा देखाइएको छ", + "visibilitySubagent": "Subagent - अरूबाट मात्र कल गर्न मिल्ने", + "visibilityAll": "सबै - दुबै", + "saveFailed": "एजेन्ट सुरक्षित गर्न सकिएन", + "nameRequired": "एजेन्टको नाम प्रविष्ट गर्नुहोस्", + "slugRequired": "एजेन्ट स्लग प्रविष्ट गर्नुहोस्", + "slugInvalid": "स्लग एउटा अक्षरबाट सुरु हुनुपर्छ र सानो अक्षर, अंक र ड्यासहरू मात्र प्रयोग गर्नुपर्छ", + "slugConflict": "त्यो स्लग बिल्ट-इन एजेन्ट द्वारा आरक्षित छ", + "edit": "एजेन्ट सम्पादन गर्नुहोस्" + }, + "slashCommands": { + "title": "स्ल्याश आदेशहरू", + "add": "स्ल्याश आदेश थप्नुहोस्", + "empty": "अहिलेसम्म कुनै स्ल्याश आदेशहरू छैनन्", + "name": "आदेश नाम", + "description": "आदेश विवरण", + "template": "टेम्प्लेट", + "namePlaceholder": "explain", + "templatePlaceholder": "$ARGUMENTS व्याख्या गर्नुहोस्", + "agent": "एजेन्ट", + "subtask": "सबटास्कको रूपमा चलाउनुहोस्", + "edit": "स्ल्याश आदेश सम्पादन गर्नुहोस्", + "saveFailed": "आदेश सुरक्षित गर्न सकिएन", + "nameRequired": "आदेश नाम प्रविष्ट गर्नुहोस्", + "nameInvalid": "नाम सानो अक्षरबाट सुरु हुनुपर्छ र सानो अक्षर, अंक र ड्यास मात्र प्रयोग गर्नुपर्छ", + "templateRequired": "आदेश टेम्प्लेट प्रविष्ट गर्नुहोस्", + "nameConflict": "त्यो नाम एक निर्मित आदेश द्वारा आरक्षित छ" + }, + "repoBindings": { + "title": "रिपोसका लागि पूर्वनिर्धारित प्रोफाइलहरू", + "add": "पूर्वनिर्धारित थप्नुहोस्", + "empty": "कुनै पूर्वनिर्धारित कन्फिगर गरिएको छैन", + "emptyDescription": "रिपोको लागि सत्रहरू सुरु गर्दा स्वचालित रूपमा प्रोफाइल लागू गर्न पूर्वनिर्धारित थप्नुहोस्।", + "repo": "भण्डार चयन गर्नुहोस्...", + "profile": "प्रोफाइल चयन गर्नुहोस्", + "remove": "अनबाइन्ड गर्नुहोस्", + "private": "निजी", + "search": "भण्डारहरू खोज्नुहोस्...", + "addedToast": "\"{{repo}}\" को लागि पूर्वनिर्धारित प्रोफाइल सेट", + "addFailed": "पूर्वनिर्धारित प्रोफाइल सेट गर्न सकिएन", + "removedToast": "\"{{repo}}\" को लागि पूर्वनिर्धारित प्रोफाइल हटाइयो", + "removeFailed": "पूर्वनिर्धारित प्रोफाइल हटाउन सकिएन", + "pinnedTitle": "भण्डारहरूमा पिन गरियो", + "pinRepo": "रिपो पिन गर्नुहोस्", + "pinnedEmpty": "कुनै पनि भण्डारहरूमा पिन गरिएको छैन।", + "pinnedDescription": "यी भण्डारहरू मध्ये कुनै एक सत्रलाई लक्षित गर्दा स्वत: लोड हुन्छ।", + "pinnedToast": "\"{{repo}}\" मा पिन गरियो", + "pinFailed": "रिपो पिन गर्न सकिएन", + "unboundToast": "रिपो अनबाउन्ड", + "unbindFailed": "रिपो अनबाइन्ड गर्न सकेन" + } } } diff --git a/apps/mobile/src/i18n/locales/nl.json b/apps/mobile/src/i18n/locales/nl.json index cc507efafdc..99a9a11b23f 100644 --- a/apps/mobile/src/i18n/locales/nl.json +++ b/apps/mobile/src/i18n/locales/nl.json @@ -1687,7 +1687,33 @@ "continueFrom": "Je gaat verder met {{title}}.", "importingSession": "Sessie importeren…", "cliCannotContinue": "Deze CLI kan geen sessie voortzetten. Kies Cloud Agent of een andere CLI.", - "importFailed": "Deze sessie kon niet worden geïmporteerd." + "importFailed": "Deze sessie kon niet worden geïmporteerd.", + "pickProfile": "Kies een profiel", + "noProfile": "Geen profiel", + "manageProfiles": "Beheer profielen", + "noProfiles": "Nog geen profielen. Maak er een om omgevingsvariabelen, MCP-servers en vaardigheden toe te voegen.", + "advancedConfig": "Geavanceerde configuratie", + "advancedConfigDescription": "Selecteer een profiel om opgeslagen configuraties te laden en configureer handmatig aanvullende omgevingsvariabelen en installatieopdrachten", + "saveAsProfile": "Opslaan als profiel", + "saveProfileDescription": "Sla de huidige omgevingsvariabelen en installatieopdrachten op als een herbruikbaar profiel.", + "profileSummary": "{{vars}} omgevingsvariabelen · {{commands}} installatieopdrachten", + "profileSaved": "Profiel \"{{name}}\" opgeslagen", + "organizationProfiles": "Organisatieprofielen", + "personalProfiles": "Persoonlijke profielen", + "yourProfiles": "Jouw profielen", + "failedToLoadProfiles": "Kan profielen niet laden", + "nameTooLong": "Profielnaam is te lang", + "descriptionTooLong": "Beschrijving is te lang", + "secretsEncrypted": "Geheimen worden gecodeerd voordat ze worden opgeslagen.", + "profileActive": "Profiel actief", + "profilesActive": "Profielen actief", + "customConfigActive": "Aangepaste configuratie actief", + "configNeedsAttention": "Configuratie heeft aandacht nodig", + "repoProfile": "Repo-profiel", + "selectedProfile": "Geselecteerd profiel", + "manualOverrides": "Handmatige overschrijvingen", + "openSettingsToReview": "Open Instellingen om te bekijken", + "profileSelection": "Profiel selectie" }, "partRenderer": { "updatedFileCount_one": "Er is {{displayCount}} bestand bijgewerkt.", @@ -3224,5 +3250,180 @@ "multiplierError": "Voer een vermenigvuldiger van 1 of meer in", "permissionDenied": "Je hebt geen toestemming om bestedingswaarschuwingen te beheren.", "pushOffByCategory": "Uit in Meldingen" + }, + "profiles": { + "title": "Profielen", + "entrySubtitle": "Hergebruik omgevingsinstellingen tussen sessies", + "newProfile": "Nieuw profiel", + "emptyTitle": "Nog geen profielen", + "emptyDescription": "Maak een profiel aan om omgevingsvariabelen, installatieopdrachten en vaardigheden tijdens sessies opnieuw te gebruiken.", + "loadFailed": "Kan profielen niet laden", + "counts": { + "vars": "{{count}} variabelen", + "mcp": "{{count}} MCP", + "skills": "{{count}} vaardigheden", + "commands": "{{count}} opdrachten", + "varsShort": "{{count}}var", + "skillsShort": "{{count}}vaa", + "commandsShort": "{{count}}opd" + }, + "nameLabel": "Profielnaam", + "namePlaceholder": "bijv. Foutopsporing in de backend", + "descriptionLabel": "Profielbeschrijving", + "descriptionPlaceholder": "Optionele beschrijving", + "ownerLabel": "Profieleigenaar", + "nameRequired": "Voer een profielnaam in", + "createAction": "Profiel aanmaken", + "createFailed": "Kan profiel niet maken", + "createdToast": "Profiel \"{{name}}\" gemaakt", + "settingsSectionTitle": "Configuratie", + "defaultSectionTitle": "Standaardprofiel", + "personalDefaultDescription": "Automatisch geladen wanneer geen enkele repository een vastgezet profiel heeft.", + "organizationDefaultDescription": "Automatisch geladen voor leden die geen persoonlijke standaard hebben.", + "setAsDefault": "Als standaard instellen", + "removeDefault": "Standaard verwijderen", + "saveFailed": "Kan profiel niet opslaan", + "deleteTitle": "Profiel verwijderen", + "deleteMessage": "Hierdoor worden het profiel en de instellingen ervan permanent verwijderd. Dit kan niet ongedaan worden gemaakt.", + "deleteFailed": "Kan profiel niet verwijderen", + "deleteBlocked": "Dit profiel wordt gebruikt door een webhooktrigger. Verwijder het eerst uit die triggers.", + "deletedToast": "Profiel \"{{name}}\" verwijderd", + "variablesTitle": "Omgevingsvariabelen", + "addVariable": "Variabele toevoegen", + "keyLabel": "Sleutel", + "keyTooLong": "De sleutel mag maximaal 256 tekens bevatten", + "keyDuplicate": "Deze sleutel bestaat al in het profiel", + "valueLabel": "Waarde", + "secretLabel": "Geheim", + "secrets": { + "markSecret": "Markeer als geheim", + "masked": "Verborgen waarde", + "reveal": "Onthul waarde", + "newValuePlaceholder": "Voer een nieuwe geheime waarde in", + "valuePlaceholder": "Geheime waarde (versleuteld)" + }, + "variablesEmpty": "Nog geen variabelen", + "variablesSaveFailed": "Kan variabele niet opslaan", + "commandsTitle": "Instelopdrachten", + "addCommand": "Commando toevoegen", + "commandLabel": "Commando", + "commandPlaceholder": "e.g. pnpm install", + "commandsEmpty": "Nog geen installatieopdrachten", + "commandsSaveFailed": "Kan installatieopdrachten niet opslaan", + "moveUp": "Ga omhoog", + "moveDown": "Ga naar beneden", + "skillsTitle": "Vaardigheden", + "addSkill": "Vaardigheid toevoegen", + "skillNameLabel": "Vaardigheidsnaam", + "skillMarkdownLabel": "Vaardigheid inhoud", + "skillsEmpty": "Nog geen vaardigheden", + "skillNameRequired": "Voer een vaardigheidsnaam in", + "skillNameInvalid": "De naam van de vaardigheid moet beginnen met een letter of cijfer en mag alleen kleine letters, cijfers en streepjes bevatten", + "skillContentRequired": "Vaardigheidsinhoud is vereist", + "skillSaveFailed": "Kan vaardigheid niet opslaan", + "skillsHint": "Plak een SKILL.md en de naam wordt ingevuld; voeg een vaardigheid toe vanaf je telefoon zonder slepen en neerzetten.", + "skillEditTitle": "Vaardigheid bewerken", + "mcp": { + "title": "MCP-servers", + "add": "MCP-server toevoegen", + "empty": "Nog geen MCP-servers", + "name": "Servernaam", + "type": "Servertype", + "localType": "Lokaal", + "remoteType": "Op afstand", + "command": "Commandoregel", + "url": "URL", + "environment": "Omgeving (JSON)", + "headers": "Kopteksten (JSON)", + "timeout": "Time-out", + "secretCount": "Geheime telling", + "namePlaceholder": "my-server", + "commandPlaceholder": "npx @example/mcp-server", + "jsonPlaceholder": "{\n \"API_KEY\": \"sk-...\"\n}", + "nameRequired": "Voer een servernaam in", + "nameInvalid": "De naam mag alleen letters, cijfers, onderstrepingstekens en streepjes bevatten", + "commandRequired": "Voer een opdracht in", + "commandTooLong": "Gebruik maximaal 50 argumenten van elk 500 tekens", + "urlRequired": "Voer een URL in", + "urlInvalid": "Voer een geldige URL in", + "urlTooLong": "De URL mag maximaal 2048 tekens bevatten", + "jsonInvalid": "Voer geldige JSON in: een object met tekenreekswaarden", + "jsonTooLarge": "Gebruik maximaal 50 vermeldingen, sleutels tot 128 en waarden tot 4096 tekens", + "timeoutInvalid": "De time-out moet een geheel aantal milliseconden bedragen", + "edit": "MCP-server bewerken", + "saveFailed": "Kan MCP-server niet opslaan" + }, + "agents": { + "title": "Profiel agenten", + "add": "Agent toevoegen", + "empty": "Nog geen agenten", + "name": "Naam agent", + "slug": "Agentenslak", + "description": "Beschrijving van de agent", + "model": "Modeloverschrijving", + "modelPlaceholder": "Gebruik de sessiestandaard", + "prompt": "Systeemprompt", + "steps": "Max. stappen", + "stepsPlaceholder": "bijv. 50", + "temperature": "Temperatuur", + "temperaturePlaceholder": "bijv. 0.2", + "topP": "top_p", + "topPPlaceholder": "bijv. 0.95", + "slugPlaceholder": "my-agent", + "namePlaceholder": "Mijn agent", + "promptPlaceholder": "Je bent een...", + "tools": "Hulpmiddelen", + "visibility": "Zichtbaarheid", + "visibilityPrimary": "Primair — weergegeven in de kiezer", + "visibilitySubagent": "Subagent — alleen opvraagbaar door anderen", + "visibilityAll": "Allemaal – allebei", + "saveFailed": "Kan agent niet opslaan", + "nameRequired": "Voer een agentnaam in", + "slugRequired": "Voer een agentenslak in", + "slugInvalid": "Slug moet beginnen met een letter en mag alleen kleine letters, cijfers en streepjes gebruiken", + "slugConflict": "Die naaktslak wordt gereserveerd door een ingebouwde agent", + "edit": "Agent bewerken" + }, + "slashCommands": { + "title": "Slash-opdrachten", + "add": "Voeg een slash-opdracht toe", + "empty": "Nog geen slash-opdrachten", + "name": "Naam van opdracht", + "description": "Beschrijving van de opdracht", + "template": "Sjabloon", + "namePlaceholder": "explain", + "templatePlaceholder": "Leg $ARGUMENTS uit", + "agent": "Tussenpersoon", + "subtask": "Uitvoeren als subtaak", + "edit": "Bewerk de slash-opdracht", + "saveFailed": "Kan opdracht niet opslaan", + "nameRequired": "Voer een opdrachtnaam in", + "nameInvalid": "De naam moet beginnen met een kleine letter en mag alleen kleine letters, cijfers en streepjes bevatten", + "templateRequired": "Voer een opdrachtsjabloon in", + "nameConflict": "Die naam is gereserveerd door een ingebouwd commando" + }, + "repoBindings": { + "title": "Standaardprofielen voor repo's", + "add": "Standaard toevoegen", + "empty": "Geen standaardinstellingen geconfigureerd", + "emptyDescription": "Voeg een standaard toe om automatisch een profiel toe te passen bij het starten van sessies voor een opslagplaats.", + "repo": "Selecteer opslagplaats…", + "profile": "Selecteer profiel", + "remove": "Ontbinden", + "private": "Privé", + "search": "Zoek opslagplaatsen…", + "addedToast": "Standaardprofiel ingesteld op \"{{repo}}\"", + "addFailed": "Kan standaardprofiel niet instellen", + "removedToast": "Standaardprofiel verwijderd voor '{{repo}}'", + "removeFailed": "Kan standaardprofiel niet verwijderen", + "pinnedTitle": "Vastgemaakt aan opslagplaatsen", + "pinRepo": "Een opslagplaats vastzetten", + "pinnedEmpty": "Niet vastgemaakt aan opslagplaatsen.", + "pinnedDescription": "Wordt automatisch geladen wanneer een sessie een van deze opslagplaatsen target.", + "pinnedToast": "Vastgezet op '{{repo}}'", + "pinFailed": "Kan de opslagplaats niet vastzetten", + "unboundToast": "Repo niet geconsolideerd", + "unbindFailed": "Kan repository niet ongedaan maken" + } } } diff --git a/apps/mobile/src/i18n/locales/om.json b/apps/mobile/src/i18n/locales/om.json index ed30d952952..ec1a7061576 100644 --- a/apps/mobile/src/i18n/locales/om.json +++ b/apps/mobile/src/i18n/locales/om.json @@ -2191,7 +2191,33 @@ "continueFrom": "{{title}} itti fufaa jira.", "importingSession": "Turtii galchaa jira", "cliCannotContinue": "CLI kun turtii itti fufuu hin danda'u. Cloud Agent yookaan CLI biraa filadhu.", - "importFailed": "Turtii kana galchuu hin danda'amne." + "importFailed": "Turtii kana galchuu hin danda'amne.", + "pickProfile": "Profaayila tokko filadhu", + "noProfile": "Profaayila hin qabu", + "manageProfiles": "Profaayilii bulchuu", + "noProfiles": "Ammatti piroofaayilii hin jiru. Jijjiiramoota naannoo, sarvaroota MCP, fi dandeettiiwwan dabaluudhaaf tokko uumi.", + "advancedConfig": "Qindeessituu Ol'aanaa", + "advancedConfigDescription": "Qindaa'inoota qusataman fe'uuf piroofaayilii fili, fi jijjiiramoota naannoo dabalataa fi ajajoota qindeessaa harkaan qindeessii", + "saveAsProfile": "Akka Profile tti saagi", + "saveProfileDescription": "Jijjiiramoota naannoo ammaa fi ajajoota saaguu akka piroofaayilii irra deebi'amee fayyadamuu danda'utti saagi.", + "profileSummary": "{{vars}} jijjiiramoota naannoo · {{commands}} ajajoota saaguu", + "profileSaved": "Profaayilii \"{{name}}\" qusatameera", + "organizationProfiles": "Profaayilii Jaarmiyaa", + "personalProfiles": "Profaayilii Dhuunfaa", + "yourProfiles": "Profaayilii Keessan", + "failedToLoadProfiles": "Profaayilii fe'uu hin dandeenye", + "nameTooLong": "Maqaan piroofaayilii baayyee dheeraa dha", + "descriptionTooLong": "Ibsi baay'ee dheeraa dha", + "secretsEncrypted": "Iccitiin osoo hin kuufamin dura icciitii ta'a.", + "profileActive": "Profaayilli sochiirra jira", + "profilesActive": "Profaayilota socho'aa jiru", + "customConfigActive": "Qindaa'ina amala socho'aa jira", + "configNeedsAttention": "Config xiyyeeffannoo barbaada", + "repoProfile": "Profaayilii Repo", + "selectedProfile": "Profaayilii filatame", + "manualOverrides": "Harkaan irra darbuu", + "openSettingsToReview": "Irra deebi'uuf Sajoo banaa", + "profileSelection": "Filannoo piroofaayilii" }, "partRenderer": { "updatedFileCount_one": "Faayiliin {{displayCount}} haaromfameera.", @@ -3224,5 +3250,180 @@ "multiplierError": "Baay'isaa 1 ykn isaa ol galchi", "permissionDenied": "Akeekkachiisa baasii bulchuuf hayyamni sitti hin jiru.", "pushOffByCategory": "Beeksisota keessatti cufaa" + }, + "profiles": { + "title": "Profaayilii", + "entrySubtitle": "Sajoo naannoo turtii hunda keessatti irra deebi'ii fayyadami", + "newProfile": "Profaayila haaraa", + "emptyTitle": "Ammatti piroofaayilii hin jiru", + "emptyDescription": "Jijjiiramoota naannoo, ajajoota saaguu, fi dandeettiiwwan turtii hunda irra deebi'ee fayyadamuuf piroofaayilii uumi.", + "loadFailed": "Profaayilii fe'uu hin dandeenye", + "counts": { + "vars": "{{count}} jijjiiramoota", + "mcp": "{{count}} MCP", + "skills": "{{count}} dandeettiiwwan", + "commands": "{{count}} ajajoota", + "varsShort": "{{count}}j", + "skillsShort": "{{count}}d", + "commandsShort": "{{count}}a" + }, + "nameLabel": "Maqaa piroofaayilii", + "namePlaceholder": "fkn. Backend sirreessuu", + "descriptionLabel": "Ibsa piroofaayilii", + "descriptionPlaceholder": "Ibsa filannoo", + "ownerLabel": "Abbaa piroofaayilii", + "nameRequired": "Maqaa piroofaayilii galchi", + "createAction": "Profaayilii uumuu", + "createFailed": "Profaayilii uumuu hin dandeenye", + "createdToast": "Profaayilii \"{{name}}\" uumame", + "settingsSectionTitle": "Qindeessituu", + "defaultSectionTitle": "Profaayilii durtii", + "personalDefaultDescription": "Yeroo kuusaan tokkollee piroofaayilii pinned hin qabne ofumaan fe'ama.", + "organizationDefaultDescription": "Miseensoota durtii dhuunfaa hin qabneef ofumaan fe'ameera.", + "setAsDefault": "Akka durtiitti saagi", + "removeDefault": "Akka durtiitti haqi", + "saveFailed": "Profaayilii qusachuu hin dandeenye", + "deleteTitle": "Profaayilii haquu", + "deleteMessage": "Kunis dhaabbataadhaan piroofaayilii fi qindaa'inoota isaa ni haqa. Kun duubatti deebi’uu hin danda’u.", + "deleteFailed": "Profaayila haquu hin dandeenye", + "deleteBlocked": "Profaayilli kun webhook trigger tiin fayyadama. Dursa triggers sana irraa kaasi.", + "deletedToast": "Profaayilli \"{{name}}\" haqame", + "variablesTitle": "Jijjiiramoota naannoo", + "addVariable": "Jijjiiramaa itti dabali", + "keyLabel": "Furtuu", + "keyTooLong": "Furtuun 256 arfii ykn isaa gadi ta'uu qaba", + "keyDuplicate": "Furtuun kun duraanuu piroofaayilii keessa jira", + "valueLabel": "Gatii", + "secretLabel": "Icitii", + "secrets": { + "markSecret": "Akka iccitiitti Mallatteessi", + "masked": "Gatii dhokataa", + "reveal": "Gatii mul'isuu", + "newValuePlaceholder": "Gatii iccitii haaraa galchi", + "valuePlaceholder": "Gatii iccitii (dhoksame)" + }, + "variablesEmpty": "Jijjiiramoota hanga ammaatti hin jiran", + "variablesSaveFailed": "Jijjiiramaa qusachuu hin dandeenye", + "commandsTitle": "Ajajoota saaguu", + "addCommand": "Ajaja itti dabali", + "commandLabel": "Ajaja", + "commandPlaceholder": "e.g. pnpm install", + "commandsEmpty": "Ajajni setup hanga ammaatti hin jiru", + "commandsSaveFailed": "Ajajoota saaguu qusachuu hin dandeenye", + "moveUp": "Ol socho'aa", + "moveDown": "Gad bu'aa", + "skillsTitle": "Dandeettiiwwan", + "addSkill": "Ogummaa itti dabali", + "skillNameLabel": "Maqaa ogummaa", + "skillMarkdownLabel": "Qabiyyee ogummaa", + "skillsEmpty": "Ammatti ogummaa hin qabu", + "skillNameRequired": "Maqaa ogummaa galchi", + "skillNameInvalid": "Maqaan ogummaa qubee ykn dijiitii irraa jalqabuu fi qubee xixiqqaa, dijiitii fi daash qofa fayyadamuu qaba", + "skillContentRequired": "Qabiyyeen ogummaa barbaachisaadha", + "skillSaveFailed": "Ogummaa qusachuu hin dandeenye", + "skillsHint": "SKILL.md tokko maxxansi maqaan isaa ni guuta; bilbila keessan irraa ogummaa tokko osoo harkisuu fi hin gadi dhiisin itti dabalaa.", + "skillEditTitle": "Dandeettii gulaaluu", + "mcp": { + "title": "Sarvaroota MCP", + "add": "Sarvarii MCP itti dabali", + "empty": "Ammatti sarvaroota MCP hin jiran", + "name": "Maqaa sarvarii", + "type": "Gosa sarvarii", + "localType": "Kan naannoo", + "remoteType": "Fagoo", + "command": "Sarara ajajaa", + "url": "URL", + "environment": "Naannoo (JSON) .", + "headers": "Mata dureewwan (JSON) .", + "timeout": "Yeroon xumuraa", + "secretCount": "Lakkoofsa dhoksaa", + "namePlaceholder": "my-server", + "commandPlaceholder": "npx @example/mcp-server", + "jsonPlaceholder": "{\n \"API_KEY\": \"sk-...\"\n}", + "nameRequired": "Maqaa sarvarii galchi", + "nameInvalid": "Maqaan qubee, lakkoofsota, sarara jalaa fi daash qofa fayyadamuu danda'a", + "commandRequired": "Ajaja galchi", + "commandTooLong": "Arguumentii 50 ol fayyadami, tokkoon tokkoon isaanii arfii 500", + "urlRequired": "URL galchi", + "urlInvalid": "URL sirrii ta'e galchi", + "urlTooLong": "URL arfii 2048 ykn isaa gadi ta'uu qaba", + "jsonInvalid": "JSON sirrii galchi — wanta gatiiwwan dhangii", + "jsonTooLarge": "Galmee 50 ol fayyadami, furtuu hanga 128 fi gatii hanga arfii 4096", + "timeoutInvalid": "Yeroon xumuraa lakkoofsa milisekondii guutuu ta'uu qaba", + "edit": "Sarvarii MCP gulaali", + "saveFailed": "Sarvarii MCP qusachuu hin dandeenye" + }, + "agents": { + "title": "Ergamtoota piroofaayilii", + "add": "Bakka bu'aa itti dabali", + "empty": "Ammatti ergamtoonni hin jiran", + "name": "Maqaa bakka bu'aa", + "slug": "Ergamaa slug", + "description": "Ibsa bakka bu’aa", + "model": "Moodeela irra darbuu", + "modelPlaceholder": "Durtii turtii fayyadami", + "prompt": "Gaaffii sirnaa", + "steps": "Tarkaanfii ol'aanaa", + "stepsPlaceholder": "fakkeenyaaf 50", + "temperature": "Ho'a", + "temperaturePlaceholder": "fakkeenyaaf 0.2", + "topP": "top_p", + "topPPlaceholder": "fakkeenyaaf 0.95", + "slugPlaceholder": "my-agent", + "namePlaceholder": "Bakka bu'aa koo", + "promptPlaceholder": "Ati nama ...", + "tools": "Meeshaalee", + "visibility": "Mul’achuu", + "visibilityPrimary": "Primary — kan agarsiifame picker keessatti", + "visibilitySubagent": "Subagent — kanneen biroo irraa qofa kan waamamu", + "visibilityAll": "Hundi — lamaan isaanii", + "saveFailed": "Bakka bu'aa qusachuu hin dandeenye", + "nameRequired": "Maqaa bakka bu'aa galchi", + "slugRequired": "Slug ergamaa galchi", + "slugInvalid": "Slug qubee irraa jalqabuu fi qubee xixiqqaa, dijiitota, fi daash qofa fayyadamuu qaba", + "slugConflict": "Slug sun ergamaa ijaarameen kan eegamedha", + "edit": "Bakka bu'aa gulaaluu" + }, + "slashCommands": { + "title": "Ajajoota cirracha", + "add": "Ajaja slash itti dabali", + "empty": "Ammatti ajajni slash hin jiru", + "name": "Maqaa ajajaa", + "description": "Ibsa ajajaa", + "template": "Unkaa", + "namePlaceholder": "explain", + "templatePlaceholder": "$ARGUMENTS ibsi", + "agent": "Bakka bu'aa", + "subtask": "Akka hojii xiqqaatti fiigi", + "edit": "Ajaja slash gulaali", + "saveFailed": "Ajaja qusachuu hin dandeenye", + "nameRequired": "Maqaa ajaja galchi", + "nameInvalid": "Maqaan qubee xixiqqaatiin jalqabuu fi qubee xixiqqaa, dijiitii fi daash qofa fayyadamuu qaba", + "templateRequired": "Unkaa ajaja galchi", + "nameConflict": "Maqaan sun ajaja keessaa ijaarameen eegama" + }, + "repoBindings": { + "title": "Profaayilota Durtii Repos", + "add": "Durtii itti dabali", + "empty": "Durtii hin qindaa'e", + "emptyDescription": "Yeroo turtii repo'f jalqabdu ofumaan piroofaayilii hojii irra oolchuuf durtii dabali.", + "repo": "Kuusaa filadhu...", + "profile": "Profaayilii filadhu", + "remove": "Hidhaa hiikaa", + "private": "Dhuunfaa", + "search": "Kuusaawwan barbaadi...", + "addedToast": "Profaayilii durtii \"{{repo}}\" tiif saagame.", + "addFailed": "Profaayilii durtii saaguu hin dandeenye", + "removedToast": "Profaayilii durtii \"{{repo}}\" tiif haqame.", + "removeFailed": "Profaayilii durtii haquu hin dandeenye", + "pinnedTitle": "Kuusaawwan irratti kan maxxane", + "pinRepo": "Repo tokko pin godhaa", + "pinnedEmpty": "Kuusaa kamiyyuu irratti hin pinned.", + "pinnedDescription": "Yeroo hunda turtiin kuusaawwan kana keessaa tokkotti xiyyeeffatu ofumaan fe'ama.", + "pinnedToast": "\"{{repo}}\" tti kan maxxane.", + "pinFailed": "Repo pin gochuu hin dandeenye", + "unboundToast": "Repo hin hidhamne", + "unbindFailed": "Repo hiikuu hin dandeenye" + } } } diff --git a/apps/mobile/src/i18n/locales/or.json b/apps/mobile/src/i18n/locales/or.json index 5511c5204f0..6c8fe2aedac 100644 --- a/apps/mobile/src/i18n/locales/or.json +++ b/apps/mobile/src/i18n/locales/or.json @@ -2191,7 +2191,33 @@ "continueFrom": "{{title}} ଜାରି ରଖାଯାଉଛି।", "importingSession": "ସେସନ୍ ଆମଦାନି ହେଉଛି", "cliCannotContinue": "ଏହି CLI ସେସନ୍ ଜାରି ରଖିପାରିବ ନାହିଁ। Cloud Agent କିମ୍ବା ଅନ୍ୟ ଏକ CLI ବାଛନ୍ତୁ।", - "importFailed": "ଏହି ସେସନ୍ ଆମଦାନି ହୋଇପାରିଲା ନାହିଁ।" + "importFailed": "ଏହି ସେସନ୍ ଆମଦାନି ହୋଇପାରିଲା ନାହିଁ।", + "pickProfile": "ଏକ ପ୍ରୋଫାଇଲ୍ ବାଛନ୍ତୁ।", + "noProfile": "କୌଣସି ପ୍ରୋଫାଇଲ୍ ନାହିଁ।", + "manageProfiles": "ପ୍ରୋଫାଇଲ୍ ପରିଚାଳନା କରନ୍ତୁ।", + "noProfiles": "ଏପର୍ଯ୍ୟନ୍ତ କୌଣସି ପ୍ରୋଫାଇଲ୍ ନାହିଁ। ପରିବେଶ ଭେରିଏବଲ୍, MCP ସର୍ଭର, ଏବଂ କୌଶଳ ଯୋଗ କରିବାକୁ ଗୋଟିଏ ସୃଷ୍ଟି କରନ୍ତୁ।", + "advancedConfig": "ଉନ୍ନତ ବିନ୍ୟାସ", + "advancedConfigDescription": "ସଞ୍ଚିତ ସଂରଚନାଗୁଡ଼ିକୁ ଲୋଡ୍ କରିବା ପାଇଁ ଏକ ପ୍ରୋଫାଇଲ୍ ଚୟନ କରନ୍ତୁ, ଏବଂ ଅତିରିକ୍ତ ପରିବେଶ ଭେରିଏବଲ୍ ଏବଂ ସେଟଅପ୍ କମାଣ୍ଡଗୁଡ଼ିକୁ ହସ୍ତକୃତ ଭାବରେ ବିନ୍ୟାସ କରନ୍ତୁ।", + "saveAsProfile": "ପ୍ରୋଫାଇଲ୍ ଭାବରେ ସେଭ୍ କରନ୍ତୁ।", + "saveProfileDescription": "ପୁନଃ ବ୍ୟବହାର ଯୋଗ୍ୟ ପ୍ରୋଫାଇଲ୍ ଭାବରେ ସାମ୍ପ୍ରତିକ ପରିବେଶ ଭେରିଏବଲ୍ ଏବଂ ସେଟଅପ୍ କମାଣ୍ଡଗୁଡ଼ିକୁ ସେଭ୍ କରନ୍ତୁ।", + "profileSummary": "{{vars}} ପରିବେଶ ଭେରିଏବଲ୍ · {{commands}} ସେଟଅପ୍ କମାଣ୍ଡ।", + "profileSaved": "ପ୍ରୋଫାଇଲ୍ \"{{name}}\" ସଞ୍ଚୟ ହୋଇଛି।", + "organizationProfiles": "ସଂଗଠନ ପ୍ରୋଫାଇଲ୍।", + "personalProfiles": "ବ୍ୟକ୍ତିଗତ ପ୍ରୋଫାଇଲ୍।", + "yourProfiles": "ତୁମର ପ୍ରୋଫାଇଲ୍", + "failedToLoadProfiles": "ପ୍ରୋଫାଇଲ୍ ଲୋଡ୍ କରିବାରେ ବିଫଳ।", + "nameTooLong": "ପ୍ରୋଫାଇଲ୍ ନାମ ବହୁତ ଲମ୍ବା ଅଟେ।", + "descriptionTooLong": "ବର୍ଣ୍ଣନା ବହୁତ ଲମ୍ବା ଅଟେ।", + "secretsEncrypted": "ଷ୍ଟୋରେଜ୍ ପୂର୍ବରୁ ରହସ୍ୟ ଏନକ୍ରିପ୍ଟ କରାଯାଏ।", + "profileActive": "ପ୍ରୋଫାଇଲ୍ ସକ୍ରିୟ।", + "profilesActive": "ପ୍ରୋଫାଇଲ୍ ସକ୍ରିୟ।", + "customConfigActive": "କଷ୍ଟମ୍ ବିନ୍ୟାସ ସକ୍ରିୟ।", + "configNeedsAttention": "ବିନ୍ୟାସ ଧ୍ୟାନ ଆବଶ୍ୟକ କରେ।", + "repoProfile": "ରେପୋ ପ୍ରୋଫାଇଲ୍।", + "selectedProfile": "ମନୋନୀତ ପ୍ରୋଫାଇଲ୍।", + "manualOverrides": "ମାନୁଆଲ୍ ଓଭରବ୍ରିଡ୍।", + "openSettingsToReview": "ସମୀକ୍ଷା କରିବାକୁ ସେଟିଂସମୂହ ଖୋଲନ୍ତୁ।", + "profileSelection": "ପ୍ରୋଫାଇଲ୍ ଚୟନ" }, "partRenderer": { "updatedFileCount_one": "{{displayCount}}ଟି ଫାଇଲ୍ ଅଦ୍ୟତନ ହୋଇଛି।", @@ -3224,5 +3250,180 @@ "multiplierError": "1 କିମ୍ବା ତାହାଠାରୁ ଅଧିକ ଗୁଣକ ଦିଅନ୍ତୁ", "permissionDenied": "ଖର୍ଚ୍ଚ ସତର୍କତା ପରିଚାଳନା କରିବାକୁ ଆପଣଙ୍କର ଅନୁମତି ନାହିଁ।", "pushOffByCategory": "ବିଜ୍ଞପ୍ତିରେ ବନ୍ଦ" + }, + "profiles": { + "title": "ପ୍ରୋଫାଇଲ୍", + "entrySubtitle": "ଅଧିବେଶନରେ ପରିବେଶ ସେଟିଂସମୂହ ପୁନଃ ବ୍ୟବହାର କରନ୍ତୁ।", + "newProfile": "ନୂତନ ପ୍ରୋଫାଇଲ୍।", + "emptyTitle": "ଏପର୍ଯ୍ୟନ୍ତ କୌଣସି ପ୍ରୋଫାଇଲ୍ ନାହିଁ।", + "emptyDescription": "ପରିବେଶ ଭେରିଏବଲ୍, ସେଟଅପ୍ କମାଣ୍ଡ, ଏବଂ ଅଧିବେଶନରେ କୌଶଳ ପୁନଃ ବ୍ୟବହାର କରିବାକୁ ଏକ ପ୍ରୋଫାଇଲ୍ ସୃଷ୍ଟି କରନ୍ତୁ।", + "loadFailed": "ପ୍ରୋଫାଇଲ୍ ଲୋଡ୍ ହୋଇପାରିଲା ନାହିଁ।", + "counts": { + "vars": "{{count}} ଭେରିଏବଲ୍", + "mcp": "{{count}} MCP", + "skills": "{{count}} ଦକ୍ଷତା", + "commands": "{{count}} କମାଣ୍ଡ୍", + "varsShort": "{{count}}ଭ", + "skillsShort": "{{count}}ଦ", + "commandsShort": "{{count}}କ" + }, + "nameLabel": "ପ୍ରୋଫାଇଲ୍ ନାମ", + "namePlaceholder": "ଯଥା ବ୍ୟାକେଣ୍ଡ୍ ତ୍ରୁଟି ନିବାରଣ।", + "descriptionLabel": "ପ୍ରୋଫାଇଲ୍ ବର୍ଣ୍ଣନା", + "descriptionPlaceholder": "ବୈକଳ୍ପିକ ବର୍ଣ୍ଣନା", + "ownerLabel": "ପ୍ରୋଫାଇଲ୍ ମାଲିକ", + "nameRequired": "ଏକ ପ୍ରୋଫାଇଲ୍ ନାମ ପ୍ରବେଶ କରନ୍ତୁ।", + "createAction": "ପ୍ରୋଫାଇଲ୍ ସୃଷ୍ଟି କରନ୍ତୁ।", + "createFailed": "ପ୍ରୋଫାଇଲ୍ ସୃଷ୍ଟି କରିପାରିଲା ନାହିଁ।", + "createdToast": "ପ୍ରୋଫାଇଲ୍ \"{{name}}\" ସୃଷ୍ଟି ହୋଇଛି।", + "settingsSectionTitle": "ବିନ୍ୟାସ", + "defaultSectionTitle": "ଡିଫଲ୍ଟ ପ୍ରୋଫାଇଲ୍।", + "personalDefaultDescription": "ଯେତେବେଳେ କୌଣସି ସଂଗ୍ରହାଳୟର ଏକ ପିନ୍ ପ୍ରୋଫାଇଲ୍ ନଥାଏ ସେତେବେଳେ ଅଟୋ-ଲୋଡ୍ ହୁଏ।", + "organizationDefaultDescription": "ଯେଉଁ ସଦସ୍ୟମାନଙ୍କର ବ୍ୟକ୍ତିଗତ ଡିଫଲ୍ଟ ନାହିଁ ସେମାନଙ୍କ ପାଇଁ ଅଟୋ-ଲୋଡ୍।", + "setAsDefault": "ଡିଫଲ୍ଟ ଭାବରେ ସେଟ୍ କରନ୍ତୁ।", + "removeDefault": "ଡିଫଲ୍ଟ ଭାବରେ ଅପସାରଣ କରନ୍ତୁ।", + "saveFailed": "ପ୍ରୋଫାଇଲ୍ ସଞ୍ଚୟ କରିପାରିଲା ନାହିଁ।", + "deleteTitle": "ପ୍ରୋଫାଇଲ୍ ବିଲୋପ କରନ୍ତୁ।", + "deleteMessage": "ଏହା ସ୍ଥାୟୀ ଭାବରେ ପ୍ରୋଫାଇଲ୍ ଏବଂ ଏହାର ସେଟିଙ୍ଗ୍ ଅପସାରଣ କରେ। ଏହାକୁ ପୂର୍ବବତ୍ କରାଯାଇପାରିବ ନାହିଁ।", + "deleteFailed": "ପ୍ରୋଫାଇଲ୍ ବିଲୋପ କରିପାରିଲା ନାହିଁ।", + "deleteBlocked": "ଏହି ପ୍ରୋଫାଇଲ୍ ଏକ ୱେବହକ୍ ଟ୍ରିଗର ଦ୍ୱାରା ବ୍ୟବହୃତ ହୁଏ। ପ୍ରଥମେ ସେହି ଟ୍ରିଗରଗୁଡିକରୁ ଏହାକୁ ହଟାନ୍ତୁ।", + "deletedToast": "ପ୍ରୋଫାଇଲ୍ \"{{name}}\" ବିଲୋପ ହେଲା।", + "variablesTitle": "ପରିବେଶ ଭେରିଏବଲ୍।", + "addVariable": "ଭେରିଏବଲ୍ ଆଡ୍ କରନ୍ତୁ।", + "keyLabel": "ଚାବି", + "keyTooLong": "ଚାବି 256 ଅକ୍ଷର କିମ୍ବା କମ୍ ହେବା ଉଚିତ୍", + "keyDuplicate": "ଏହି ଚାବି ପୂର୍ବରୁ ପ୍ରୋଫାଇଲରେ ଅଛି", + "valueLabel": "ମୂଲ୍ୟ", + "secretLabel": "ଗୁପ୍ତ", + "secrets": { + "markSecret": "ଗୁପ୍ତ ଭାବରେ ଚିହ୍ନିତ କର।", + "masked": "ଲୁକ୍କାୟିତ ମୂଲ୍ୟ", + "reveal": "ମୂଲ୍ୟ ପ୍ରକାଶ କରନ୍ତୁ।", + "newValuePlaceholder": "ନୂଆ ଗୁପ୍ତ ମୂଲ୍ୟ ପ୍ରବେଶ କରନ୍ତୁ", + "valuePlaceholder": "ଗୁପ୍ତ ମୂଲ୍ୟ (ଏନକ୍ରିପ୍ଟ ହୋଇଛି)" + }, + "variablesEmpty": "ଏପର୍ଯ୍ୟନ୍ତ କୌଣସି ଭେରିଏବଲ୍ ନାହିଁ।", + "variablesSaveFailed": "ଭେରିଏବଲ୍ ସେଭ୍ କରିପାରିଲା ନାହିଁ।", + "commandsTitle": "ସେଟଅପ୍ କମାଣ୍ଡ୍।", + "addCommand": "କମାଣ୍ଡ୍ ଯୋଡନ୍ତୁ।", + "commandLabel": "ନିର୍ଦ୍ଦେଶ।", + "commandPlaceholder": "e.g. pnpm install", + "commandsEmpty": "ଏପର୍ଯ୍ୟନ୍ତ କୌଣସି ସେଟଅପ୍ ନିର୍ଦ୍ଦେଶ ନାହିଁ।", + "commandsSaveFailed": "ସେଟଅପ୍ କମାଣ୍ଡ୍ ସଞ୍ଚୟ କରିପାରିଲା ନାହିଁ।", + "moveUp": "ଉପରକୁ ଯାଅ।", + "moveDown": "ତଳକୁ ଯାଅ।", + "skillsTitle": "ଦକ୍ଷତା।", + "addSkill": "ଦକ୍ଷତା ଯୋଡନ୍ତୁ।", + "skillNameLabel": "ଦକ୍ଷତା ନାମ", + "skillMarkdownLabel": "ଦକ୍ଷତା ବିଷୟବସ୍ତୁ", + "skillsEmpty": "ଏପର୍ଯ୍ୟନ୍ତ କୌଣସି ଦକ୍ଷତା ନାହିଁ।", + "skillNameRequired": "ଏକ ଦକ୍ଷତା ନାମ ପ୍ରବେଶ କରନ୍ତୁ।", + "skillNameInvalid": "କୌଶଳ ନାମ ନିଶ୍ଚିତ ଭାବରେ ଏକ ଅକ୍ଷର କିମ୍ବା ଅଙ୍କରୁ ଆରମ୍ଭ ହେବ ଏବଂ କେବଳ ଛୋଟ ଅକ୍ଷର, ସଂଖ୍ୟା ଏବଂ ଡ୍ୟାସ୍ ବ୍ୟବହାର କରିବ।", + "skillContentRequired": "ଦକ୍ଷତା ବିଷୟବସ୍ତୁ ଆବଶ୍ୟକ।", + "skillSaveFailed": "ଦକ୍ଷତା ସଞ୍ଚୟ କରିପାରିଲା ନାହିଁ।", + "skillsHint": "ଏକ SKILL.md ଲେପନ କରନ୍ତୁ ଏବଂ ଏହାର ନାମ ପୁରଣ କରେ। ଡ୍ରାଗ୍ ଏବଂ ଡ୍ରପ୍ ନକରି ଆପଣଙ୍କ ଫୋନରୁ ଏକ ଦକ୍ଷତା ଯୋଡନ୍ତୁ।", + "skillEditTitle": "ଦକ୍ଷତା ସମ୍ପାଦନ କରନ୍ତୁ।", + "mcp": { + "title": "MCP ସର୍ଭରଗୁଡ଼ିକ।", + "add": "MCP ସର୍ଭର ଯୋଡନ୍ତୁ।", + "empty": "ଏପର୍ଯ୍ୟନ୍ତ କୌଣସି MCP ସର୍ଭର ନାହିଁ।", + "name": "ସର୍ଭର ନାମ।", + "type": "ସର୍ଭର ପ୍ରକାର।", + "localType": "ସ୍ଥାନୀୟ", + "remoteType": "ସୁଦୂର", + "command": "ନିର୍ଦ୍ଦେଶନାମା।", + "url": "URL", + "environment": "ପରିବେଶ (JSON)", + "headers": "ହେଡର୍ସ (JSON)", + "timeout": "ସମୟ ସମାପ୍ତ", + "secretCount": "ଗୁପ୍ତ ଗଣନା", + "namePlaceholder": "my-server", + "commandPlaceholder": "npx @example/mcp-server", + "jsonPlaceholder": "{\n \"API_KEY\": \"sk-...\"\n}", + "nameRequired": "ଏକ ସର୍ଭର ନାମ ପ୍ରବେଶ କରନ୍ତୁ।", + "nameInvalid": "ନାମ କେବଳ ଅକ୍ଷର, ସଂଖ୍ୟା, ଅଣ୍ଡରସ୍କୋର୍, ଏବଂ ଡ୍ୟାସ୍ ବ୍ୟବହାର କରିପାରିବ।", + "commandRequired": "ଏକ ନିର୍ଦ୍ଦେଶ ପ୍ରବେଶ କରନ୍ତୁ।", + "commandTooLong": "ସର୍ବାଧିକ 50 ଆର୍ଗୁମେଣ୍ଟ ବ୍ୟବହାର କରନ୍ତୁ, ପ୍ରତ୍ୟେକଟି 500 ଅକ୍ଷର", + "urlRequired": "ଏକ URL ପ୍ରବେଶ କରନ୍ତୁ।", + "urlInvalid": "ଏକ ବୈଧ URL ପ୍ରବେଶ କରନ୍ତୁ।", + "urlTooLong": "URL 2048 ଅକ୍ଷର କିମ୍ବା କମ୍ ହେବା ଉଚିତ୍", + "jsonInvalid": "ବୈଧ JSON ପ୍ରବେଶ କରନ୍ତୁ - ଷ୍ଟ୍ରିଙ୍ଗ ମୂଲ୍ୟର ଏକ ବସ୍ତୁ।", + "jsonTooLarge": "ସର୍ବାଧିକ 50 ଏଣ୍ଟ୍ରି ବ୍ୟବହାର କରନ୍ତୁ, ଚାବି 128 ପର୍ଯ୍ୟନ୍ତ ଏବଂ ମୂଲ୍ୟ 4096 ଅକ୍ଷର ପର୍ଯ୍ୟନ୍ତ", + "timeoutInvalid": "ସମୟ ସମାପ୍ତ ହେବା ନିଶ୍ଚିତ ଭାବରେ ଏକ ମିଲିସେକେଣ୍ଡ।", + "edit": "MCP ସର୍ଭର ସଂପାଦନ କରନ୍ତୁ।", + "saveFailed": "MCP ସର୍ଭର ସଞ୍ଚୟ କରିପାରିଲା ନାହିଁ।" + }, + "agents": { + "title": "ପ୍ରୋଫାଇଲ୍ ଏଜେଣ୍ଟଗୁଡିକ।", + "add": "ଏଜେଣ୍ଟ ଯୋଡନ୍ତୁ।", + "empty": "ଏପର୍ଯ୍ୟନ୍ତ କୌଣସି ଏଜେଣ୍ଟ ନାହିଁ।", + "name": "ଏଜେଣ୍ଟ ନାମ", + "slug": "ଏଜେଣ୍ଟ ସ୍ଲଗ୍।", + "description": "ଏଜେଣ୍ଟ ବର୍ଣ୍ଣନା", + "model": "ମଡେଲ୍ ନବଲିଖନ କରନ୍ତୁ।", + "modelPlaceholder": "ଅଧିବେଶନ ଡିଫଲ୍ଟ ବ୍ୟବହାର କରନ୍ତୁ।", + "prompt": "ସିଷ୍ଟମ୍ ପ୍ରମ୍ପ୍ଟ।", + "steps": "ସର୍ବାଧିକ ପଦକ୍ଷେପ", + "stepsPlaceholder": "ଉଦା. 50", + "temperature": "ତାପମାତ୍ରା", + "temperaturePlaceholder": "ଉଦା. 0.2", + "topP": "top_p", + "topPPlaceholder": "ଉଦା. 0.95", + "slugPlaceholder": "my-agent", + "namePlaceholder": "ମୋର ଏଜେଣ୍ଟ", + "promptPlaceholder": "ତୁମେ ଏକ ...", + "tools": "ସାଧନଗୁଡ଼ିକ।", + "visibility": "ଦୃଶ୍ୟମାନତା।", + "visibilityPrimary": "ପ୍ରାଥମିକ - ପିକଚରରେ ଦର୍ଶାଯାଇଛି।", + "visibilitySubagent": "ସବଜେଣ୍ଟ୍ - କେବଳ ଅନ୍ୟମାନଙ୍କଠାରୁ କଲ୍ ଯୋଗ୍ୟ।", + "visibilityAll": "ସମସ୍ତ - ଉଭୟ", + "saveFailed": "ଏଜେଣ୍ଟ ସଞ୍ଚୟ କରିପାରିଲା ନାହିଁ।", + "nameRequired": "ଏକ ଏଜେଣ୍ଟ ନାମ ପ୍ରବେଶ କରନ୍ତୁ।", + "slugRequired": "ଏକ ଏଜେଣ୍ଟ ସ୍ଲଗ୍ ପ୍ରବେଶ କରନ୍ତୁ।", + "slugInvalid": "ସ୍ଲଗ୍ ନିଶ୍ଚିତ ଭାବରେ ଏକ ଅକ୍ଷରରୁ ଆରମ୍ଭ ହେବ ଏବଂ କେବଳ ଲୋୟର କେସ୍ ଅକ୍ଷର, ସଂଖ୍ୟା, ଏବଂ ଡ୍ୟାସ୍ ବ୍ୟବହାର କରିବ।", + "slugConflict": "ସେହି ସ୍ଲଗ୍ ଏକ ବିଲ୍ଟ-ଇନ୍ ଏଜେଣ୍ଟ ଦ୍ୱାରା ସଂରକ୍ଷିତ।", + "edit": "ଏଜେଣ୍ଟ ସଂପାଦନ କରନ୍ତୁ।" + }, + "slashCommands": { + "title": "ସ୍ଲାସ୍ କମାଣ୍ଡ୍।", + "add": "ସ୍ଲାସ୍ କମାଣ୍ଡ୍ ଯୋଡନ୍ତୁ।", + "empty": "ଏପର୍ଯ୍ୟନ୍ତ କୌଣସି ସ୍ଲାସ୍ କମାଣ୍ଡ୍ ନାହିଁ।", + "name": "ନିର୍ଦ୍ଦେଶ ନାମ", + "description": "ନିର୍ଦ୍ଦେଶ ବର୍ଣ୍ଣନା", + "template": "ଟେମ୍ପଲେଟ୍।", + "namePlaceholder": "explain", + "templatePlaceholder": "$ARGUMENTS ବ୍ୟାଖ୍ୟା କର।", + "agent": "ଏଜେଣ୍ଟ।", + "subtask": "ସବଟାସ୍କ ଭାବରେ ଚଲାନ୍ତୁ।", + "edit": "ସ୍ଲାସ୍ କମାଣ୍ଡ୍ ଏଡିଟ୍ କରନ୍ତୁ।", + "saveFailed": "କମାଣ୍ଡ୍ ସେଭ୍ କରିପାରିଲା ନାହିଁ।", + "nameRequired": "ଏକ ନିର୍ଦ୍ଦେଶ ନାମ ପ୍ରବେଶ କରନ୍ତୁ।", + "nameInvalid": "ନାମ ନିଶ୍ଚିତ ଭାବରେ ଏକ ଛୋଟ ଅକ୍ଷରରୁ ଆରମ୍ଭ ହେବ ଏବଂ କେବଳ ଲୋୟର କେସ୍ ଅକ୍ଷର, ସଂଖ୍ୟା ଏବଂ ଡ୍ୟାସ୍ ବ୍ୟବହାର କରିବ।", + "templateRequired": "ଏକ ନିର୍ଦ୍ଦେଶ ଟେମ୍ପଲେଟ୍ ପ୍ରବେଶ କରନ୍ତୁ।", + "nameConflict": "ସେହି ନାମ ଏକ ବିଲ୍ଟ-ଇନ୍ କମାଣ୍ଡ ଦ୍ୱାରା ସଂରକ୍ଷିତ।" + }, + "repoBindings": { + "title": "ରେପୋ ପାଇଁ ଡିଫଲ୍ଟ ପ୍ରୋଫାଇଲ୍।", + "add": "ଡିଫଲ୍ଟ ଯୋଡନ୍ତୁ।", + "empty": "କୌଣସି ଡିଫଲ୍ଟ ବିନ୍ୟାସ ହୋଇନାହିଁ।", + "emptyDescription": "ଏକ ରେପୋ ପାଇଁ ଅଧିବେଶନ ଆରମ୍ଭ କରିବା ସମୟରେ ସ୍ୱୟଂଚାଳିତ ଭାବରେ ଏକ ପ୍ରୋଫାଇଲ୍ ପ୍ରୟୋଗ କରିବାକୁ ଏକ ଡିଫଲ୍ଟ ଯୋଡନ୍ତୁ।", + "repo": "ସଂଗ୍ରହାଳୟ ଚୟନ କରନ୍ତୁ…", + "profile": "ପ୍ରୋଫାଇଲ୍ ଚୟନ କରନ୍ତୁ।", + "remove": "ଅନ୍ବିଣ୍ଡ କରନ୍ତୁ।", + "private": "ବ୍ୟକ୍ତିଗତ", + "search": "ସଂଗ୍ରହାଳୟ ଖୋଜ…", + "addedToast": "ଡିଫଲ୍ଟ ପ୍ରୋଫାଇଲ୍ \"{{repo}}\" ପାଇଁ ସେଟ୍ ହୋଇଛି।", + "addFailed": "ଡିଫଲ୍ଟ ପ୍ରୋଫାଇଲ୍ ସେଟ୍ କରିପାରିଲା ନାହିଁ।", + "removedToast": "ଡିଫଲ୍ଟ ପ୍ରୋଫାଇଲ୍ \"{{repo}}\" ପାଇଁ ଅପସାରିତ ହୋଇଛି।", + "removeFailed": "ଡିଫଲ୍ଟ ପ୍ରୋଫାଇଲ୍ ଅପସାରଣ କରିପାରିଲା ନାହିଁ।", + "pinnedTitle": "ସଂଗ୍ରହାଳୟରେ ପିନ୍ ହୋଇଛି।", + "pinRepo": "ଏକ ରେପୋ ପିନ୍ କରନ୍ତୁ।", + "pinnedEmpty": "କୌଣସି ସଂଗ୍ରହାଳୟରେ ପିନ୍ ହୋଇନାହିଁ।", + "pinnedDescription": "ଯେତେବେଳେ ଏକ ଅଧିବେଶନ ଏହି ସଂଗ୍ରହାଳୟ ମଧ୍ୟରୁ ଗୋଟିଏକୁ ଟାର୍ଗେଟ କରେ ଅଟୋ-ଲୋଡ୍।", + "pinnedToast": "\"{{repo}}\" କୁ ପିନ୍ କରାଯାଇଛି", + "pinFailed": "ରେପୋ ପିନ୍ କରିପାରିଲା ନାହିଁ।", + "unboundToast": "ରେପୋ ଅନ୍ବାଉଣ୍ଡ୍।", + "unbindFailed": "ରେପୋ ଅନ୍ବିଣ୍ଡ କରିପାରିବ ନାହିଁ।" + } } } diff --git a/apps/mobile/src/i18n/locales/pa.json b/apps/mobile/src/i18n/locales/pa.json index cf792a63cf6..2a4f1a5de2e 100644 --- a/apps/mobile/src/i18n/locales/pa.json +++ b/apps/mobile/src/i18n/locales/pa.json @@ -2191,7 +2191,33 @@ "continueFrom": "{{title}} ਨੂੰ ਜਾਰੀ ਰੱਖਿਆ ਜਾ ਰਿਹਾ ਹੈ।", "importingSession": "ਸੈਸ਼ਨ ਆਯਾਤ ਕੀਤਾ ਜਾ ਰਿਹਾ ਹੈ", "cliCannotContinue": "ਇਹ CLI ਸੈਸ਼ਨ ਜਾਰੀ ਨਹੀਂ ਰੱਖ ਸਕਦਾ। Cloud Agent ਜਾਂ ਕੋਈ ਹੋਰ CLI ਚੁਣੋ।", - "importFailed": "ਇਸ ਸੈਸ਼ਨ ਨੂੰ ਆਯਾਤ ਨਹੀਂ ਕੀਤਾ ਜਾ ਸਕਿਆ।" + "importFailed": "ਇਸ ਸੈਸ਼ਨ ਨੂੰ ਆਯਾਤ ਨਹੀਂ ਕੀਤਾ ਜਾ ਸਕਿਆ।", + "pickProfile": "ਇੱਕ ਪ੍ਰੋਫਾਈਲ ਚੁਣੋ", + "noProfile": "ਕੋਈ ਪ੍ਰੋਫਾਈਲ ਨਹੀਂ", + "manageProfiles": "ਪ੍ਰੋਫਾਈਲਾਂ ਦਾ ਪ੍ਰਬੰਧਨ ਕਰੋ", + "noProfiles": "ਅਜੇ ਤੱਕ ਕੋਈ ਪ੍ਰੋਫਾਈਲ ਨਹੀਂ। ਵਾਤਾਵਰਣ ਵੇਰੀਏਬਲ, MCP ਸਰਵਰ, ਅਤੇ ਹੁਨਰ ਜੋੜਨ ਲਈ ਇੱਕ ਬਣਾਓ।", + "advancedConfig": "ਉੱਨਤ ਸੰਰਚਨਾ", + "advancedConfigDescription": "ਸੁਰੱਖਿਅਤ ਕੀਤੀਆਂ ਸੰਰਚਨਾਵਾਂ ਨੂੰ ਲੋਡ ਕਰਨ ਲਈ ਇੱਕ ਪ੍ਰੋਫਾਈਲ ਚੁਣੋ, ਅਤੇ ਵਾਧੂ ਵਾਤਾਵਰਣ ਵੇਰੀਏਬਲ ਅਤੇ ਸੈੱਟਅੱਪ ਕਮਾਂਡਾਂ ਨੂੰ ਹੱਥੀਂ ਕੌਂਫਿਗਰ ਕਰੋ", + "saveAsProfile": "ਪ੍ਰੋਫਾਈਲ ਵਜੋਂ ਸੁਰੱਖਿਅਤ ਕਰੋ", + "saveProfileDescription": "ਮੌਜੂਦਾ ਵਾਤਾਵਰਣ ਵੇਰੀਏਬਲ ਅਤੇ ਸੈੱਟਅੱਪ ਕਮਾਂਡਾਂ ਨੂੰ ਮੁੜ ਵਰਤੋਂ ਯੋਗ ਪ੍ਰੋਫਾਈਲ ਵਜੋਂ ਸੁਰੱਖਿਅਤ ਕਰੋ।", + "profileSummary": "{{vars}} ਵਾਤਾਵਰਣ ਵੇਰੀਏਬਲ · {{commands}} ਸੈੱਟਅੱਪ ਕਮਾਂਡਾਂ", + "profileSaved": "ਪ੍ਰੋਫਾਈਲ \"{{name}}\" ਸੁਰੱਖਿਅਤ ਕੀਤੀ ਗਈ", + "organizationProfiles": "ਸੰਗਠਨ ਪ੍ਰੋਫਾਈਲ", + "personalProfiles": "ਨਿੱਜੀ ਪ੍ਰੋਫਾਈਲਾਂ", + "yourProfiles": "ਤੁਹਾਡੇ ਪ੍ਰੋਫਾਈਲ", + "failedToLoadProfiles": "ਪ੍ਰੋਫਾਈਲਾਂ ਨੂੰ ਲੋਡ ਕਰਨਾ ਅਸਫਲ ਰਿਹਾ", + "nameTooLong": "ਪ੍ਰੋਫਾਈਲ ਨਾਮ ਬਹੁਤ ਲੰਮਾ ਹੈ", + "descriptionTooLong": "ਵਰਣਨ ਬਹੁਤ ਲੰਮਾ ਹੈ", + "secretsEncrypted": "ਭੇਦ ਸਟੋਰੇਜ ਤੋਂ ਪਹਿਲਾਂ ਏਨਕ੍ਰਿਪਟ ਕੀਤੇ ਜਾਂਦੇ ਹਨ।", + "profileActive": "ਪ੍ਰੋਫਾਈਲ ਕਿਰਿਆਸ਼ੀਲ ਹੈ", + "profilesActive": "ਪ੍ਰੋਫਾਈਲ ਕਿਰਿਆਸ਼ੀਲ ਹਨ", + "customConfigActive": "ਕਸਟਮ ਸੰਰਚਨਾ ਕਿਰਿਆਸ਼ੀਲ ਹੈ", + "configNeedsAttention": "ਸੰਰਚਨਾ ਨੂੰ ਧਿਆਨ ਦੇਣ ਦੀ ਲੋੜ ਹੈ", + "repoProfile": "ਰੇਪੋ ਪ੍ਰੋਫਾਈਲ", + "selectedProfile": "ਚੁਣਿਆ ਗਿਆ ਪ੍ਰੋਫਾਈਲ", + "manualOverrides": "ਮੈਨੁਅਲ ਓਵਰਰਾਈਡ", + "openSettingsToReview": "ਸਮੀਖਿਆ ਕਰਨ ਲਈ ਸੈਟਿੰਗਾਂ ਖੋਲ੍ਹੋ", + "profileSelection": "ਪ੍ਰੋਫਾਈਲ ਦੀ ਚੋਣ" }, "partRenderer": { "updatedFileCount_one": "{{displayCount}} ਫਾਈਲ ਅੱਪਡੇਟ ਕੀਤੀ ਗਈ ਹੈ।", @@ -3224,5 +3250,180 @@ "multiplierError": "1 ਜਾਂ ਵੱਧ ਦਾ ਗੁਣਕ ਦਾਖਲ ਕਰੋ", "permissionDenied": "ਤੁਹਾਨੂੰ ਖਰਚੇ ਦੀਆਂ ਚੇਤਾਵਨੀਆਂ ਪ੍ਰਬੰਧਨ ਦੀ ਇਜਾਜ਼ਤ ਨਹੀਂ ਹੈ।", "pushOffByCategory": "ਸੂਚਨਾਵਾਂ ਵਿੱਚ ਬੰਦ" + }, + "profiles": { + "title": "ਪ੍ਰੋਫਾਈਲਾਂ", + "entrySubtitle": "ਸੈਸ਼ਨਾਂ ਵਿੱਚ ਵਾਤਾਵਰਣ ਸੈਟਿੰਗਾਂ ਦੀ ਮੁੜ ਵਰਤੋਂ ਕਰੋ", + "newProfile": "ਨਵਾਂ ਪ੍ਰੋਫਾਈਲ", + "emptyTitle": "ਅਜੇ ਤੱਕ ਕੋਈ ਪ੍ਰੋਫਾਈਲ ਨਹੀਂ", + "emptyDescription": "ਸਾਰੇ ਸੈਸ਼ਨਾਂ ਵਿੱਚ ਵਾਤਾਵਰਣ ਵੇਰੀਏਬਲ, ਸੈੱਟਅੱਪ ਕਮਾਂਡਾਂ ਅਤੇ ਹੁਨਰਾਂ ਦੀ ਮੁੜ ਵਰਤੋਂ ਕਰਨ ਲਈ ਇੱਕ ਪ੍ਰੋਫਾਈਲ ਬਣਾਓ।", + "loadFailed": "ਪ੍ਰੋਫਾਈਲਾਂ ਨੂੰ ਲੋਡ ਨਹੀਂ ਕੀਤਾ ਜਾ ਸਕਿਆ", + "counts": { + "vars": "{{count}} ਵੇਰੀਏਬਲ", + "mcp": "{{count}} MCP", + "skills": "{{count}} ਹੁਨਰ", + "commands": "{{count}} ਕਮਾਂਡਾਂ", + "varsShort": "{{count}}ਵ", + "skillsShort": "{{count}}ਹ", + "commandsShort": "{{count}}ਕ" + }, + "nameLabel": "ਪ੍ਰੋਫਾਈਲ ਨਾਮ", + "namePlaceholder": "ਜਿਵੇਂ ਕਿ ਬੈਕਐਂਡ ਡੀਬੱਗਿੰਗ", + "descriptionLabel": "ਪ੍ਰੋਫਾਈਲ ਵਰਣਨ", + "descriptionPlaceholder": "ਵਿਕਲਪਿਕ ਵਰਣਨ", + "ownerLabel": "ਪ੍ਰੋਫਾਈਲ ਮਾਲਕ", + "nameRequired": "ਇੱਕ ਪ੍ਰੋਫਾਈਲ ਨਾਮ ਦਰਜ ਕਰੋ", + "createAction": "ਪ੍ਰੋਫਾਈਲ ਬਣਾਓ", + "createFailed": "ਪ੍ਰੋਫਾਈਲ ਨਹੀਂ ਬਣਾਇਆ ਜਾ ਸਕਿਆ", + "createdToast": "ਪ੍ਰੋਫਾਈਲ \"{{name}}\" ਬਣਾਇਆ ਗਿਆ", + "settingsSectionTitle": "ਸੰਰਚਨਾ", + "defaultSectionTitle": "ਪੂਰਵ-ਨਿਰਧਾਰਤ ਪ੍ਰੋਫਾਈਲ", + "personalDefaultDescription": "ਜਦੋਂ ਕਿਸੇ ਰਿਪੋਜ਼ਟਰੀ ਵਿੱਚ ਪਿੰਨ ਕੀਤਾ ਪ੍ਰੋਫਾਈਲ ਨਹੀਂ ਹੁੰਦਾ ਹੈ ਤਾਂ ਆਟੋ-ਲੋਡ ਹੁੰਦਾ ਹੈ।", + "organizationDefaultDescription": "ਉਹਨਾਂ ਮੈਂਬਰਾਂ ਲਈ ਸਵੈ-ਲੋਡ ਕੀਤਾ ਗਿਆ ਜਿਨ੍ਹਾਂ ਕੋਲ ਕੋਈ ਨਿੱਜੀ ਡਿਫੌਲਟ ਨਹੀਂ ਹੈ।", + "setAsDefault": "ਪੂਰਵ-ਨਿਰਧਾਰਤ ਵਜੋਂ ਸੈੱਟ ਕਰੋ", + "removeDefault": "ਡਿਫੌਲਟ ਵਜੋਂ ਹਟਾਓ", + "saveFailed": "ਪ੍ਰੋਫਾਈਲ ਨੂੰ ਸੁਰੱਖਿਅਤ ਨਹੀਂ ਕੀਤਾ ਜਾ ਸਕਿਆ", + "deleteTitle": "ਪ੍ਰੋਫਾਈਲ ਮਿਟਾਓ", + "deleteMessage": "ਇਹ ਪ੍ਰੋਫਾਈਲ ਅਤੇ ਇਸਦੀਆਂ ਸੈਟਿੰਗਾਂ ਨੂੰ ਸਥਾਈ ਤੌਰ 'ਤੇ ਹਟਾਉਂਦਾ ਹੈ। ਇਸ ਨੂੰ ਅਣਕੀਤਾ ਨਹੀਂ ਕੀਤਾ ਜਾ ਸਕਦਾ।", + "deleteFailed": "ਪ੍ਰੋਫਾਈਲ ਨੂੰ ਮਿਟਾਇਆ ਨਹੀਂ ਜਾ ਸਕਿਆ", + "deleteBlocked": "ਇਹ ਪ੍ਰੋਫਾਈਲ ਵੈੱਬਹੁੱਕ ਟਰਿੱਗਰ ਦੁਆਰਾ ਵਰਤੀ ਜਾਂਦੀ ਹੈ। ਇਸਨੂੰ ਪਹਿਲਾਂ ਉਹਨਾਂ ਟਰਿਗਰਾਂ ਤੋਂ ਹਟਾਓ।", + "deletedToast": "ਪ੍ਰੋਫਾਈਲ \"{{name}}\" ਮਿਟਾਇਆ ਗਿਆ", + "variablesTitle": "ਵਾਤਾਵਰਣ ਵੇਰੀਏਬਲ", + "addVariable": "ਵੇਰੀਏਬਲ ਸ਼ਾਮਲ ਕਰੋ", + "keyLabel": "ਕੁੰਜੀ", + "keyTooLong": "ਕੁੰਜੀ 256 ਅੱਖਰਾਂ ਜਾਂ ਘੱਟ ਦੀ ਹੋਣੀ ਚਾਹੀਦੀ ਹੈ", + "keyDuplicate": "ਇਹ ਕੁੰਜੀ ਪਹਿਲਾਂ ਹੀ ਪ੍ਰੋਫਾਈਲ ਵਿੱਚ ਮੌਜੂਦ ਹੈ", + "valueLabel": "ਮੁੱਲ", + "secretLabel": "ਗੁਪਤ", + "secrets": { + "markSecret": "ਗੁਪਤ ਵਜੋਂ ਮਾਰਕ ਕਰੋ", + "masked": "ਲੁਕਿਆ ਹੋਇਆ ਮੁੱਲ", + "reveal": "ਮੁੱਲ ਪ੍ਰਗਟ ਕਰੋ", + "newValuePlaceholder": "ਨਵਾਂ ਗੁਪਤ ਮੁੱਲ ਦਰਜ ਕਰੋ", + "valuePlaceholder": "ਗੁਪਤ ਮੁੱਲ (ਐਨਕ੍ਰਿਪਟ ਕੀਤਾ)" + }, + "variablesEmpty": "ਹਾਲੇ ਕੋਈ ਵੇਰੀਏਬਲ ਨਹੀਂ", + "variablesSaveFailed": "ਵੇਰੀਏਬਲ ਨੂੰ ਸੁਰੱਖਿਅਤ ਨਹੀਂ ਕੀਤਾ ਜਾ ਸਕਿਆ", + "commandsTitle": "ਸੈੱਟਅੱਪ ਕਮਾਂਡਾਂ", + "addCommand": "ਕਮਾਂਡ ਸ਼ਾਮਲ ਕਰੋ", + "commandLabel": "ਹੁਕਮ", + "commandPlaceholder": "e.g. pnpm install", + "commandsEmpty": "ਹਾਲੇ ਤੱਕ ਕੋਈ ਸੈੱਟਅੱਪ ਕਮਾਂਡ ਨਹੀਂ ਹੈ", + "commandsSaveFailed": "ਸੈੱਟਅੱਪ ਕਮਾਂਡਾਂ ਨੂੰ ਰੱਖਿਅਤ ਨਹੀਂ ਕੀਤਾ ਜਾ ਸਕਿਆ", + "moveUp": "ਉੱਪਰ ਜਾਓ", + "moveDown": "ਹੇਠਾਂ ਚਲੇ ਜਾਓ", + "skillsTitle": "ਹੁਨਰ", + "addSkill": "ਹੁਨਰ ਸ਼ਾਮਲ ਕਰੋ", + "skillNameLabel": "ਹੁਨਰ ਦਾ ਨਾਮ", + "skillMarkdownLabel": "ਹੁਨਰ ਸਮੱਗਰੀ", + "skillsEmpty": "ਅਜੇ ਕੋਈ ਹੁਨਰ ਨਹੀਂ ਹੈ", + "skillNameRequired": "ਇੱਕ ਹੁਨਰ ਦਾ ਨਾਮ ਦਰਜ ਕਰੋ", + "skillNameInvalid": "ਹੁਨਰ ਦਾ ਨਾਮ ਇੱਕ ਅੱਖਰ ਜਾਂ ਅੰਕ ਨਾਲ ਸ਼ੁਰੂ ਹੋਣਾ ਚਾਹੀਦਾ ਹੈ ਅਤੇ ਸਿਰਫ ਛੋਟੇ ਅੱਖਰਾਂ, ਅੰਕਾਂ ਅਤੇ ਡੈਸ਼ਾਂ ਦੀ ਵਰਤੋਂ ਕਰਨਾ ਚਾਹੀਦਾ ਹੈ", + "skillContentRequired": "ਹੁਨਰ ਸਮੱਗਰੀ ਦੀ ਲੋੜ ਹੈ", + "skillSaveFailed": "ਹੁਨਰ ਨੂੰ ਬਚਾ ਨਹੀਂ ਸਕਿਆ", + "skillsHint": "ਇੱਕ SKILL.md ਪੇਸਟ ਕਰੋ ਅਤੇ ਇਸਦਾ ਨਾਮ ਭਰੋ; ਬਿਨਾਂ ਡਰੈਗ ਐਂਡ ਡ੍ਰੌਪ ਦੇ ਆਪਣੇ ਫ਼ੋਨ ਤੋਂ ਹੁਨਰ ਸ਼ਾਮਲ ਕਰੋ।", + "skillEditTitle": "ਹੁਨਰ ਨੂੰ ਸੋਧੋ", + "mcp": { + "title": "MCP ਸਰਵਰ", + "add": "MCP ਸਰਵਰ ਸ਼ਾਮਲ ਕਰੋ", + "empty": "ਅਜੇ ਤੱਕ ਕੋਈ MCP ਸਰਵਰ ਨਹੀਂ ਹੈ", + "name": "ਸਰਵਰ ਦਾ ਨਾਮ", + "type": "ਸਰਵਰ ਦੀ ਕਿਸਮ", + "localType": "ਸਥਾਨਕ", + "remoteType": "ਰਿਮੋਟ", + "command": "ਕਮਾਂਡ ਲਾਈਨ", + "url": "URL", + "environment": "ਵਾਤਾਵਰਨ (JSON)", + "headers": "ਸਿਰਲੇਖ (JSON)", + "timeout": "ਸਮਾਂ ਖ਼ਤਮ", + "secretCount": "ਗੁਪਤ ਗਿਣਤੀ", + "namePlaceholder": "my-server", + "commandPlaceholder": "npx @example/mcp-server", + "jsonPlaceholder": "{\n \"API_KEY\": \"sk-...\"\n}", + "nameRequired": "ਇੱਕ ਸਰਵਰ ਨਾਮ ਦਰਜ ਕਰੋ", + "nameInvalid": "ਨਾਮ ਸਿਰਫ਼ ਅੱਖਰਾਂ, ਸੰਖਿਆਵਾਂ, ਅੰਡਰਸਕੋਰਾਂ ਅਤੇ ਡੈਸ਼ਾਂ ਦੀ ਵਰਤੋਂ ਕਰ ਸਕਦਾ ਹੈ", + "commandRequired": "ਇੱਕ ਕਮਾਂਡ ਦਿਓ", + "commandTooLong": "ਵੱਧ ਤੋਂ ਵੱਧ 50 ਆਰਗੂਮੈਂਟ ਵਰਤੋ, ਹਰ ਇੱਕ 500 ਅੱਖਰਾਂ ਦਾ", + "urlRequired": "ਇੱਕ URL ਦਾਖਲ ਕਰੋ", + "urlInvalid": "ਇੱਕ ਵੈਧ URL ਦਾਖਲ ਕਰੋ", + "urlTooLong": "URL 2048 ਅੱਖਰਾਂ ਜਾਂ ਘੱਟ ਦਾ ਹੋਣਾ ਚਾਹੀਦਾ ਹੈ", + "jsonInvalid": "ਵੈਧ JSON ਦਾਖਲ ਕਰੋ — ਸਟ੍ਰਿੰਗ ਮੁੱਲਾਂ ਦੀ ਇੱਕ ਵਸਤੂ", + "jsonTooLarge": "ਵੱਧ ਤੋਂ ਵੱਧ 50 ਐਂਟਰੀਆਂ ਵਰਤੋ, ਕੁੰਜੀਆਂ 128 ਤੱਕ ਅਤੇ ਮੁੱਲ 4096 ਅੱਖਰਾਂ ਤੱਕ", + "timeoutInvalid": "ਸਮਾਂ ਸਮਾਪਤੀ ਮਿਲੀਸਕਿੰਟ ਦੀ ਪੂਰੀ ਸੰਖਿਆ ਹੋਣੀ ਚਾਹੀਦੀ ਹੈ", + "edit": "MCP ਸਰਵਰ ਦਾ ਸੰਪਾਦਨ ਕਰੋ", + "saveFailed": "MCP ਸਰਵਰ ਨੂੰ ਸੁਰੱਖਿਅਤ ਨਹੀਂ ਕੀਤਾ ਜਾ ਸਕਿਆ" + }, + "agents": { + "title": "ਪ੍ਰੋਫਾਈਲ ਏਜੰਟ", + "add": "ਏਜੰਟ ਸ਼ਾਮਲ ਕਰੋ", + "empty": "ਅਜੇ ਤੱਕ ਕੋਈ ਏਜੰਟ ਨਹੀਂ", + "name": "ਏਜੰਟ ਦਾ ਨਾਮ", + "slug": "ਏਜੰਟ ਸਲੱਗ", + "description": "ਏਜੰਟ ਦਾ ਵੇਰਵਾ", + "model": "ਮਾਡਲ ਓਵਰਰਾਈਡ", + "modelPlaceholder": "ਸ਼ੈਸ਼ਨ ਡਿਫੌਲਟ ਵਰਤੋ", + "prompt": "ਸਿਸਟਮ ਪ੍ਰੋਂਪਟ", + "steps": "ਵੱਧ ਤੋਂ ਵੱਧ ਕਦਮ", + "stepsPlaceholder": "ਉਦਾ. 50", + "temperature": "ਤਾਪਮਾਨ", + "temperaturePlaceholder": "ਉਦਾ. 0.2", + "topP": "top_p", + "topPPlaceholder": "ਉਦਾ. 0.95", + "slugPlaceholder": "my-agent", + "namePlaceholder": "ਮੇਰਾ ਏਜੰਟ", + "promptPlaceholder": "ਤੁਸੀਂ ਇੱਕ...", + "tools": "ਸੰਦ", + "visibility": "ਦਿੱਖ", + "visibilityPrimary": "ਪ੍ਰਾਇਮਰੀ — ਚੋਣਕਾਰ ਵਿੱਚ ਦਿਖਾਇਆ ਗਿਆ ਹੈ", + "visibilitySubagent": "ਸਬਜੈਂਟ — ਸਿਰਫ਼ ਦੂਜਿਆਂ ਤੋਂ ਕਾਲ ਕਰਨ ਯੋਗ", + "visibilityAll": "ਸਾਰੇ — ਦੋਵੇਂ", + "saveFailed": "ਏਜੰਟ ਨੂੰ ਸੁਰੱਖਿਅਤ ਨਹੀਂ ਕੀਤਾ ਜਾ ਸਕਿਆ", + "nameRequired": "ਇੱਕ ਏਜੰਟ ਦਾ ਨਾਮ ਦਰਜ ਕਰੋ", + "slugRequired": "ਇੱਕ ਏਜੰਟ ਸਲੱਗ ਦਾਖਲ ਕਰੋ", + "slugInvalid": "ਸਲੱਗ ਨੂੰ ਇੱਕ ਅੱਖਰ ਨਾਲ ਸ਼ੁਰੂ ਕਰਨਾ ਚਾਹੀਦਾ ਹੈ ਅਤੇ ਸਿਰਫ਼ ਛੋਟੇ ਅੱਖਰਾਂ, ਅੰਕਾਂ ਅਤੇ ਡੈਸ਼ਾਂ ਦੀ ਵਰਤੋਂ ਕਰਨੀ ਚਾਹੀਦੀ ਹੈ", + "slugConflict": "ਉਹ ਸਲੱਗ ਇੱਕ ਬਿਲਟ-ਇਨ ਏਜੰਟ ਦੁਆਰਾ ਰਾਖਵੀਂ ਹੈ", + "edit": "ਏਜੰਟ ਦਾ ਸੰਪਾਦਨ ਕਰੋ" + }, + "slashCommands": { + "title": "ਸਲੈਸ਼ ਕਮਾਂਡਾਂ", + "add": "ਸਲੈਸ਼ ਕਮਾਂਡ ਸ਼ਾਮਲ ਕਰੋ", + "empty": "ਹਾਲੇ ਤੱਕ ਕੋਈ ਸਲੈਸ਼ ਕਮਾਂਡ ਨਹੀਂ ਹੈ", + "name": "ਕਮਾਂਡ ਦਾ ਨਾਮ", + "description": "ਕਮਾਂਡ ਦਾ ਵੇਰਵਾ", + "template": "ਟੈਂਪਲੇਟ", + "namePlaceholder": "explain", + "templatePlaceholder": "$ARGUMENTS ਦੀ ਵਿਆਖਿਆ ਕਰੋ", + "agent": "ਏਜੰਟ", + "subtask": "ਸਬਟਾਸਕ ਵਜੋਂ ਚਲਾਓ", + "edit": "ਸਲੈਸ਼ ਕਮਾਂਡ ਨੂੰ ਸੋਧੋ", + "saveFailed": "ਕਮਾਂਡ ਨੂੰ ਸੁਰੱਖਿਅਤ ਨਹੀਂ ਕੀਤਾ ਜਾ ਸਕਿਆ", + "nameRequired": "ਇੱਕ ਕਮਾਂਡ ਦਾ ਨਾਮ ਦਰਜ ਕਰੋ", + "nameInvalid": "ਨਾਮ ਇੱਕ ਛੋਟੇ ਅੱਖਰ ਨਾਲ ਸ਼ੁਰੂ ਹੋਣਾ ਚਾਹੀਦਾ ਹੈ ਅਤੇ ਸਿਰਫ ਛੋਟੇ ਅੱਖਰਾਂ, ਅੰਕਾਂ ਅਤੇ ਡੈਸ਼ਾਂ ਦੀ ਵਰਤੋਂ ਕਰਨਾ ਚਾਹੀਦਾ ਹੈ", + "templateRequired": "ਇੱਕ ਕਮਾਂਡ ਟੈਮਪਲੇਟ ਦਰਜ ਕਰੋ", + "nameConflict": "ਇਹ ਨਾਮ ਬਿਲਟ-ਇਨ ਕਮਾਂਡ ਦੁਆਰਾ ਰਾਖਵਾਂ ਹੈ" + }, + "repoBindings": { + "title": "ਰਿਪੋਜ਼ ਲਈ ਡਿਫਾਲਟ ਪ੍ਰੋਫਾਈਲ", + "add": "ਪੂਰਵ-ਨਿਰਧਾਰਤ ਸ਼ਾਮਲ ਕਰੋ", + "empty": "ਕੋਈ ਡਿਫੌਲਟ ਕੌਂਫਿਗਰ ਨਹੀਂ ਕੀਤਾ ਗਿਆ", + "emptyDescription": "ਇੱਕ ਰੇਪੋ ਲਈ ਸੈਸ਼ਨ ਸ਼ੁਰੂ ਕਰਨ ਵੇਲੇ ਇੱਕ ਪ੍ਰੋਫਾਈਲ ਨੂੰ ਆਪਣੇ ਆਪ ਲਾਗੂ ਕਰਨ ਲਈ ਇੱਕ ਡਿਫੌਲਟ ਜੋੜੋ।", + "repo": "ਰਿਪੋਜ਼ਟਰੀ ਚੁਣੋ...", + "profile": "ਪ੍ਰੋਫਾਈਲ ਚੁਣੋ", + "remove": "ਅਨਬੰਧ", + "private": "ਨਿਜੀ", + "search": "ਰਿਪੋਜ਼ਟਰੀਆਂ ਖੋਜੋ…", + "addedToast": "\"{{repo}}\" ਲਈ ਪੂਰਵ-ਨਿਰਧਾਰਤ ਪ੍ਰੋਫਾਈਲ ਸੈੱਟ ਕੀਤਾ ਗਿਆ", + "addFailed": "ਪੂਰਵ-ਨਿਰਧਾਰਤ ਪ੍ਰੋਫਾਈਲ ਸੈੱਟ ਨਹੀਂ ਕੀਤਾ ਜਾ ਸਕਿਆ", + "removedToast": "\"{{repo}}\" ਲਈ ਡਿਫੌਲਟ ਪ੍ਰੋਫਾਈਲ ਹਟਾਇਆ ਗਿਆ", + "removeFailed": "ਪੂਰਵ-ਨਿਰਧਾਰਤ ਪ੍ਰੋਫਾਈਲ ਨੂੰ ਹਟਾਇਆ ਨਹੀਂ ਜਾ ਸਕਿਆ", + "pinnedTitle": "ਰਿਪੋਜ਼ਟਰੀਆਂ 'ਤੇ ਪਿੰਨ ਕੀਤਾ ਗਿਆ", + "pinRepo": "ਇੱਕ ਰੈਪੋ ਪਿੰਨ ਕਰੋ", + "pinnedEmpty": "ਕਿਸੇ ਵੀ ਰਿਪੋਜ਼ਟਰੀਆਂ ਵਿੱਚ ਪਿੰਨ ਨਹੀਂ ਕੀਤਾ ਗਿਆ।", + "pinnedDescription": "ਜਦੋਂ ਵੀ ਇੱਕ ਸੈਸ਼ਨ ਇਹਨਾਂ ਰਿਪੋਜ਼ਟਰੀਆਂ ਵਿੱਚੋਂ ਇੱਕ ਨੂੰ ਨਿਸ਼ਾਨਾ ਬਣਾਉਂਦਾ ਹੈ ਤਾਂ ਆਟੋ-ਲੋਡ ਹੁੰਦਾ ਹੈ।", + "pinnedToast": "\"{{repo}}\" 'ਤੇ ਪਿੰਨ ਕੀਤਾ ਗਿਆ", + "pinFailed": "ਰੈਪੋ ਨੂੰ ਪਿੰਨ ਨਹੀਂ ਕੀਤਾ ਜਾ ਸਕਿਆ", + "unboundToast": "ਰੇਪੋ ਅਨਬਾਉਂਡ", + "unbindFailed": "ਰੈਪੋ ਨੂੰ ਅਨਬਾਈਂਡ ਨਹੀਂ ਕੀਤਾ ਜਾ ਸਕਿਆ" + } } } diff --git a/apps/mobile/src/i18n/locales/pl.json b/apps/mobile/src/i18n/locales/pl.json index 9c48f21d230..ec9aba2d131 100644 --- a/apps/mobile/src/i18n/locales/pl.json +++ b/apps/mobile/src/i18n/locales/pl.json @@ -1711,7 +1711,33 @@ "continueFrom": "Kontynuujesz sesję {{title}}.", "importingSession": "Importowanie sesji", "cliCannotContinue": "Ta instancja CLI nie obsługuje kontynuowania sesji. Wybierz Cloud Agent lub inną instancję CLI.", - "importFailed": "Nie udało się zaimportować tej sesji." + "importFailed": "Nie udało się zaimportować tej sesji.", + "pickProfile": "Wybierz profil", + "noProfile": "Brak profilu", + "manageProfiles": "Zarządzaj profilami", + "noProfiles": "Nie ma jeszcze profili. Utwórz go, aby dodać zmienne środowiskowe, serwery MCP i umiejętności.", + "advancedConfig": "Zaawansowana konfiguracja", + "advancedConfigDescription": "Wybierz profil, aby załadować zapisane konfiguracje i ręcznie skonfiguruj dodatkowe zmienne środowiskowe i polecenia instalacyjne", + "saveAsProfile": "Zapisz jako profil", + "saveProfileDescription": "Zapisz bieżące zmienne środowiskowe i polecenia instalacyjne jako profil do wielokrotnego użytku.", + "profileSummary": "{{vars}} zmienne środowiskowe · {{commands}} polecenia konfiguracyjne", + "profileSaved": "Profil „{{name}}” został zapisany", + "organizationProfiles": "Profile organizacji", + "personalProfiles": "Profile osobiste", + "yourProfiles": "Twoje profile", + "failedToLoadProfiles": "Nie udało się załadować profili", + "nameTooLong": "Nazwa profilu jest za długa", + "descriptionTooLong": "Opis jest za długi", + "secretsEncrypted": "Sekrety są szyfrowane przed przechowywaniem.", + "profileActive": "Profil aktywny", + "profilesActive": "Profile aktywne", + "customConfigActive": "Aktywna konfiguracja niestandardowa", + "configNeedsAttention": "Konfiguracja wymaga uwagi", + "repoProfile": "Profil repozytorium", + "selectedProfile": "Wybrany profil", + "manualOverrides": "Ręczne nadpisania", + "openSettingsToReview": "Otwórz Ustawienia, aby sprawdzić", + "profileSelection": "Wybór profilu" }, "partRenderer": { "updatedFileCount_one": "Zaktualizowano {{displayCount}} plik.", @@ -3310,5 +3336,180 @@ "multiplierError": "Podaj mnożnik równy 1 lub większy", "permissionDenied": "Nie masz uprawnień do zarządzania alertami o wydatkach.", "pushOffByCategory": "Wyłączone w Powiadomieniach" + }, + "profiles": { + "title": "Profile", + "entrySubtitle": "Wykorzystuj ponownie ustawienia środowiska w różnych sesjach", + "newProfile": "Nowy profil", + "emptyTitle": "Nie ma jeszcze profili", + "emptyDescription": "Utwórz profil, aby ponownie używać zmiennych środowiskowych, poleceń konfiguracyjnych i umiejętności w różnych sesjach.", + "loadFailed": "Nie udało się załadować profili", + "counts": { + "vars": "{{count}} zmiennych", + "mcp": "{{count}} MCP", + "skills": "{{count}} umiejętności", + "commands": "{{count}} poleceń", + "varsShort": "{{count}}z", + "skillsShort": "{{count}}u", + "commandsShort": "{{count}}p" + }, + "nameLabel": "Nazwa profilu", + "namePlaceholder": "np. Debugowanie backendu", + "descriptionLabel": "Opis profilu", + "descriptionPlaceholder": "Opcjonalny opis", + "ownerLabel": "Właściciel profilu", + "nameRequired": "Wprowadź nazwę profilu", + "createAction": "Utwórz profil", + "createFailed": "Nie udało się utworzyć profilu", + "createdToast": "Profil „{{name}}” został utworzony", + "settingsSectionTitle": "Konfiguracja", + "defaultSectionTitle": "Profil domyślny", + "personalDefaultDescription": "Ładowane automatycznie, gdy żadne repozytorium nie ma przypiętego profilu.", + "organizationDefaultDescription": "Ładowane automatycznie dla członków, którzy nie mają domyślnych ustawień osobistych.", + "setAsDefault": "Ustaw jako domyślny", + "removeDefault": "Usuń jako domyślne", + "saveFailed": "Nie udało się zapisać profilu", + "deleteTitle": "Usuń profil", + "deleteMessage": "Spowoduje to trwałe usunięcie profilu i jego ustawień. Tego nie można cofnąć.", + "deleteFailed": "Nie udało się usunąć profilu", + "deleteBlocked": "Ten profil jest używany przez wyzwalacz elementu webhook. Najpierw usuń go z tych wyzwalaczy.", + "deletedToast": "Profil „{{name}}” został usunięty", + "variablesTitle": "Zmienne środowiskowe", + "addVariable": "Dodaj zmienną", + "keyLabel": "Klawisz", + "keyTooLong": "Klucz może mieć maksymalnie 256 znaków", + "keyDuplicate": "Ten klucz już istnieje w profilu", + "valueLabel": "Wartość", + "secretLabel": "Sekret", + "secrets": { + "markSecret": "Oznacz jako tajne", + "masked": "Ukryta wartość", + "reveal": "Ujawnij wartość", + "newValuePlaceholder": "Wprowadź nową tajną wartość", + "valuePlaceholder": "Tajna wartość (zaszyfrowana)" + }, + "variablesEmpty": "Nie ma jeszcze zmiennych", + "variablesSaveFailed": "Nie udało się zapisać zmiennej", + "commandsTitle": "Polecenia konfiguracyjne", + "addCommand": "Dodaj polecenie", + "commandLabel": "Rozkaz", + "commandPlaceholder": "e.g. pnpm install", + "commandsEmpty": "Nie ma jeszcze żadnych poleceń konfiguracyjnych", + "commandsSaveFailed": "Nie udało się zapisać poleceń konfiguracyjnych", + "moveUp": "Podnieść", + "moveDown": "Opuszczać", + "skillsTitle": "Umiejętności", + "addSkill": "Dodaj umiejętność", + "skillNameLabel": "Nazwa umiejętności", + "skillMarkdownLabel": "Treść umiejętności", + "skillsEmpty": "Nie ma jeszcze żadnych umiejętności", + "skillNameRequired": "Wpisz nazwę umiejętności", + "skillNameInvalid": "Nazwa umiejętności musi zaczynać się od litery lub cyfry i zawierać wyłącznie małe litery, cyfry i myślniki", + "skillContentRequired": "Wymagane są umiejętności", + "skillSaveFailed": "Nie udało się zapisać umiejętności", + "skillsHint": "Wklej SKILL.md, a jego nazwa zostanie wypełniona; dodaj umiejętność ze swojego telefonu bez przeciągania i upuszczania.", + "skillEditTitle": "Edytuj umiejętność", + "mcp": { + "title": "Serwery MCP", + "add": "Dodaj serwer MCP", + "empty": "Nie ma jeszcze serwerów MCP", + "name": "Nazwa serwera", + "type": "Typ serwera", + "localType": "Lokalny", + "remoteType": "Zdalny", + "command": "Linia poleceń", + "url": "Adres URL", + "environment": "Środowisko (JSON)", + "headers": "Nagłówki (JSON)", + "timeout": "Limit czasu", + "secretCount": "Tajna liczba", + "namePlaceholder": "my-server", + "commandPlaceholder": "npx @example/mcp-server", + "jsonPlaceholder": "{\n \"API_KEY\": \"sk-...\"\n}", + "nameRequired": "Wprowadź nazwę serwera", + "nameInvalid": "W nazwie można używać wyłącznie liter, cyfr, podkreśleń i myślników", + "commandRequired": "Wprowadź polecenie", + "commandTooLong": "Użyj maksymalnie 50 argumentów po 500 znaków", + "urlRequired": "Wpisz adres URL", + "urlInvalid": "Wprowadź prawidłowy adres URL", + "urlTooLong": "Adres URL może mieć maksymalnie 2048 znaków", + "jsonInvalid": "Wpisz poprawny JSON — obiekt wartości łańcuchowych", + "jsonTooLarge": "Użyj maksymalnie 50 wpisów, klucze do 128 i wartości do 4096 znaków", + "timeoutInvalid": "Limit czasu musi być liczbą całkowitą milisekund", + "edit": "Edytuj serwer MCP", + "saveFailed": "Nie można zapisać serwera MCP" + }, + "agents": { + "title": "Agenci profilowi", + "add": "Dodaj agenta", + "empty": "Nie ma jeszcze agentów", + "name": "Nazwa agenta", + "slug": "Ślimak agenta", + "description": "Opis agenta", + "model": "Nadpisanie modelu", + "modelPlaceholder": "Użyj domyślnej sesji", + "prompt": "Monit systemowy", + "steps": "Maks. kroków", + "stepsPlaceholder": "np. 50", + "temperature": "Temperatura", + "temperaturePlaceholder": "np. 0.2", + "topP": "top_p", + "topPPlaceholder": "np. 0.95", + "slugPlaceholder": "my-agent", + "namePlaceholder": "Mój agent", + "promptPlaceholder": "Jesteś...", + "tools": "Narzędzia", + "visibility": "Widoczność", + "visibilityPrimary": "Podstawowy — pokazany w selektorze", + "visibilitySubagent": "Subagent — można go wywołać tylko od innych osób", + "visibilityAll": "Wszyscy – oba", + "saveFailed": "Nie udało się zapisać agenta", + "nameRequired": "Wprowadź nazwę agenta", + "slugRequired": "Wprowadź ślimak agenta", + "slugInvalid": "Ślimak musi zaczynać się od litery i używać tylko małych liter, cyfr i myślników", + "slugConflict": "Ten ślimak jest zarezerwowany przez wbudowanego agenta", + "edit": "Edytuj agenta" + }, + "slashCommands": { + "title": "Polecenia ukośnikowe", + "add": "Dodaj polecenie ukośnika", + "empty": "Nie ma jeszcze żadnych poleceń ukośnikowych", + "name": "Nazwa polecenia", + "description": "Opis polecenia", + "template": "Szablon", + "namePlaceholder": "explain", + "templatePlaceholder": "Wyjaśnij $ARGUMENTS", + "agent": "Agent", + "subtask": "Uruchom jako podzadanie", + "edit": "Edytuj polecenie ukośnika", + "saveFailed": "Nie udało się zapisać polecenia", + "nameRequired": "Wprowadź nazwę polecenia", + "nameInvalid": "Nazwa musi zaczynać się od małej litery i zawierać wyłącznie małe litery, cyfry i myślniki", + "templateRequired": "Wprowadź szablon polecenia", + "nameConflict": "Nazwa ta jest zarezerwowana przez wbudowane polecenie" + }, + "repoBindings": { + "title": "Domyślne profile dla repozytoriów", + "add": "Dodaj domyślne", + "empty": "Nie skonfigurowano żadnych ustawień domyślnych", + "emptyDescription": "Dodaj ustawienie domyślne, aby automatycznie stosować profil podczas rozpoczynania sesji dla repozytorium.", + "repo": "Wybierz repozytorium…", + "profile": "Wybierz profil", + "remove": "Rozwiązać", + "private": "Prywatny", + "search": "Przeszukaj repozytoria…", + "addedToast": "Domyślny profil ustawiony na „{{repo}}”", + "addFailed": "Nie można ustawić profilu domyślnego", + "removedToast": "Domyślny profil usunięty dla „{{repo}}”", + "removeFailed": "Nie można usunąć profilu domyślnego", + "pinnedTitle": "Przypięte do repozytoriów", + "pinRepo": "Przypnij repozytorium", + "pinnedEmpty": "Nie jest przypięty do żadnego repozytorium.", + "pinnedDescription": "Ładowane automatycznie za każdym razem, gdy sesja jest skierowana do jednego z tych repozytoriów.", + "pinnedToast": "Przypięty do „{{repo}}”", + "pinFailed": "Nie udało się przypiąć repozytorium", + "unboundToast": "Repo niezwiązane", + "unbindFailed": "Nie można odłączyć repozytorium" + } } } diff --git a/apps/mobile/src/i18n/locales/ps.json b/apps/mobile/src/i18n/locales/ps.json index 60f501b1eb2..00c7c96d4d1 100644 --- a/apps/mobile/src/i18n/locales/ps.json +++ b/apps/mobile/src/i18n/locales/ps.json @@ -2191,7 +2191,33 @@ "continueFrom": "د {{title}} ناستې دوام روان دی.", "importingSession": "ناسته واردېږي", "cliCannotContinue": "دا CLI ناستې ته دوام نه شي ورکولی. Cloud Agent یا بل CLI غوره کړئ.", - "importFailed": "دا ناسته وارده نه شوه." + "importFailed": "دا ناسته وارده نه شوه.", + "pickProfile": "یو پروفایل غوره کړئ", + "noProfile": "هیڅ پروفایل نشته", + "manageProfiles": "پروفایلونه اداره کړئ", + "noProfiles": "تراوسه هیڅ پروفایل نشته. د چاپیریال متغیرونو، MCP سرورونو، او مهارتونو اضافه کولو لپاره یو جوړ کړئ.", + "advancedConfig": "پرمختللی ترتیب", + "advancedConfigDescription": "د خوندي شوي تشکیلاتو بارولو لپاره پروفایل غوره کړئ ، او په لاسي ډول د چاپیریال اضافي تغیرات او تنظیم کولو کمانډونه تنظیم کړئ", + "saveAsProfile": "د پروفایل په توګه خوندي کړئ", + "saveProfileDescription": "د اوسني چاپیریال متغیرات او د تنظیم کولو کمانډونه د بیا کارونې وړ پروفایل په توګه خوندي کړئ.", + "profileSummary": "{{vars}} د چاپیریال تغیرات · {{commands}} د ترتیب کولو امرونه", + "profileSaved": "پروفایل \"{{name}}\" خوندي شو", + "organizationProfiles": "د سازمان پروفایلونه", + "personalProfiles": "شخصي پروفایلونه", + "yourProfiles": "ستاسو پروفایلونه", + "failedToLoadProfiles": "د پروفایلونو په پورته کولو کې پاتې راغلی", + "nameTooLong": "د پروفایل نوم ډیر اوږد دی", + "descriptionTooLong": "تفصیل ډیر اوږد دی", + "secretsEncrypted": "رازونه د ذخیره کولو دمخه کوډ شوي دي.", + "profileActive": "پروفایل فعال دی", + "profilesActive": "فعال پروفایلونه", + "customConfigActive": "دودیز تشکیل فعال دی", + "configNeedsAttention": "تشکیلاتو ته اړتیا لري", + "repoProfile": "د ریپو پروفایل", + "selectedProfile": "ټاکل شوی پروفایل", + "manualOverrides": "لاسي اوورډونه", + "openSettingsToReview": "د بیاکتنې لپاره تنظیمات خلاص کړئ", + "profileSelection": "د پروفایل انتخاب" }, "partRenderer": { "updatedFileCount_one": "{{displayCount}} فایل تازه شو.", @@ -3224,5 +3250,180 @@ "multiplierError": "د 1 یا ډېر ضریب ولیکه", "permissionDenied": "تاسو د لګښت خبرتیاوو د اداره کولو اجازه نه لرئ.", "pushOffByCategory": "په خبرتیاوو کې بند" + }, + "profiles": { + "title": "پروفایلونه", + "entrySubtitle": "په غونډو کې د چاپیریال تنظیمات بیا وکاروئ", + "newProfile": "نوی پروفایل", + "emptyTitle": "تراوسه هیڅ پروفایل نشته", + "emptyDescription": "د چاپیریال متغیرونو ، تنظیم کولو امرونو ، او په غونډو کې مهارتونه د بیا کارولو لپاره پروفایل رامینځته کړئ.", + "loadFailed": "پروفایلونه نشي پورته کولی", + "counts": { + "vars": "{{count}} تغیرات", + "mcp": "{{count}} MCP", + "skills": "{{count}} مهارتونه", + "commands": "{{count}} امرونه", + "varsShort": "{{count}}ت", + "skillsShort": "{{count}}م", + "commandsShort": "{{count}}ا" + }, + "nameLabel": "د پروفایل نوم", + "namePlaceholder": "د مثال په توګه د شاتړ ډیبګ کول", + "descriptionLabel": "د پروفایل توضیحات", + "descriptionPlaceholder": "اختیاري توضیحات", + "ownerLabel": "د پروفایل خاوند", + "nameRequired": "د پروفایل نوم دننه کړئ", + "createAction": "پروفایل جوړ کړئ", + "createFailed": "پروفایل جوړ نه شو", + "createdToast": "پروفایل \"{{name}}\" جوړ شو", + "settingsSectionTitle": "ترتیب", + "defaultSectionTitle": "ډیفالټ پروفایل", + "personalDefaultDescription": "په اوتومات ډول بار شوی کله چې هیڅ ذخیره پین شوی پروفایل نلري.", + "organizationDefaultDescription": "د غړو لپاره په اتوماتيک ډول بار شوی څوک چې شخصي ډیفالټ نلري.", + "setAsDefault": "د ډیفالټ په توګه تنظیم کړئ", + "removeDefault": "د ډیفالټ په توګه لرې کړئ", + "saveFailed": "پروفایل خوندي نشو کړای", + "deleteTitle": "پروفایل ړنګ کړئ", + "deleteMessage": "دا د تل لپاره پروفایل او د هغې تنظیمات لرې کوي. دا نشي رد کیدی.", + "deleteFailed": "پروفایل ړنګ نه شو", + "deleteBlocked": "دا پروفایل د ویب هک محرک لخوا کارول کیږي. دا لومړی له دې محرکاتو څخه لرې کړئ.", + "deletedToast": "پروفایل \"{{name}}\" ړنګ شو", + "variablesTitle": "د چاپیریال تغیرات", + "addVariable": "متغیر اضافه کړئ", + "keyLabel": "کلید", + "keyTooLong": "کلید باید 256 توري یا لږ وي", + "keyDuplicate": "دا کلید دمخه په پروفایل کې شته", + "valueLabel": "ارزښت", + "secretLabel": "پټه", + "secrets": { + "markSecret": "د پټ په توګه نښه کړئ", + "masked": "پټ ارزښت", + "reveal": "ارزښت څرګند کړئ", + "newValuePlaceholder": "نوی پټ ارزښت ولیکئ", + "valuePlaceholder": "پټ ارزښت (کوډ شوی)" + }, + "variablesEmpty": "تر اوسه کوم تغیرات نشته", + "variablesSaveFailed": "متغیر خوندي نه شو", + "commandsTitle": "امرونه تنظیم کړئ", + "addCommand": "کمانډ اضافه کړئ", + "commandLabel": "امر", + "commandPlaceholder": "e.g. pnpm install", + "commandsEmpty": "تر اوسه د تنظیم کولو امرونه ندي", + "commandsSaveFailed": "د ترتیب کولو کمانډونه خوندي نشو کولی", + "moveUp": "پورته لاړ شه", + "moveDown": "لاندې حرکت وکړئ", + "skillsTitle": "مهارتونه", + "addSkill": "مهارتونه اضافه کړئ", + "skillNameLabel": "د مهارت نوم", + "skillMarkdownLabel": "د مهارت منځپانګه", + "skillsEmpty": "تراوسه هیڅ مهارت نه لري", + "skillNameRequired": "د مهارت نوم دننه کړئ", + "skillNameInvalid": "د مهارت نوم باید د یوه لیک یا عدد سره پیل شي او یوازې د کوچنیو حروفو، ګوتو او ډیشونو څخه کار واخلئ", + "skillContentRequired": "د مهارت منځپانګې ته اړتیا ده", + "skillSaveFailed": "مهارت نه شو ژغورلی", + "skillsHint": "یو SKILL.md پیسټ کړئ او نوم یې ډک شي؛ پرته له ډریګ او ډراپ خپل تلیفون څخه مهارت اضافه کړئ.", + "skillEditTitle": "د ترمیم مهارت", + "mcp": { + "title": "د MCP سرورونه", + "add": "د MCP سرور اضافه کړئ", + "empty": "تراوسه د MCP سرورونه نشته", + "name": "د سرور نوم", + "type": "د سرور ډول", + "localType": "ځايي", + "remoteType": "لرې", + "command": "د قوماندې کرښه", + "url": "URL", + "environment": "چاپیریال (JSON)", + "headers": "سرلیکونه (JSON)", + "timeout": "وخت ختم شو", + "secretCount": "پټه شمیره", + "namePlaceholder": "my-server", + "commandPlaceholder": "npx @example/mcp-server", + "jsonPlaceholder": "{\n \"API_KEY\": \"sk-...\"\n}", + "nameRequired": "د سرور نوم دننه کړئ", + "nameInvalid": "نوم کولی شي یوازې توري، شمیرې، انډر سکورونه او ډشونه وکاروي", + "commandRequired": "یو کمانډ دننه کړئ", + "commandTooLong": "تر 50 پورې دلیلونه وکاروئ، هر یو 500 توري", + "urlRequired": "یو URL داخل کړئ", + "urlInvalid": "یو معتبر URL داخل کړئ", + "urlTooLong": "URL باید 2048 توري یا لږ وي", + "jsonInvalid": "د اعتبار وړ JSON داخل کړئ - د تار ارزښتونو څیز", + "jsonTooLarge": "تر 50 پورې ننوتنې وکاروئ، کلیدونه تر 128 او ارزښتونه تر 4096 تورو پورې", + "timeoutInvalid": "د وخت پای باید د ملی ثانیو بشپړ شمیر وي", + "edit": "د MCP سرور ترمیم کړئ", + "saveFailed": "د MCP سرور خوندي نه شو" + }, + "agents": { + "title": "پروفایل اجنټان", + "add": "اجنټ اضافه کړئ", + "empty": "تراوسه هیڅ استازی نشته", + "name": "د اجنټ نوم", + "slug": "اجنټ سلګ", + "description": "د اجنټ تفصیل", + "model": "موډل اوورډ", + "modelPlaceholder": "د سیشن ډیفالټ وکاروئ", + "prompt": "د سیسټم چټکتیا", + "steps": "د اعظمي ګامونو", + "stepsPlaceholder": "لکه 50", + "temperature": "تودوخه", + "temperaturePlaceholder": "لکه 0.2", + "topP": "top_p", + "topPPlaceholder": "لکه 0.95", + "slugPlaceholder": "my-agent", + "namePlaceholder": "زما اجنټ", + "promptPlaceholder": "تاسو یو...", + "tools": "وسیلې", + "visibility": "لید", + "visibilityPrimary": "لومړني - په غوره کونکي کې ښودل شوي", + "visibilitySubagent": "Subagent - یوازې د نورو څخه د غوښتنې وړ", + "visibilityAll": "ټول - دواړه", + "saveFailed": "اجنټ خوندي نشو کړای", + "nameRequired": "د اجنټ نوم دننه کړئ", + "slugRequired": "د اجنټ سلګ داخل کړئ", + "slugInvalid": "سلګ باید د یوه لیک سره پیل شي او یوازې کوچني توري، عددونه او ډیشونه وکاروي", + "slugConflict": "دا سلګ د جوړ شوي اجنټ لخوا ساتل کیږي", + "edit": "د ایجنټ ترمیم" + }, + "slashCommands": { + "title": "د سلیش امرونه", + "add": "د سلیش کمانډ اضافه کړئ", + "empty": "تر اوسه د سلیش حکمونه نشته", + "name": "د قوماندې نوم", + "description": "د قوماندې توضیحات", + "template": "کينډۍ", + "namePlaceholder": "explain", + "templatePlaceholder": "$ARGUMENTS تشریح کړئ", + "agent": "اجنټ", + "subtask": "د فرعي دندې په توګه چلول", + "edit": "د سلیش کمانډ ایډیټ کړئ", + "saveFailed": "کمانډ خوندي نشو کړای", + "nameRequired": "د کمانډ نوم دننه کړئ", + "nameInvalid": "نوم باید د کوچنیو تورو سره پیل شي او یوازې د کوچنیو تورو، عددونو او ډیشونو څخه کار واخلئ", + "templateRequired": "د کمانډ ټیمپلیټ دننه کړئ", + "nameConflict": "دا نوم د جوړ شوي قوماندې لخوا ساتل کیږي" + }, + "repoBindings": { + "title": "د ریپوز لپاره ډیفالټ پروفایلونه", + "add": "ډیفالټ اضافه کړئ", + "empty": "هیڅ ډیفالټ نه دی ترتیب شوی", + "emptyDescription": "د ریپو لپاره ناستې پیل کولو پرمهال په اتوماتيک ډول پروفایل پلي کولو لپاره ډیفالټ اضافه کړئ.", + "repo": "ذخیره غوره کړئ…", + "profile": "پروفایل وټاکئ", + "remove": "تړل", + "private": "شخصي", + "search": "ذخیره لټونه…", + "addedToast": "ډیفالټ پروفایل د \"{{repo}}\" لپاره ټاکل شوی", + "addFailed": "ډیفالټ پروفایل نشي تنظیمولی", + "removedToast": "د \"{{repo}}\" لپاره ډیفالټ پروفایل لرې شوی", + "removeFailed": "د ډیفالټ پروفایل لرې نشو کولی", + "pinnedTitle": "زیرمو ته ځړول شوی", + "pinRepo": "یو ریپو پین کړئ", + "pinnedEmpty": "په هیڅ ذخیره کې نده تړل شوې.", + "pinnedDescription": "په اتوماتيک ډول بار شوی کله چې یوه ناسته د دې زیرمو څخه یوه په نښه کوي.", + "pinnedToast": "\"{{repo}}\" ته پنډ شوی", + "pinFailed": "ریپو ونشو کړای", + "unboundToast": "ریپو بې حده", + "unbindFailed": "د ریپو بندول نشو کولی" + } } } diff --git a/apps/mobile/src/i18n/locales/pt-BR.json b/apps/mobile/src/i18n/locales/pt-BR.json index aac71477018..61970a8e427 100644 --- a/apps/mobile/src/i18n/locales/pt-BR.json +++ b/apps/mobile/src/i18n/locales/pt-BR.json @@ -1668,7 +1668,33 @@ "continueFrom": "Continuando a sessão {{title}}.", "importingSession": "Importando sessão", "cliCannotContinue": "Esta CLI não permite continuar uma sessão. Escolha o Cloud Agent ou outra CLI.", - "importFailed": "Não foi possível importar esta sessão." + "importFailed": "Não foi possível importar esta sessão.", + "pickProfile": "Escolha um perfil", + "noProfile": "Sem perfil", + "manageProfiles": "Gerenciar perfis", + "noProfiles": "Ainda não há perfis. Crie um para adicionar variáveis de ambiente, servidores MCP e habilidades.", + "advancedConfig": "Configuração avançada", + "advancedConfigDescription": "Selecione um perfil para carregar configurações salvas e configure manualmente variáveis de ambiente adicionais e comandos de configuração", + "saveAsProfile": "Salvar como perfil", + "saveProfileDescription": "Salve as variáveis de ambiente atuais e os comandos de configuração como um perfil reutilizável.", + "profileSummary": "{{vars}} variáveis de ambiente · {{commands}} comandos de configuração", + "profileSaved": "Perfil \"{{name}}\" salvo", + "organizationProfiles": "Perfis de organização", + "personalProfiles": "Perfis Pessoais", + "yourProfiles": "Seus perfis", + "failedToLoadProfiles": "Falha ao carregar perfis", + "nameTooLong": "O nome do perfil é muito longo", + "descriptionTooLong": "A descrição é muito longa", + "secretsEncrypted": "Os segredos são criptografados antes do armazenamento.", + "profileActive": "Perfil ativo", + "profilesActive": "Perfis ativos", + "customConfigActive": "Configuração personalizada ativa", + "configNeedsAttention": "A configuração precisa de atenção", + "repoProfile": "Perfil de repositório", + "selectedProfile": "Perfil selecionado", + "manualOverrides": "Substituições manuais", + "openSettingsToReview": "Abra Configurações para revisar", + "profileSelection": "Seleção de perfil" }, "partRenderer": { "updatedFileCount_one": "{{displayCount}} arquivo foi atualizado.", @@ -3267,5 +3293,180 @@ "multiplierError": "Insira um multiplicador de 1 ou mais", "permissionDenied": "Você não tem permissão para gerenciar os alertas de gastos.", "pushOffByCategory": "Desativado em Notificações" + }, + "profiles": { + "title": "Perfis", + "entrySubtitle": "Reutilizar configurações de ambiente entre sessões", + "newProfile": "Novo perfil", + "emptyTitle": "Ainda não há perfis", + "emptyDescription": "Crie um perfil para reutilizar variáveis de ambiente, comandos de configuração e habilidades entre sessões.", + "loadFailed": "Não foi possível carregar perfis", + "counts": { + "vars": "{{count}} variáveis", + "mcp": "{{count}} MCP", + "skills": "{{count}} habilidades", + "commands": "{{count}} comandos", + "varsShort": "{{count}}v", + "skillsShort": "{{count}}h", + "commandsShort": "{{count}}c" + }, + "nameLabel": "Nome do perfil", + "namePlaceholder": "por exemplo Depuração de back-end", + "descriptionLabel": "Descrição do perfil", + "descriptionPlaceholder": "Descrição opcional", + "ownerLabel": "Proprietário do perfil", + "nameRequired": "Insira um nome de perfil", + "createAction": "Criar perfil", + "createFailed": "Não foi possível criar o perfil", + "createdToast": "Perfil \"{{name}}\" criado", + "settingsSectionTitle": "Configuração", + "defaultSectionTitle": "Perfil padrão", + "personalDefaultDescription": "Carregado automaticamente quando nenhum repositório possui um perfil fixado.", + "organizationDefaultDescription": "Carregado automaticamente para membros que não possuem padrão pessoal.", + "setAsDefault": "Definir como padrão", + "removeDefault": "Remover como padrão", + "saveFailed": "Não foi possível salvar o perfil", + "deleteTitle": "Excluir perfil", + "deleteMessage": "Isso remove permanentemente o perfil e suas configurações. Isto não pode ser desfeito.", + "deleteFailed": "Não foi possível excluir o perfil", + "deleteBlocked": "Este perfil é usado por um gatilho de webhook. Remova-o desses gatilhos primeiro.", + "deletedToast": "Perfil \"{{name}}\" excluído", + "variablesTitle": "Variáveis de ambiente", + "addVariable": "Adicionar variável", + "keyLabel": "Chave", + "keyTooLong": "A chave deve ter 256 caracteres ou menos", + "keyDuplicate": "Esta chave já existe no perfil", + "valueLabel": "Valor", + "secretLabel": "Segredo", + "secrets": { + "markSecret": "Marcar como secreto", + "masked": "Valor oculto", + "reveal": "Revelar valor", + "newValuePlaceholder": "Insira um novo valor secreto", + "valuePlaceholder": "Valor secreto (criptografado)" + }, + "variablesEmpty": "Ainda não há variáveis", + "variablesSaveFailed": "Não foi possível salvar a variável", + "commandsTitle": "Comandos de configuração", + "addCommand": "Adicionar comando", + "commandLabel": "Comando", + "commandPlaceholder": "e.g. pnpm install", + "commandsEmpty": "Nenhum comando de configuração ainda", + "commandsSaveFailed": "Não foi possível salvar os comandos de configuração", + "moveUp": "Subir", + "moveDown": "Mover para baixo", + "skillsTitle": "Habilidades", + "addSkill": "Adicionar habilidade", + "skillNameLabel": "Nome da habilidade", + "skillMarkdownLabel": "Conteúdo de habilidade", + "skillsEmpty": "Nenhuma habilidade ainda", + "skillNameRequired": "Insira um nome de habilidade", + "skillNameInvalid": "O nome da habilidade deve começar com uma letra ou dígito e usar apenas letras minúsculas, dígitos e travessões", + "skillContentRequired": "O conteúdo da habilidade é obrigatório", + "skillSaveFailed": "Não foi possível salvar a habilidade", + "skillsHint": "Cole um SKILL.md e seu nome será preenchido; adicione uma habilidade do seu telefone sem arrastar e soltar.", + "skillEditTitle": "Editar habilidade", + "mcp": { + "title": "Servidores MCP", + "add": "Adicionar servidor MCP", + "empty": "Ainda não há servidores MCP", + "name": "Nome do servidor", + "type": "Tipo de servidor", + "localType": "Local", + "remoteType": "Remoto", + "command": "Linha de comando", + "url": "URL", + "environment": "Meio Ambiente (JSON)", + "headers": "Cabeçalhos (JSON)", + "timeout": "Tempo esgotado", + "secretCount": "Contagem secreta", + "namePlaceholder": "my-server", + "commandPlaceholder": "npx @example/mcp-server", + "jsonPlaceholder": "{\n \"API_KEY\": \"sk-...\"\n}", + "nameRequired": "Insira um nome de servidor", + "nameInvalid": "O nome pode usar apenas letras, números, sublinhados e travessões", + "commandRequired": "Digite um comando", + "commandTooLong": "Use no máximo 50 argumentos de 500 caracteres cada", + "urlRequired": "Insira um URL", + "urlInvalid": "Insira um URL válido", + "urlTooLong": "A URL deve ter 2048 caracteres ou menos", + "jsonInvalid": "Insira JSON válido – um objeto de valores de string", + "jsonTooLarge": "Use no máximo 50 entradas, chaves de até 128 e valores de até 4096 caracteres", + "timeoutInvalid": "O tempo limite deve ser um número inteiro de milissegundos", + "edit": "Editar servidor MCP", + "saveFailed": "Não foi possível salvar o servidor MCP" + }, + "agents": { + "title": "Agentes de perfil", + "add": "Adicionar agente", + "empty": "Nenhum agente ainda", + "name": "Nome do agente", + "slug": "Lesma de agente", + "description": "Descrição do agente", + "model": "Substituição de modelo", + "modelPlaceholder": "Usar sessão padrão", + "prompt": "Alerta do sistema", + "steps": "Etapas máximas", + "stepsPlaceholder": "ex. 50", + "temperature": "Temperatura", + "temperaturePlaceholder": "ex. 0.2", + "topP": "top_p", + "topPPlaceholder": "ex. 0.95", + "slugPlaceholder": "my-agent", + "namePlaceholder": "Meu agente", + "promptPlaceholder": "Você é um...", + "tools": "Ferramentas", + "visibility": "Visibilidade", + "visibilityPrimary": "Primário — mostrado no seletor", + "visibilitySubagent": "Subagente – só pode ser chamado por outros", + "visibilityAll": "Todos - ambos", + "saveFailed": "Não foi possível salvar o agente", + "nameRequired": "Insira um nome de agente", + "slugRequired": "Insira um slug de agente", + "slugInvalid": "Slug deve começar com uma letra e usar apenas letras minúsculas, dígitos e travessões", + "slugConflict": "Esse slug é reservado por um agente integrado", + "edit": "Editar agente" + }, + "slashCommands": { + "title": "Comandos de barra", + "add": "Adicionar comando de barra", + "empty": "Ainda não há comandos de barra", + "name": "Nome do comando", + "description": "Descrição do comando", + "template": "Modelo", + "namePlaceholder": "explain", + "templatePlaceholder": "Explique $ARGUMENTS", + "agent": "Agente", + "subtask": "Executar como subtarefa", + "edit": "Editar comando de barra", + "saveFailed": "Não foi possível salvar o comando", + "nameRequired": "Digite um nome de comando", + "nameInvalid": "O nome deve começar com uma letra minúscula e usar apenas letras minúsculas, dígitos e travessões", + "templateRequired": "Insira um modelo de comando", + "nameConflict": "Esse nome é reservado por um comando interno" + }, + "repoBindings": { + "title": "Perfis padrão para repositórios", + "add": "Adicionar padrão", + "empty": "Nenhum padrão configurado", + "emptyDescription": "Adicione um padrão para aplicar automaticamente um perfil ao iniciar sessões de um repositório.", + "repo": "Selecione o repositório…", + "profile": "Selecione o perfil", + "remove": "Desvincular", + "private": "Privado", + "search": "Pesquisar repositórios…", + "addedToast": "Perfil padrão definido para \"{{repo}}\"", + "addFailed": "Não foi possível definir o perfil padrão", + "removedToast": "Perfil padrão removido para \"{{repo}}\"", + "removeFailed": "Não foi possível remover o perfil padrão", + "pinnedTitle": "Fixado em repositórios", + "pinRepo": "Fixar um repositório", + "pinnedEmpty": "Não fixado em nenhum repositório.", + "pinnedDescription": "Carregado automaticamente sempre que uma sessão tem como alvo um desses repositórios.", + "pinnedToast": "Fixado em \"{{repo}}\"", + "pinFailed": "Não foi possível fixar o repositório", + "unboundToast": "Repo não consolidado", + "unbindFailed": "Não foi possível desvincular o repositório" + } } } diff --git a/apps/mobile/src/i18n/locales/pt.json b/apps/mobile/src/i18n/locales/pt.json index f98490d8d80..526be450cc9 100644 --- a/apps/mobile/src/i18n/locales/pt.json +++ b/apps/mobile/src/i18n/locales/pt.json @@ -2219,7 +2219,33 @@ "continueFrom": "A sessão {{title}} está a ser retomada.", "importingSession": "A importar a sessão", "cliCannotContinue": "Esta CLI não permite continuar uma sessão. Escolha o Cloud Agent ou outra CLI.", - "importFailed": "Não foi possível importar esta sessão." + "importFailed": "Não foi possível importar esta sessão.", + "pickProfile": "Escolha um perfil", + "noProfile": "Sem perfil", + "manageProfiles": "Gerir perfis", + "noProfiles": "Ainda não há perfis. Crie um para adicionar variáveis de ambiente, servidores MCP e competências.", + "advancedConfig": "Configuração avançada", + "advancedConfigDescription": "Selecione um perfil para carregar configurações guardadas e configure manualmente variáveis de ambiente e comandos de configuração adicionais", + "saveAsProfile": "Guardar como perfil", + "saveProfileDescription": "Guarde as variáveis de ambiente e os comandos de configuração atuais como um perfil reutilizável.", + "profileSummary": "{{vars}} variáveis de ambiente · {{commands}} comandos de configuração", + "profileSaved": "Perfil \"{{name}}\" guardado", + "organizationProfiles": "Perfis da organização", + "personalProfiles": "Perfis pessoais", + "yourProfiles": "Os teus perfis", + "failedToLoadProfiles": "Não foi possível carregar os perfis", + "nameTooLong": "O nome do perfil é demasiado longo", + "descriptionTooLong": "A descrição é demasiado longa", + "secretsEncrypted": "Os segredos são encriptados antes de serem guardados.", + "profileActive": "Perfil ativo", + "profilesActive": "Perfis ativos", + "customConfigActive": "Configuração personalizada ativa", + "configNeedsAttention": "A configuração precisa de atenção", + "repoProfile": "Perfil do repositório", + "selectedProfile": "Perfil selecionado", + "manualOverrides": "Substituições manuais", + "openSettingsToReview": "Abra as definições para rever", + "profileSelection": "Seleção de perfil" }, "partRenderer": { "updatedFileCount_one": "Foi atualizado {{displayCount}} ficheiro.", @@ -3267,5 +3293,180 @@ "multiplierError": "Introduza um multiplicador de 1 ou mais", "permissionDenied": "Não tem permissão para gerir os alertas de gastos.", "pushOffByCategory": "Desativado em Notificações" + }, + "profiles": { + "title": "Perfis", + "entrySubtitle": "Reutilizar definições de ambiente entre sessões", + "newProfile": "Novo perfil", + "emptyTitle": "Ainda não há perfis", + "emptyDescription": "Crie um perfil para reutilizar variáveis de ambiente, comandos de configuração e competências entre sessões.", + "loadFailed": "Não foi possível carregar perfis", + "counts": { + "vars": "{{count}} variáveis", + "mcp": "{{count}} MCP", + "skills": "{{count}} competências", + "commands": "{{count}} comandos", + "varsShort": "{{count}}v", + "skillsShort": "{{count}}comp", + "commandsShort": "{{count}}cmd" + }, + "nameLabel": "Nome do perfil", + "namePlaceholder": "ex.: Depuração de back-end", + "descriptionLabel": "Descrição do perfil", + "descriptionPlaceholder": "Descrição opcional", + "ownerLabel": "Proprietário do perfil", + "nameRequired": "Introduza um nome de perfil", + "createAction": "Criar perfil", + "createFailed": "Não foi possível criar o perfil", + "createdToast": "Perfil \"{{name}}\" criado", + "settingsSectionTitle": "Configuração", + "defaultSectionTitle": "Perfil predefinido", + "personalDefaultDescription": "Carregado automaticamente quando nenhum repositório tem um perfil fixado.", + "organizationDefaultDescription": "Carregado automaticamente para membros que não têm predefinição pessoal.", + "setAsDefault": "Definir como predefinido", + "removeDefault": "Remover predefinição", + "saveFailed": "Não foi possível guardar o perfil", + "deleteTitle": "Eliminar perfil", + "deleteMessage": "Elimina permanentemente o perfil e as respetivas definições. Esta ação não pode ser anulada.", + "deleteFailed": "Não foi possível eliminar o perfil", + "deleteBlocked": "Este perfil é usado por um acionador de webhook. Remova-o primeiro desses acionadores.", + "deletedToast": "Perfil \"{{name}}\" eliminado", + "variablesTitle": "Variáveis de ambiente", + "addVariable": "Adicionar variável", + "keyLabel": "Chave", + "keyTooLong": "A chave deve ter 256 caracteres ou menos", + "keyDuplicate": "Esta chave já existe no perfil", + "valueLabel": "Valor", + "secretLabel": "Segredo", + "secrets": { + "markSecret": "Marcar como secreto", + "masked": "Valor oculto", + "reveal": "Mostrar valor", + "newValuePlaceholder": "Introduza um novo valor secreto", + "valuePlaceholder": "Valor secreto (encriptado)" + }, + "variablesEmpty": "Ainda não há variáveis", + "variablesSaveFailed": "Não foi possível guardar a variável", + "commandsTitle": "Comandos de configuração", + "addCommand": "Adicionar comando", + "commandLabel": "Comando", + "commandPlaceholder": "e.g. pnpm install", + "commandsEmpty": "Ainda não há comandos de configuração", + "commandsSaveFailed": "Não foi possível guardar os comandos de configuração", + "moveUp": "Mover para cima", + "moveDown": "Mover para baixo", + "skillsTitle": "Competências", + "addSkill": "Adicionar competência", + "skillNameLabel": "Nome da competência", + "skillMarkdownLabel": "Conteúdo da competência", + "skillsEmpty": "Ainda não há competências", + "skillNameRequired": "Introduza um nome de competência", + "skillNameInvalid": "O nome da competência deve começar por uma letra ou dígito e usar apenas letras minúsculas, dígitos e hífenes", + "skillContentRequired": "O conteúdo da competência é obrigatório", + "skillSaveFailed": "Não foi possível guardar a competência", + "skillsHint": "Cole um SKILL.md e o nome é preenchido automaticamente; adicione uma competência a partir do telemóvel, sem arrastar e largar.", + "skillEditTitle": "Editar competência", + "mcp": { + "title": "Servidores MCP", + "add": "Adicionar servidor MCP", + "empty": "Ainda não há servidores MCP", + "name": "Nome do servidor", + "type": "Tipo de servidor", + "localType": "Local", + "remoteType": "Remoto", + "command": "Linha de comando", + "url": "URL", + "environment": "Ambiente (JSON)", + "headers": "Cabeçalhos (JSON)", + "timeout": "Tempo limite", + "secretCount": "Número de segredos", + "namePlaceholder": "my-server", + "commandPlaceholder": "npx @example/mcp-server", + "jsonPlaceholder": "{\n \"API_KEY\": \"sk-...\"\n}", + "nameRequired": "Introduza um nome de servidor", + "nameInvalid": "O nome pode usar apenas letras, números, sublinhados e hífenes", + "commandRequired": "Introduza um comando", + "commandTooLong": "Utilize no máximo 50 argumentos de 500 caracteres cada", + "urlRequired": "Introduza um URL", + "urlInvalid": "Introduza um URL válido", + "urlTooLong": "O URL deve ter 2048 caracteres ou menos", + "jsonInvalid": "Introduza JSON válido — um objeto de cadeias de caracteres", + "jsonTooLarge": "Utilize no máximo 50 entradas, chaves até 128 e valores até 4096 caracteres", + "timeoutInvalid": "O tempo limite deve ser um número inteiro de milissegundos", + "edit": "Editar servidor MCP", + "saveFailed": "Não foi possível guardar o servidor MCP" + }, + "agents": { + "title": "Agentes do perfil", + "add": "Adicionar agente", + "empty": "Ainda não há agentes", + "name": "Nome do agente", + "slug": "Slug do agente", + "description": "Descrição do agente", + "model": "Substituição do modelo", + "modelPlaceholder": "Usar predefinição da sessão", + "prompt": "Instrução do sistema", + "steps": "Passos máximos", + "stepsPlaceholder": "p. ex. 50", + "temperature": "Temperatura", + "temperaturePlaceholder": "p. ex. 0.2", + "topP": "top_p", + "topPPlaceholder": "p. ex. 0.95", + "slugPlaceholder": "my-agent", + "namePlaceholder": "Meu agente", + "promptPlaceholder": "És um...", + "tools": "Ferramentas", + "visibility": "Visibilidade", + "visibilityPrimary": "Primário — mostrado no seletor", + "visibilitySubagent": "Subagente — só pode ser invocado por outros", + "visibilityAll": "Todos — ambos", + "saveFailed": "Não foi possível guardar o agente", + "nameRequired": "Introduza um nome de agente", + "slugRequired": "Introduza um slug de agente", + "slugInvalid": "O slug deve começar por uma letra e usar apenas letras minúsculas, dígitos e hífenes", + "slugConflict": "Esse slug está reservado por um agente incorporado", + "edit": "Editar agente" + }, + "slashCommands": { + "title": "Comandos de barra", + "add": "Adicionar comando de barra", + "empty": "Ainda não há comandos de barra", + "name": "Nome do comando", + "description": "Descrição do comando", + "template": "Modelo", + "namePlaceholder": "explain", + "templatePlaceholder": "Explica $ARGUMENTS", + "agent": "Agente", + "subtask": "Executar como subtarefa", + "edit": "Editar comando de barra", + "saveFailed": "Não foi possível guardar o comando", + "nameRequired": "Introduza um nome de comando", + "nameInvalid": "O nome deve começar por uma letra minúscula e usar apenas letras minúsculas, dígitos e hífenes", + "templateRequired": "Introduza um modelo de comando", + "nameConflict": "Esse nome está reservado por um comando incorporado" + }, + "repoBindings": { + "title": "Perfis predefinidos para repositórios", + "add": "Adicionar padrão", + "empty": "Nenhuma predefinição configurada", + "emptyDescription": "Adicione um padrão para aplicar automaticamente um perfil ao iniciar sessões de um repositório.", + "repo": "Selecione o repositório…", + "profile": "Selecione o perfil", + "remove": "Desvincular", + "private": "Privado", + "search": "Pesquisar repositórios…", + "addedToast": "Perfil predefinido definido para \"{{repo}}\"", + "addFailed": "Não foi possível definir o perfil predefinido", + "removedToast": "Perfil predefinido removido para \"{{repo}}\"", + "removeFailed": "Não foi possível remover o perfil predefinido", + "pinnedTitle": "Fixado a repositórios", + "pinRepo": "Fixar um repositório", + "pinnedEmpty": "Não fixado a nenhum repositório.", + "pinnedDescription": "Carregado automaticamente sempre que uma sessão tem como destino um destes repositórios.", + "pinnedToast": "Fixado a \"{{repo}}\"", + "pinFailed": "Não foi possível fixar o repositório", + "unboundToast": "Repositório desvinculado", + "unbindFailed": "Não foi possível desvincular o repositório" + } } } diff --git a/apps/mobile/src/i18n/locales/ro.json b/apps/mobile/src/i18n/locales/ro.json index 4cd3b440143..ade5722de19 100644 --- a/apps/mobile/src/i18n/locales/ro.json +++ b/apps/mobile/src/i18n/locales/ro.json @@ -2219,7 +2219,33 @@ "continueFrom": "Se continuă sesiunea {{title}}.", "importingSession": "Se importă sesiunea", "cliCannotContinue": "Acest CLI nu poate continua o sesiune. Alege Cloud Agent sau alt CLI.", - "importFailed": "Nu s-a putut importa această sesiune." + "importFailed": "Nu s-a putut importa această sesiune.", + "pickProfile": "Alegeți un profil", + "noProfile": "Nici un profil", + "manageProfiles": "Gestionați profilurile", + "noProfiles": "Niciun profil încă. Creați unul pentru a adăuga variabile de mediu, servere MCP și abilități.", + "advancedConfig": "Configurare avansată", + "advancedConfigDescription": "Selectați un profil pentru a încărca configurațiile salvate și configurați manual variabilele de mediu suplimentare și comenzile de configurare", + "saveAsProfile": "Salvați ca profil", + "saveProfileDescription": "Salvați variabilele de mediu curente și setați comenzile ca profil reutilizabil.", + "profileSummary": "{{vars}} variabile de mediu · {{commands}} comenzi de configurare", + "profileSaved": "Profilul „{{name}}” a fost salvat", + "organizationProfiles": "Profiluri organizației", + "personalProfiles": "Profiluri personale", + "yourProfiles": "Profilurile dvs", + "failedToLoadProfiles": "Nu s-au încărcat profilurile", + "nameTooLong": "Numele profilului este prea lung", + "descriptionTooLong": "Descrierea este prea lungă", + "secretsEncrypted": "Secretele sunt criptate înainte de stocare.", + "profileActive": "Profil activ", + "profilesActive": "Profiluri active", + "customConfigActive": "Configurație personalizată activă", + "configNeedsAttention": "Configurația necesită atenție", + "repoProfile": "Profil Repo", + "selectedProfile": "Profilul selectat", + "manualOverrides": "Anulări manuale", + "openSettingsToReview": "Deschide Setări pentru a revizui", + "profileSelection": "Selectarea profilului" }, "partRenderer": { "updatedFileCount_one": "S-a actualizat {{displayCount}} fișier.", @@ -3267,5 +3293,180 @@ "multiplierError": "Introdu un multiplicator de 1 sau mai mare", "permissionDenied": "Nu ai permisiunea de a gestiona alertele de cheltuieli.", "pushOffByCategory": "Oprit în Notificări" + }, + "profiles": { + "title": "Profiluri", + "entrySubtitle": "Reutilizați setările de mediu între sesiuni", + "newProfile": "Profil nou", + "emptyTitle": "Niciun profil încă", + "emptyDescription": "Creați un profil pentru a reutiliza variabilele de mediu, comenzile de configurare și abilitățile în sesiuni.", + "loadFailed": "Profilurile nu au putut fi încărcate", + "counts": { + "vars": "{{count}} variabile", + "mcp": "{{count}} MCP", + "skills": "{{count}} abilități", + "commands": "{{count}} comenzi", + "varsShort": "{{count}}v", + "skillsShort": "{{count}}a", + "commandsShort": "{{count}}c" + }, + "nameLabel": "Numele profilului", + "namePlaceholder": "de ex. Depanare backend", + "descriptionLabel": "Descrierea profilului", + "descriptionPlaceholder": "Descriere opțională", + "ownerLabel": "Proprietarul profilului", + "nameRequired": "Introduceți un nume de profil", + "createAction": "Creați un profil", + "createFailed": "Nu s-a putut crea profilul", + "createdToast": "Profilul „{{name}}” a fost creat", + "settingsSectionTitle": "Configurare", + "defaultSectionTitle": "Profil implicit", + "personalDefaultDescription": "Se încarcă automat când niciun depozit nu are un profil fixat.", + "organizationDefaultDescription": "Încărcat automat pentru membrii care nu au o valoare implicită personală.", + "setAsDefault": "Setați ca implicit", + "removeDefault": "Eliminați implicit", + "saveFailed": "Profilul nu a putut fi salvat", + "deleteTitle": "Ștergeți profilul", + "deleteMessage": "Acest lucru elimină definitiv profilul și setările acestuia. Acest lucru nu poate fi anulat.", + "deleteFailed": "Nu s-a putut șterge profilul", + "deleteBlocked": "Acest profil este utilizat de un declanșator webhook. Scoateți-l mai întâi de pe acele declanșatoare.", + "deletedToast": "Profilul „{{name}}” a fost șters", + "variablesTitle": "Variabile de mediu", + "addVariable": "Adăugați o variabilă", + "keyLabel": "Cheie", + "keyTooLong": "Cheia trebuie să aibă 256 de caractere sau mai puțin", + "keyDuplicate": "Această cheie există deja în profil", + "valueLabel": "Valoare", + "secretLabel": "Secret", + "secrets": { + "markSecret": "Marcați ca secret", + "masked": "Valoare ascunsă", + "reveal": "Dezvăluie valoarea", + "newValuePlaceholder": "Introdu o nouă valoare secretă", + "valuePlaceholder": "Valoare secretă (criptată)" + }, + "variablesEmpty": "Încă nu există variabile", + "variablesSaveFailed": "Variabila nu a putut fi salvată", + "commandsTitle": "Configurați comenzi", + "addCommand": "Adăugați comanda", + "commandLabel": "Comanda", + "commandPlaceholder": "e.g. pnpm install", + "commandsEmpty": "Încă nu există comenzi de configurare", + "commandsSaveFailed": "Comenzile de configurare nu au putut fi salvate", + "moveUp": "Mișcă-te în sus", + "moveDown": "Deplasați-vă în jos", + "skillsTitle": "Abilități", + "addSkill": "Adăugați abilitate", + "skillNameLabel": "Numele aptitudinii", + "skillMarkdownLabel": "Conținut de abilități", + "skillsEmpty": "Nu există încă abilități", + "skillNameRequired": "Introduceți un nume de abilitate", + "skillNameInvalid": "Numele aptitudinii trebuie să înceapă cu o literă sau o cifră și să folosească numai litere mici, cifre și liniuțe", + "skillContentRequired": "Conținutul de calificare este necesar", + "skillSaveFailed": "Nu s-a putut salva calificarea", + "skillsHint": "Lipiți un SKILL.md și numele acestuia se completează; adăugați o abilitate de pe telefon fără glisare și plasare.", + "skillEditTitle": "Editați abilitate", + "mcp": { + "title": "servere MCP", + "add": "Adăugați server MCP", + "empty": "Încă nu există servere MCP", + "name": "Numele serverului", + "type": "Tip server", + "localType": "Local", + "remoteType": "Telecomanda", + "command": "Linia de comandă", + "url": "URL", + "environment": "Mediu (JSON)", + "headers": "Anteturi (JSON)", + "timeout": "Pauză", + "secretCount": "Numărarea secretă", + "namePlaceholder": "my-server", + "commandPlaceholder": "npx @example/mcp-server", + "jsonPlaceholder": "{\n \"API_KEY\": \"sk-...\"\n}", + "nameRequired": "Introduceți un nume de server", + "nameInvalid": "Numele poate folosi numai litere, cifre, litere de subliniere și liniuțe", + "commandRequired": "Introduceți o comandă", + "commandTooLong": "Folosiți cel mult 50 de argumente de 500 de caractere fiecare", + "urlRequired": "Introduceți o adresă URL", + "urlInvalid": "Introduceți o adresă URL validă", + "urlTooLong": "URL-ul trebuie să aibă 2048 de caractere sau mai puțin", + "jsonInvalid": "Introduceți JSON valid — un obiect cu valori de șir", + "jsonTooLarge": "Folosiți cel mult 50 de intrări, chei de până la 128 și valori de până la 4096 de caractere", + "timeoutInvalid": "Timeout trebuie să fie un număr întreg de milisecunde", + "edit": "Editați serverul MCP", + "saveFailed": "Serverul MCP nu a putut fi salvat" + }, + "agents": { + "title": "Agenți de profil", + "add": "Adăugați agent", + "empty": "Încă nu există agenți", + "name": "Numele agentului", + "slug": "Agent melc", + "description": "Descrierea agentului", + "model": "Anularea modelului", + "modelPlaceholder": "Utilizați sesiunea implicită", + "prompt": "prompt de sistem", + "steps": "Pași maximi", + "stepsPlaceholder": "de ex. 50", + "temperature": "Temperatură", + "temperaturePlaceholder": "de ex. 0.2", + "topP": "top_p", + "topPPlaceholder": "de ex. 0.95", + "slugPlaceholder": "my-agent", + "namePlaceholder": "Agentul meu", + "promptPlaceholder": "esti un...", + "tools": "Instrumente", + "visibility": "Vizibilitate", + "visibilityPrimary": "Primar — afișat în selector", + "visibilitySubagent": "Subagent - apelabil numai de la alții", + "visibilityAll": "Toate — ambele", + "saveFailed": "Agentul nu a putut fi salvat", + "nameRequired": "Introduceți un nume de agent", + "slugRequired": "Introduceți un melc de agent", + "slugInvalid": "Slug trebuie să înceapă cu o literă și să folosească numai litere mici, cifre și liniuțe", + "slugConflict": "Acest melc este rezervat de un agent încorporat", + "edit": "Editați agentul" + }, + "slashCommands": { + "title": "Comenzi Slash", + "add": "Adăugați comanda slash", + "empty": "Nicio comandă bară oblică încă", + "name": "Numele comenzii", + "description": "Descrierea comenzii", + "template": "Șablon", + "namePlaceholder": "explain", + "templatePlaceholder": "Explicați $ARGUMENTS", + "agent": "Agent", + "subtask": "Rulați ca subsarcină", + "edit": "Comanda Editați slash", + "saveFailed": "Comanda nu a putut fi salvată", + "nameRequired": "Introduceți un nume de comandă", + "nameInvalid": "Numele trebuie să înceapă cu o literă mică și să folosească numai litere mici, cifre și liniuțe", + "templateRequired": "Introduceți un șablon de comandă", + "nameConflict": "Acest nume este rezervat printr-o comandă încorporată" + }, + "repoBindings": { + "title": "Profiluri implicite pentru Repos", + "add": "Adăugați implicit", + "empty": "Nu sunt configurate implicite", + "emptyDescription": "Adăugați o valoare prestabilită pentru a aplica automat un profil la pornirea sesiunilor pentru un depozit.", + "repo": "Selectați depozitul...", + "profile": "Selectați profilul", + "remove": "Desface", + "private": "Privat", + "search": "Căutați în depozite...", + "addedToast": "Profil implicit setat pentru „{{repo}}”", + "addFailed": "Nu s-a putut seta profilul implicit", + "removedToast": "Profilul implicit a fost eliminat pentru „{{repo}}”", + "removeFailed": "Nu s-a putut elimina profilul implicit", + "pinnedTitle": "Fixat în depozite", + "pinRepo": "Fixați un repo", + "pinnedEmpty": "Nu este fixat în niciun depozit.", + "pinnedDescription": "Se încarcă automat ori de câte ori o sesiune vizează unul dintre aceste depozite.", + "pinnedToast": "Fixat la „{{repo}}”", + "pinFailed": "Nu s-a putut fixa repo", + "unboundToast": "Repo nelegat", + "unbindFailed": "Nu s-a putut dezlega repo" + } } } diff --git a/apps/mobile/src/i18n/locales/ru.json b/apps/mobile/src/i18n/locales/ru.json index f4d1c4574c0..53adb618134 100644 --- a/apps/mobile/src/i18n/locales/ru.json +++ b/apps/mobile/src/i18n/locales/ru.json @@ -1711,7 +1711,33 @@ "continueFrom": "Продолжаем сеанс «{{title}}».", "importingSession": "Импорт сеанса", "cliCannotContinue": "Этот CLI не поддерживает продолжение сеанса. Выберите Cloud Agent или другой CLI.", - "importFailed": "Не удалось импортировать этот сеанс." + "importFailed": "Не удалось импортировать этот сеанс.", + "pickProfile": "Выберите профиль", + "noProfile": "Нет профиля", + "manageProfiles": "Управление профилями", + "noProfiles": "Профилей пока нет. Создайте его, чтобы добавить переменные среды, серверы MCP и навыки.", + "advancedConfig": "Расширенная конфигурация", + "advancedConfigDescription": "Выберите профиль для загрузки сохраненных конфигураций и вручную настройте дополнительные переменные среды и команды настройки.", + "saveAsProfile": "Сохранить как профиль", + "saveProfileDescription": "Сохраните текущие переменные среды и команды настройки как профиль многократного использования.", + "profileSummary": "{{vars}} переменные среды · {{commands}} команды настройки", + "profileSaved": "Профиль \"{{name}}\" сохранен.", + "organizationProfiles": "Профили организаций", + "personalProfiles": "Личные профили", + "yourProfiles": "Ваши профили", + "failedToLoadProfiles": "Не удалось загрузить профили.", + "nameTooLong": "Имя профиля слишком длинное", + "descriptionTooLong": "Описание слишком длинное", + "secretsEncrypted": "Секреты шифруются перед хранением.", + "profileActive": "Профиль активен", + "profilesActive": "Профили активны", + "customConfigActive": "Пользовательская конфигурация активна", + "configNeedsAttention": "Конфигурация требует внимания", + "repoProfile": "Профиль репо", + "selectedProfile": "Выбранный профиль", + "manualOverrides": "Ручное переопределение", + "openSettingsToReview": "Откройте настройки для просмотра", + "profileSelection": "Выбор профиля" }, "partRenderer": { "updatedFileCount_one": "Обновлён {{displayCount}} файл.", @@ -3310,5 +3336,180 @@ "multiplierError": "Введите множитель 1 или больше", "permissionDenied": "У вас нет прав на управление оповещениями о расходах.", "pushOffByCategory": "Выключено в уведомлениях" + }, + "profiles": { + "title": "Профили", + "entrySubtitle": "Повторное использование настроек среды между сеансами", + "newProfile": "Новый профиль", + "emptyTitle": "Пока нет профилей", + "emptyDescription": "Создайте профиль для повторного использования переменных среды, команд настройки и навыков в разных сеансах.", + "loadFailed": "Не удалось загрузить профили", + "counts": { + "vars": "{{count}} переменных", + "mcp": "{{count}} MCP", + "skills": "{{count}} навыков", + "commands": "{{count}} команд", + "varsShort": "{{count}}пер", + "skillsShort": "{{count}}нав", + "commandsShort": "{{count}}ком" + }, + "nameLabel": "Имя профиля", + "namePlaceholder": "например Серверная отладка", + "descriptionLabel": "Описание профиля", + "descriptionPlaceholder": "Дополнительное описание", + "ownerLabel": "Владелец профиля", + "nameRequired": "Введите имя профиля", + "createAction": "Создать профиль", + "createFailed": "Не удалось создать профиль", + "createdToast": "Профиль \"{{name}}\" создан.", + "settingsSectionTitle": "Конфигурация", + "defaultSectionTitle": "Профиль по умолчанию", + "personalDefaultDescription": "Загружается автоматически, если ни в одном репозитории нет закрепленного профиля.", + "organizationDefaultDescription": "Автоматически загружается для участников, у которых нет личных настроек по умолчанию.", + "setAsDefault": "Установить по умолчанию", + "removeDefault": "Удалить по умолчанию", + "saveFailed": "Не удалось сохранить профиль", + "deleteTitle": "Удалить профиль", + "deleteMessage": "При этом профиль и его настройки будут удалены без возможности восстановления. Это невозможно отменить.", + "deleteFailed": "Не удалось удалить профиль", + "deleteBlocked": "Этот профиль используется триггером веб-перехватчика. Сначала удалите его из этих триггеров.", + "deletedToast": "Профиль \"{{name}}\" удален.", + "variablesTitle": "Переменные среды", + "addVariable": "Добавить переменную", + "keyLabel": "Ключ", + "keyTooLong": "Ключ должен содержать не более 256 символов", + "keyDuplicate": "Этот ключ уже существует в профиле", + "valueLabel": "Ценить", + "secretLabel": "Секрет", + "secrets": { + "markSecret": "Отметить как секретное", + "masked": "Скрытое значение", + "reveal": "Раскрыть ценность", + "newValuePlaceholder": "Введите новое секретное значение", + "valuePlaceholder": "Секретное значение (зашифровано)" + }, + "variablesEmpty": "Переменных пока нет", + "variablesSaveFailed": "Не удалось сохранить переменную", + "commandsTitle": "Команды настройки", + "addCommand": "Добавить команду", + "commandLabel": "Команда", + "commandPlaceholder": "e.g. pnpm install", + "commandsEmpty": "Команд настройки пока нет", + "commandsSaveFailed": "Не удалось сохранить команды настройки.", + "moveUp": "Вверх", + "moveDown": "Двигаться вниз", + "skillsTitle": "Навыки", + "addSkill": "Добавить навык", + "skillNameLabel": "Название навыка", + "skillMarkdownLabel": "Содержание навыков", + "skillsEmpty": "Навыков пока нет", + "skillNameRequired": "Введите название навыка", + "skillNameInvalid": "Название навыка должно начинаться с буквы или цифры и содержать только строчные буквы, цифры и тире.", + "skillContentRequired": "Требуется содержание навыков.", + "skillSaveFailed": "Не удалось сохранить навык", + "skillsHint": "Вставьте SKILL.md, и его имя заполнится; добавьте навык со своего телефона без перетаскивания.", + "skillEditTitle": "Изменить навык", + "mcp": { + "title": "MCP-серверы", + "add": "Добавить MCP-сервер", + "empty": "Серверов MCP пока нет", + "name": "Имя сервера", + "type": "Тип сервера", + "localType": "Местный", + "remoteType": "Удаленный", + "command": "Командная строка", + "url": "URL-адрес", + "environment": "Окружающая среда (JSON)", + "headers": "Заголовки (JSON)", + "timeout": "Тайм-аут", + "secretCount": "Секретный счетчик", + "namePlaceholder": "my-server", + "commandPlaceholder": "npx @example/mcp-server", + "jsonPlaceholder": "{\n \"API_KEY\": \"sk-...\"\n}", + "nameRequired": "Введите имя сервера", + "nameInvalid": "В имени можно использовать только буквы, цифры, символы подчеркивания и тире.", + "commandRequired": "Введите команду", + "commandTooLong": "Используйте не более 50 аргументов по 500 символов каждый", + "urlRequired": "Введите URL-адрес", + "urlInvalid": "Введите действительный URL-адрес", + "urlTooLong": "URL должен содержать не более 2048 символов", + "jsonInvalid": "Введите действительный JSON — объект строковых значений.", + "jsonTooLarge": "Используйте не более 50 записей, ключи до 128 и значения до 4096 символов", + "timeoutInvalid": "Таймаут должен составлять целое число миллисекунд.", + "edit": "Редактировать MCP-сервер", + "saveFailed": "Не удалось сохранить сервер MCP." + }, + "agents": { + "title": "Профильные агенты", + "add": "Добавить агента", + "empty": "Агентов пока нет", + "name": "Имя агента", + "slug": "Агент-слизняк", + "description": "Описание агента", + "model": "Переопределение модели", + "modelPlaceholder": "Использовать сеанс по умолчанию", + "prompt": "Системная подсказка", + "steps": "Макс. шагов", + "stepsPlaceholder": "напр. 50", + "temperature": "Температура", + "temperaturePlaceholder": "напр. 0.2", + "topP": "top_p", + "topPPlaceholder": "напр. 0.95", + "slugPlaceholder": "my-agent", + "namePlaceholder": "Мой агент", + "promptPlaceholder": "Вы ...", + "tools": "Инструменты", + "visibility": "Видимость", + "visibilityPrimary": "Первичный — отображается в окне выбора.", + "visibilitySubagent": "Субагент — доступен только для вызова от других", + "visibilityAll": "Все — оба", + "saveFailed": "Не удалось сохранить агента.", + "nameRequired": "Введите имя агента", + "slugRequired": "Введите ярлык агента", + "slugInvalid": "Ссылка должна начинаться с буквы и содержать только строчные буквы, цифры и тире.", + "slugConflict": "Этот пул зарезервирован встроенным агентом.", + "edit": "Редактировать агента" + }, + "slashCommands": { + "title": "Слэш-команды", + "add": "Добавить команду косой черты", + "empty": "Слэш-команд пока нет", + "name": "Имя команды", + "description": "Описание команды", + "template": "Шаблон", + "namePlaceholder": "explain", + "templatePlaceholder": "Объясните $ARGUMENTS", + "agent": "Агент", + "subtask": "Запустить как подзадачу", + "edit": "Команда редактирования косой черты", + "saveFailed": "Не удалось сохранить команду", + "nameRequired": "Введите имя команды", + "nameInvalid": "Имя должно начинаться со строчной буквы и использовать только строчные буквы, цифры и тире.", + "templateRequired": "Введите шаблон команды", + "nameConflict": "Это имя зарезервировано встроенной командой" + }, + "repoBindings": { + "title": "Профили по умолчанию для репозиториев", + "add": "Добавить по умолчанию", + "empty": "Значения по умолчанию не настроены", + "emptyDescription": "Добавьте значение по умолчанию для автоматического применения профиля при запуске сеансов репозитория.", + "repo": "Выберите репозиторий…", + "profile": "Выберите профиль", + "remove": "Отвязать", + "private": "Частный", + "search": "Поиск в репозиториях…", + "addedToast": "Профиль по умолчанию установлен для «{{repo}}»", + "addFailed": "Не удалось установить профиль по умолчанию", + "removedToast": "Профиль по умолчанию удален для \"{{repo}}\"", + "removeFailed": "Не удалось удалить профиль по умолчанию.", + "pinnedTitle": "Закреплено в репозиториях", + "pinRepo": "Закрепить репозиторий", + "pinnedEmpty": "Не привязан ни к одному репозиторию.", + "pinnedDescription": "Загружается автоматически всякий раз, когда сеанс нацелен на один из этих репозиториев.", + "pinnedToast": "Закреплено в \"{{repo}}\"", + "pinFailed": "Не удалось закрепить репозиторий", + "unboundToast": "Репо несвязанное", + "unbindFailed": "Не удалось отменить привязку репо" + } } } diff --git a/apps/mobile/src/i18n/locales/si.json b/apps/mobile/src/i18n/locales/si.json index 56760e30c03..ac12c1a5a7e 100644 --- a/apps/mobile/src/i18n/locales/si.json +++ b/apps/mobile/src/i18n/locales/si.json @@ -2191,7 +2191,33 @@ "continueFrom": "{{title}} සැසිය දිගටම කරගෙන යමින් පවතී.", "importingSession": "සැසිය ආයාත කරමින්", "cliCannotContinue": "මෙම CLI අනුවාදයට සැසියක් දිගටම කරගෙන යා නොහැක. Cloud Agent හෝ වෙනත් CLI එකක් තෝරන්න.", - "importFailed": "මෙම සැසිය ආයාත කළ නොහැකි විය." + "importFailed": "මෙම සැසිය ආයාත කළ නොහැකි විය.", + "pickProfile": "පැතිකඩක් තෝරන්න", + "noProfile": "පැතිකඩක් නැත", + "manageProfiles": "පැතිකඩ කළමනාකරණය කරන්න", + "noProfiles": "තවම පැතිකඩ නැත. පාරිසරික විචල්‍යයන්, MCP සේවාදායකයන් සහ කුසලතා එක් කිරීමට එකක් සාදන්න.", + "advancedConfig": "උසස් වින්යාසය", + "advancedConfigDescription": "සුරකින ලද වින්‍යාස කිරීම් පූරණය කිරීමට පැතිකඩක් තෝරන්න, සහ අතිරේක පරිසර විචල්‍යයන් සහ සැකසුම් විධානයන් අතින් වින්‍යාස කරන්න", + "saveAsProfile": "පැතිකඩ ලෙස සුරකින්න", + "saveProfileDescription": "වත්මන් පරිසර විචල්‍යයන් සහ සැකසුම් විධාන නැවත භාවිත කළ හැකි පැතිකඩක් ලෙස සුරකින්න.", + "profileSummary": "{{vars}} පරිසර විචල්‍යයන් · {{commands}} පිහිටුවීම් විධාන", + "profileSaved": "පැතිකඩ \"{{name}}\" සුරකින ලදී", + "organizationProfiles": "සංවිධානයේ පැතිකඩ", + "personalProfiles": "පුද්ගලික පැතිකඩ", + "yourProfiles": "ඔබේ පැතිකඩ", + "failedToLoadProfiles": "පැතිකඩ පූරණය කිරීමට අසමත් විය", + "nameTooLong": "පැතිකඩ නම දිග වැඩියි", + "descriptionTooLong": "විස්තරය දිග වැඩියි", + "secretsEncrypted": "ගබඩා කිරීමට පෙර රහස් සංකේතනය කර ඇත.", + "profileActive": "පැතිකඩ සක්රීයයි", + "profilesActive": "පැතිකඩ ක්‍රියාකාරී", + "customConfigActive": "අභිරුචි සැකසුම් සක්‍රීයයි", + "configNeedsAttention": "වින්‍යාසය කෙරෙහි අවධානය යොමු කළ යුතුය", + "repoProfile": "Repo පැතිකඩ", + "selectedProfile": "තෝරාගත් පැතිකඩ", + "manualOverrides": "අතින් අභිබවා යයි", + "openSettingsToReview": "සමාලෝචනය කිරීමට සැකසීම් විවෘත කරන්න", + "profileSelection": "පැතිකඩ තේරීම" }, "partRenderer": { "updatedFileCount_one": "ගොනු {{displayCount}} ක් යාවත්කාලීන කර ඇත.", @@ -3224,5 +3250,180 @@ "multiplierError": "1 හෝ ඊට වැඩි ගුණකයක් ඇතුළත් කරන්න", "permissionDenied": "වියදම් දැනුම්දීම් කළමනාකරණය කිරීමට ඔබට අවසර නැත.", "pushOffByCategory": "දැනුම්දීම් තුළ ක්‍රියා විරහිතයි" + }, + "profiles": { + "title": "පැතිකඩ", + "entrySubtitle": "සැසි හරහා පරිසර සැකසීම් නැවත භාවිතා කරන්න", + "newProfile": "නව පැතිකඩ", + "emptyTitle": "තවම පැතිකඩ නැත", + "emptyDescription": "සැසි හරහා පරිසර විචල්‍ය, සැකසුම් විධාන සහ කුසලතා නැවත භාවිතා කිරීමට පැතිකඩක් සාදන්න.", + "loadFailed": "පැතිකඩ පූරණය කළ නොහැකි විය", + "counts": { + "vars": "{{count}} විචල්ය", + "mcp": "{{count}} MCP", + "skills": "{{count}} කුසලතා", + "commands": "{{count}} විධාන", + "varsShort": "{{count}}වි", + "skillsShort": "{{count}}කු", + "commandsShort": "{{count}}විධ" + }, + "nameLabel": "පැතිකඩ නම", + "namePlaceholder": "උදා. පසුපෙළ නිදොස්කරණය", + "descriptionLabel": "පැතිකඩ විස්තරය", + "descriptionPlaceholder": "විකල්ප විස්තරය", + "ownerLabel": "පැතිකඩ හිමිකරු", + "nameRequired": "පැතිකඩ නමක් ඇතුළත් කරන්න", + "createAction": "පැතිකඩ සාදන්න", + "createFailed": "පැතිකඩ නිර්මාණය කිරීමට නොහැකි විය", + "createdToast": "පැතිකඩ \"{{name}}\" සාදන ලදී", + "settingsSectionTitle": "මානකරනය", + "defaultSectionTitle": "පෙරනිමි පැතිකඩ", + "personalDefaultDescription": "කිසිදු ගබඩාවක පින් කළ පැතිකඩක් නොමැති විට ස්වයංක්‍රීයව පටවනු ලැබේ.", + "organizationDefaultDescription": "පුද්ගලික පෙරනිමියක් නොමැති සාමාජිකයින් සඳහා ස්වයංක්‍රීයව පටවනු ලැබේ.", + "setAsDefault": "පෙරනිමිය ලෙස සකසන්න", + "removeDefault": "පෙරනිමියෙන් ඉවත් කරන්න", + "saveFailed": "පැතිකඩ සුරැකීමට නොහැකි විය", + "deleteTitle": "පැතිකඩ මකන්න", + "deleteMessage": "මෙය පැතිකඩ සහ එහි සැකසුම් ස්ථිරවම ඉවත් කරයි. මෙය ආපසු හැරවිය නොහැක.", + "deleteFailed": "පැතිකඩ මැකීමට නොහැකි විය", + "deleteBlocked": "මෙම පැතිකඩ webhook ප්‍රේරකයක් මගින් භාවිතා කරයි. එය මුලින්ම එම ප්‍රේරක වලින් ඉවත් කරන්න.", + "deletedToast": "පැතිකඩ \"{{name}}\" මකා ඇත", + "variablesTitle": "පාරිසරික විචල්යයන්", + "addVariable": "විචල්‍ය එකතු කරන්න", + "keyLabel": "යතුර", + "keyTooLong": "යතුර අක්ෂර 256ක් හෝ ඊට අඩු විය යුතුය", + "keyDuplicate": "මෙම යතුර දැනටමත් පැතිකඩෙහි පවතී", + "valueLabel": "වටිනාකම", + "secretLabel": "රහස", + "secrets": { + "markSecret": "රහසක් ලෙස සලකුණු කරන්න", + "masked": "සැඟවුණු අගය", + "reveal": "වටිනාකම හෙළි කරන්න", + "newValuePlaceholder": "නව රහස් අගයක් ඇතුළත් කරන්න", + "valuePlaceholder": "රහස් අගය (සංකේතනය කළ)" + }, + "variablesEmpty": "තවමත් විචල්‍ය නොමැත", + "variablesSaveFailed": "විචල්‍යය සුරැකීමට නොහැකි විය", + "commandsTitle": "සැකසුම් විධාන", + "addCommand": "විධානය එක් කරන්න", + "commandLabel": "විධානය", + "commandPlaceholder": "e.g. pnpm install", + "commandsEmpty": "තවමත් පිහිටුවීම් විධාන නොමැත", + "commandsSaveFailed": "පිහිටුවීම් විධාන සුරැකීමට නොහැකි විය", + "moveUp": "ඉහළට ගමන් කරන්න", + "moveDown": "පහළට ගමන් කරන්න", + "skillsTitle": "කුසලතා", + "addSkill": "කුසලතා එකතු කරන්න", + "skillNameLabel": "කුසලතා නාමය", + "skillMarkdownLabel": "කුසලතා අන්තර්ගතය", + "skillsEmpty": "තවමත් කුසලතා නොමැත", + "skillNameRequired": "කුසලතා නාමයක් ඇතුළත් කරන්න", + "skillNameInvalid": "කුසලතා නාමය අකුරකින් හෝ ඉලක්කමකින් ආරම්භ විය යුතු අතර කුඩා අකුරු, ඉලක්කම් සහ ඉරි පමණක් භාවිතා කළ යුතුය", + "skillContentRequired": "නිපුණතා අන්තර්ගතය අවශ්ය වේ", + "skillSaveFailed": "කුසලතා සුරැකීමට නොහැකි විය", + "skillsHint": "SKILL.md එකක් අලවන්න සහ එහි නම පුරවයි; ඇදගෙන යාමකින් තොරව ඔබගේ දුරකථනයෙන් කුසලතා එක් කරන්න.", + "skillEditTitle": "සංස්කරණ කුසලතා", + "mcp": { + "title": "MCP සේවාදායකයන්", + "add": "MCP සේවාදායකය එක් කරන්න", + "empty": "MCP සේවාදායකයන් තවමත් නොමැත", + "name": "සේවාදායකයේ නම", + "type": "සේවාදායක වර්ගය", + "localType": "දේශීය", + "remoteType": "දුරස්ථ", + "command": "විධාන රේඛාව", + "url": "URL", + "environment": "පරිසරය (JSON)", + "headers": "ශීර්ෂ (JSON)", + "timeout": "කාලය හමාරයි", + "secretCount": "රහස් ගණන", + "namePlaceholder": "my-server", + "commandPlaceholder": "npx @example/mcp-server", + "jsonPlaceholder": "{\n \"API_KEY\": \"sk-...\"\n}", + "nameRequired": "සේවාදායකයේ නමක් ඇතුළත් කරන්න", + "nameInvalid": "නමට භාවිත කළ හැක්කේ අකුරු, අංක, යටි ඉරි සහ ඉරි පමණි", + "commandRequired": "විධානයක් ඇතුල් කරන්න", + "commandTooLong": "උපරිම අක්ෂර 500 බැගින් තර්ක 50ක් භාවිත කරන්න", + "urlRequired": "URL එකක් ඇතුල් කරන්න", + "urlInvalid": "වලංගු URL එකක් ඇතුළු කරන්න", + "urlTooLong": "URL අක්ෂර 2048ක් හෝ ඊට අඩු විය යුතුය", + "jsonInvalid": "වලංගු JSON ඇතුලත් කරන්න — තන්තු අගයන් වස්තුවක්", + "jsonTooLarge": "උපරිම සටහන් 50ක් භාවිත කරන්න, යතුරු 128 දක්වා සහ අගයන් අක්ෂර 4096 දක්වා", + "timeoutInvalid": "කල් ඉකුත්වීම සම්පූර්ණ මිලි තත්පර ගණනක් විය යුතුය", + "edit": "MCP සේවාදායකය සංස්කරණය කරන්න", + "saveFailed": "MCP සේවාදායකය සුරැකීමට නොහැකි විය" + }, + "agents": { + "title": "පැතිකඩ නියෝජිතයන්", + "add": "නියෝජිතයා එකතු කරන්න", + "empty": "තවමත් නියෝජිතයන් නැත", + "name": "නියෝජිතයාගේ නම", + "slug": "නියෝජිත ස්ලග්", + "description": "නියෝජිත විස්තරය", + "model": "මාදිලිය අභිබවා යාම", + "modelPlaceholder": "සැසි පෙරනිමිය භාවිතා කරන්න", + "prompt": "පද්ධති විමසුම", + "steps": "උපරිම පියවර", + "stepsPlaceholder": "උදා. 50", + "temperature": "උෂ්ණත්වය", + "temperaturePlaceholder": "උදා. 0.2", + "topP": "top_p", + "topPPlaceholder": "උදා. 0.95", + "slugPlaceholder": "my-agent", + "namePlaceholder": "මගේ නියෝජිතයා", + "promptPlaceholder": "ඔබ කෙනෙක්...", + "tools": "මෙවලම්", + "visibility": "දෘශ්‍යතාව", + "visibilityPrimary": "ප්‍රාථමික - පිකර් තුළ පෙන්වා ඇත", + "visibilitySubagent": "Subagen - අන් අයගෙන් පමණක් ඇමතිය හැකිය", + "visibilityAll": "සියල්ල - දෙකම", + "saveFailed": "නියෝජිතයා සුරැකීමට නොහැකි විය", + "nameRequired": "නියෝජිත නාමයක් ඇතුළත් කරන්න", + "slugRequired": "නියෝජිත ස්ලග් එකක් ඇතුළු කරන්න", + "slugInvalid": "Slug අකුරකින් ආරම්භ විය යුතු අතර කුඩා අකුරු, ඉලක්කම් සහ ඉරි පමණක් භාවිතා කළ යුතුය", + "slugConflict": "එම හම්බෙල්ලන් වෙන් කර ඇත්තේ ගොඩනඟන ලද නියෝජිතයෙකු විසිනි", + "edit": "සංස්කරණ නියෝජිතයා" + }, + "slashCommands": { + "title": "Slash විධාන", + "add": "slash විධානය එක් කරන්න", + "empty": "තවම slash විධාන නොමැත", + "name": "විධාන නාමය", + "description": "විධාන විස්තරය", + "template": "සැකිල්ල", + "namePlaceholder": "explain", + "templatePlaceholder": "$ARGUMENTS පැහැදිලි කරන්න", + "agent": "නියෝජිතයා", + "subtask": "උප කාර්යයක් ලෙස ධාවනය කරන්න", + "edit": "slash විධානය සංස්කරණය කරන්න", + "saveFailed": "විධානය සුරැකීමට නොහැකි විය", + "nameRequired": "විධාන නාමයක් ඇතුළත් කරන්න", + "nameInvalid": "නම කුඩා අකුරකින් ආරම්භ විය යුතු අතර කුඩා අකුරු, ඉලක්කම් සහ ඉරි පමණක් භාවිතා කළ යුතුය", + "templateRequired": "විධාන ආකෘතියක් ඇතුළත් කරන්න", + "nameConflict": "එම නම බිල්ට් විධානයක් මගින් වෙන් කර ඇත" + }, + "repoBindings": { + "title": "Repos සඳහා පෙරනිමි පැතිකඩ", + "add": "පෙරනිමිය එක් කරන්න", + "empty": "පෙරනිමි කිසිවක් වින්‍යාස කර නැත", + "emptyDescription": "repo සඳහා සැසි ආරම්භ කරන විට පැතිකඩක් ස්වයංක්‍රීයව යෙදීමට පෙරනිමියක් එක් කරන්න.", + "repo": "ගබඩාව තෝරන්න...", + "profile": "පැතිකඩ තෝරන්න", + "remove": "නොබැඳීම", + "private": "පුද්ගලිකයි", + "search": "ගබඩාවන් සොයන්න...", + "addedToast": "\"{{repo}}\" සඳහා පෙරනිමි පැතිකඩ සකසා ඇත", + "addFailed": "පෙරනිමි පැතිකඩ සැකසීමට නොහැකි විය", + "removedToast": "\"{{repo}}\" සඳහා පෙරනිමි පැතිකඩ ඉවත් කර ඇත", + "removeFailed": "පෙරනිමි පැතිකඩ ඉවත් කිරීමට නොහැකි විය", + "pinnedTitle": "ගබඩාවලට අමුණා ඇත", + "pinRepo": "repo එකක් Pin කරන්න", + "pinnedEmpty": "කිසිදු ගබඩාවකට අමුණා නැත.", + "pinnedDescription": "සැසියක් මෙම ගබඩාවලින් එකක් ඉලක්ක කරන විට ස්වයංක්‍රීයව පටවනු ලැබේ.", + "pinnedToast": "\"{{repo}}\" වෙත අමුණන ලදී", + "pinFailed": "repo පින් කිරීමට නොහැකි විය", + "unboundToast": "Repo නොබැඳි", + "unbindFailed": "repo බැඳීම ඉවත් කළ නොහැකි විය" + } } } diff --git a/apps/mobile/src/i18n/locales/sk.json b/apps/mobile/src/i18n/locales/sk.json index c1e23c6728f..44441b252b4 100644 --- a/apps/mobile/src/i18n/locales/sk.json +++ b/apps/mobile/src/i18n/locales/sk.json @@ -2247,7 +2247,33 @@ "continueFrom": "Pokračuje sa v relácii {{title}}.", "importingSession": "Importuje sa relácia", "cliCannotContinue": "Táto verzia CLI nepodporuje pokračovanie v relácii. Vyber službu Cloud Agent alebo inú inštanciu CLI.", - "importFailed": "Túto reláciu sa nepodarilo importovať." + "importFailed": "Túto reláciu sa nepodarilo importovať.", + "pickProfile": "Vyberte profil", + "noProfile": "Žiadny profil", + "manageProfiles": "Spravovať profily", + "noProfiles": "Zatiaľ žiadne profily. Vytvorte jeden a pridajte premenné prostredia, servery MCP a zručnosti.", + "advancedConfig": "Pokročilá konfigurácia", + "advancedConfigDescription": "Vyberte profil na načítanie uložených konfigurácií a manuálne nakonfigurujte ďalšie premenné prostredia a príkazy nastavenia", + "saveAsProfile": "Uložiť ako profil", + "saveProfileDescription": "Uložte aktuálne premenné prostredia a príkazy nastavenia ako opätovne použiteľný profil.", + "profileSummary": "{{vars}} premenné prostredia · {{commands}} príkazy nastavenia", + "profileSaved": "Profil {{name}} bol uložený", + "organizationProfiles": "Profily organizácií", + "personalProfiles": "Osobné profily", + "yourProfiles": "Vaše profily", + "failedToLoadProfiles": "Nepodarilo sa načítať profily", + "nameTooLong": "Názov profilu je príliš dlhý", + "descriptionTooLong": "Popis je príliš dlhý", + "secretsEncrypted": "Tajomstvá sú pred uložením zašifrované.", + "profileActive": "Profil je aktívny", + "profilesActive": "Profily sú aktívne", + "customConfigActive": "Vlastná konfigurácia je aktívna", + "configNeedsAttention": "Konfigurácia vyžaduje pozornosť", + "repoProfile": "Repo profil", + "selectedProfile": "Vybraný profil", + "manualOverrides": "Manuálne prepisy", + "openSettingsToReview": "Ak chcete skontrolovať, otvorte Nastavenia", + "profileSelection": "Výber profilu" }, "partRenderer": { "updatedFileCount_one": "Aktualizoval sa {{displayCount}} súbor.", @@ -3310,5 +3336,180 @@ "multiplierError": "Zadaj násobiteľ 1 alebo väčší", "permissionDenied": "Nemáš povolenie spravovať upozornenia na výdavky.", "pushOffByCategory": "Vypnuté v Upozorneniach" + }, + "profiles": { + "title": "Profily", + "entrySubtitle": "Opätovne použite nastavenia prostredia v rámci relácií", + "newProfile": "Nový profil", + "emptyTitle": "Zatiaľ žiadne profily", + "emptyDescription": "Vytvorte profil na opätovné použitie premenných prostredia, príkazov nastavenia a zručností v rámci relácií.", + "loadFailed": "Profily sa nepodarilo načítať", + "counts": { + "vars": "{{count}} premenných", + "mcp": "{{count}} MCP", + "skills": "{{count}} zručností", + "commands": "{{count}} príkazov", + "varsShort": "{{count}}pre", + "skillsShort": "{{count}}zru", + "commandsShort": "{{count}}prí" + }, + "nameLabel": "Názov profilu", + "namePlaceholder": "napr. Ladenie backendu", + "descriptionLabel": "Popis profilu", + "descriptionPlaceholder": "Voliteľný popis", + "ownerLabel": "Vlastník profilu", + "nameRequired": "Zadajte názov profilu", + "createAction": "Vytvoriť profil", + "createFailed": "Profil sa nepodarilo vytvoriť", + "createdToast": "Profil „{{name}}“ bol vytvorený", + "settingsSectionTitle": "Konfigurácia", + "defaultSectionTitle": "Predvolený profil", + "personalDefaultDescription": "Automaticky načítané, keď žiadne úložisko nemá pripnutý profil.", + "organizationDefaultDescription": "Automaticky načítané pre členov, ktorí nemajú žiadne osobné predvolené nastavenia.", + "setAsDefault": "Nastaviť ako predvolené", + "removeDefault": "Odstrániť ako predvolené", + "saveFailed": "Profil sa nepodarilo uložiť", + "deleteTitle": "Odstrániť profil", + "deleteMessage": "Tým sa natrvalo odstráni profil a jeho nastavenia. Toto sa nedá vrátiť späť.", + "deleteFailed": "Profil sa nepodarilo odstrániť", + "deleteBlocked": "Tento profil používa spúšťač webhooku. Najprv ho odstráňte z týchto spúšťačov.", + "deletedToast": "Profil „{{name}}“ bol odstránený", + "variablesTitle": "Premenné prostredia", + "addVariable": "Pridať premennú", + "keyLabel": "kľúč", + "keyTooLong": "Kľúč môže mať maximálne 256 znakov", + "keyDuplicate": "Tento kľúč už v profile existuje", + "valueLabel": "Hodnota", + "secretLabel": "Tajomstvo", + "secrets": { + "markSecret": "Označiť ako tajné", + "masked": "Skrytá hodnota", + "reveal": "Odhaliť hodnotu", + "newValuePlaceholder": "Zadajte novú tajnú hodnotu", + "valuePlaceholder": "Tajná hodnota (šifrovaná)" + }, + "variablesEmpty": "Zatiaľ žiadne premenné", + "variablesSaveFailed": "Premennú sa nepodarilo uložiť", + "commandsTitle": "Príkazy nastavenia", + "addCommand": "Pridať príkaz", + "commandLabel": "Príkaz", + "commandPlaceholder": "e.g. pnpm install", + "commandsEmpty": "Zatiaľ žiadne príkazy na nastavenie", + "commandsSaveFailed": "Nepodarilo sa uložiť príkazy nastavenia", + "moveUp": "Posunúť sa hore", + "moveDown": "Presuňte sa nadol", + "skillsTitle": "Zručnosti", + "addSkill": "Pridajte zručnosť", + "skillNameLabel": "Názov zručnosti", + "skillMarkdownLabel": "Obsah zručností", + "skillsEmpty": "Zatiaľ žiadne zručnosti", + "skillNameRequired": "Zadajte názov zručnosti", + "skillNameInvalid": "Názov zručnosti musí začínať písmenom alebo číslicou a používať iba malé písmená, číslice a pomlčky", + "skillContentRequired": "Vyžaduje sa obsah zručností", + "skillSaveFailed": "Nepodarilo sa zachrániť zručnosť", + "skillsHint": "Vložte súbor SKILL.md a jeho názov sa vyplní; pridajte zručnosť zo svojho telefónu bez presúvania myšou.", + "skillEditTitle": "Upraviť zručnosť", + "mcp": { + "title": "servery MCP", + "add": "Pridajte server MCP", + "empty": "Zatiaľ žiadne servery MCP", + "name": "Názov servera", + "type": "Typ servera", + "localType": "Miestne", + "remoteType": "Diaľkové ovládanie", + "command": "Príkazový riadok", + "url": "URL", + "environment": "Prostredie (JSON)", + "headers": "Hlavičky (JSON)", + "timeout": "Časový limit", + "secretCount": "Tajný počet", + "namePlaceholder": "my-server", + "commandPlaceholder": "npx @example/mcp-server", + "jsonPlaceholder": "{\n \"API_KEY\": \"sk-...\"\n}", + "nameRequired": "Zadajte názov servera", + "nameInvalid": "Názov môže používať iba písmená, čísla, podčiarkovníky a pomlčky", + "commandRequired": "Zadajte príkaz", + "commandTooLong": "Použite najviac 50 argumentov s 500 znakmi", + "urlRequired": "Zadajte adresu URL", + "urlInvalid": "Zadajte platnú webovú adresu", + "urlTooLong": "Adresa URL môže mať najviac 2048 znakov", + "jsonInvalid": "Zadajte platný JSON — objekt reťazcových hodnôt", + "jsonTooLarge": "Použite najviac 50 položiek, kľúče do 128 a hodnoty do 4096 znakov", + "timeoutInvalid": "Časový limit musí byť celý počet milisekúnd", + "edit": "Upravte server MCP", + "saveFailed": "Server MCP sa nepodarilo uložiť" + }, + "agents": { + "title": "Profiloví agenti", + "add": "Pridať agenta", + "empty": "Zatiaľ žiadni agenti", + "name": "Meno agenta", + "slug": "Agent slimák", + "description": "Popis agenta", + "model": "Prepísanie modelu", + "modelPlaceholder": "Použiť predvolené nastavenie relácie", + "prompt": "Systémová výzva", + "steps": "Max. krokov", + "stepsPlaceholder": "napr. 50", + "temperature": "Teplota", + "temperaturePlaceholder": "napr. 0.2", + "topP": "top_p", + "topPPlaceholder": "napr. 0.95", + "slugPlaceholder": "my-agent", + "namePlaceholder": "Môj agent", + "promptPlaceholder": "Ty si...", + "tools": "Nástroje", + "visibility": "Viditeľnosť", + "visibilityPrimary": "Primárne – zobrazí sa vo výbere", + "visibilitySubagent": "Subagent — volateľný iba od ostatných", + "visibilityAll": "Všetky — oboje", + "saveFailed": "Agenta sa nepodarilo uložiť", + "nameRequired": "Zadajte meno agenta", + "slugRequired": "Zadajte agenta slimáka", + "slugInvalid": "Slug musí začínať písmenom a používať iba malé písmená, číslice a pomlčky", + "slugConflict": "Ten slimák je rezervovaný vstavaným agentom", + "edit": "Upraviť zástupcu" + }, + "slashCommands": { + "title": "Príkazy lomka", + "add": "Pridať príkaz lomka", + "empty": "Zatiaľ žiadne príkazy lomítka", + "name": "Názov príkazu", + "description": "Popis príkazu", + "template": "Šablóna", + "namePlaceholder": "explain", + "templatePlaceholder": "Vysvetlite $ARGUMENTS", + "agent": "agent", + "subtask": "Spustiť ako podúlohu", + "edit": "Príkaz na úpravu lomky", + "saveFailed": "Príkaz sa nepodarilo uložiť", + "nameRequired": "Zadajte názov príkazu", + "nameInvalid": "Názov musí začínať malým písmenom a používať iba malé písmená, číslice a pomlčky", + "templateRequired": "Zadajte šablónu príkazu", + "nameConflict": "Tento názov je rezervovaný vstavaným príkazom" + }, + "repoBindings": { + "title": "Predvolené profily pre úložiská", + "add": "Pridať predvolené", + "empty": "Nie sú nakonfigurované žiadne predvolené hodnoty", + "emptyDescription": "Pridajte predvolené nastavenie na automatické použitie profilu pri spustení relácií pre repo.", + "repo": "Vybrať úložisko…", + "profile": "Vyberte profil", + "remove": "Odviazať", + "private": "Súkromné", + "search": "Hľadať v úložiskách…", + "addedToast": "Predvolený profil nastavený pre „{{repo}}“", + "addFailed": "Nepodarilo sa nastaviť predvolený profil", + "removedToast": "Predvolený profil bol odstránený pre „{{repo}}“", + "removeFailed": "Predvolený profil sa nepodarilo odstrániť", + "pinnedTitle": "Pripnuté k úložiskám", + "pinRepo": "Pripnúť repo", + "pinnedEmpty": "Nie je pripnutý na žiadne úložiská.", + "pinnedDescription": "Automaticky sa načíta vždy, keď relácia zacieli na jeden z týchto úložísk.", + "pinnedToast": "Pripnuté na „{{repo}}“", + "pinFailed": "Úložisko sa nepodarilo pripnúť", + "unboundToast": "Repo neviazané", + "unbindFailed": "Nepodarilo sa odpojiť repo" + } } } diff --git a/apps/mobile/src/i18n/locales/sl.json b/apps/mobile/src/i18n/locales/sl.json index 0b15e3a0b8b..974783288dd 100644 --- a/apps/mobile/src/i18n/locales/sl.json +++ b/apps/mobile/src/i18n/locales/sl.json @@ -2247,7 +2247,33 @@ "continueFrom": "Nadaljuje se seja {{title}}.", "importingSession": "Uvažanje seje", "cliCannotContinue": "Ta CLI ne podpira nadaljevanja seje. Izberi Cloud Agent ali drug CLI.", - "importFailed": "Te seje ni bilo mogoče uvoziti." + "importFailed": "Te seje ni bilo mogoče uvoziti.", + "pickProfile": "Izberite profil", + "noProfile": "Brez profila", + "manageProfiles": "Upravljanje profilov", + "noProfiles": "Še ni profilov. Ustvarite ga, če želite dodati spremenljivke okolja, strežnike MCP in veščine.", + "advancedConfig": "Napredna konfiguracija", + "advancedConfigDescription": "Izberite profil za nalaganje shranjenih konfiguracij in ročno konfigurirajte dodatne spremenljivke okolja in nastavitvene ukaze", + "saveAsProfile": "Shrani kot profil", + "saveProfileDescription": "Shranite trenutne spremenljivke okolja in nastavitvene ukaze kot profil za večkratno uporabo.", + "profileSummary": "{{vars}} spremenljivke okolja · {{commands}} nastavitveni ukazi", + "profileSaved": "Profil \"{{name}}\" je shranjen", + "organizationProfiles": "Profili organizacije", + "personalProfiles": "Osebni profili", + "yourProfiles": "Vaši profili", + "failedToLoadProfiles": "Nalaganje profilov ni uspelo", + "nameTooLong": "Ime profila je predolgo", + "descriptionTooLong": "Opis je predolg", + "secretsEncrypted": "Skrivnosti so pred shranjevanjem šifrirane.", + "profileActive": "Profil aktiven", + "profilesActive": "Profili aktivni", + "customConfigActive": "Aktivna konfiguracija po meri", + "configNeedsAttention": "Konfiguracija potrebuje pozornost", + "repoProfile": "Repo profil", + "selectedProfile": "Izbrani profil", + "manualOverrides": "Ročne preglasitve", + "openSettingsToReview": "Odprite nastavitve za pregled", + "profileSelection": "Izbira profila" }, "partRenderer": { "updatedFileCount_one": "Posodobljena je {{displayCount}} datoteka.", @@ -3310,5 +3336,180 @@ "multiplierError": "Vnesi množitelj 1 ali večji", "permissionDenied": "Nimaš dovoljenja za upravljanje opozoril o porabi.", "pushOffByCategory": "Izklopljeno v Obvestilih" + }, + "profiles": { + "title": "Profili", + "entrySubtitle": "Ponovno uporabite nastavitve okolja med sejami", + "newProfile": "Nov profil", + "emptyTitle": "Še ni profilov", + "emptyDescription": "Ustvarite profil za ponovno uporabo spremenljivk okolja, nastavitvenih ukazov in veščin med sejami.", + "loadFailed": "Profilov ni bilo mogoče naložiti", + "counts": { + "vars": "{{count}} spremenljivk", + "mcp": "{{count}} MCP", + "skills": "{{count}} spretnosti", + "commands": "{{count}} ukazov", + "varsShort": "{{count}}s", + "skillsShort": "{{count}}sp", + "commandsShort": "{{count}}u" + }, + "nameLabel": "Ime profila", + "namePlaceholder": "npr. Odpravljanje napak v ozadju", + "descriptionLabel": "Opis profila", + "descriptionPlaceholder": "Izbirni opis", + "ownerLabel": "Lastnik profila", + "nameRequired": "Vnesite ime profila", + "createAction": "Ustvari profil", + "createFailed": "Profila ni bilo mogoče ustvariti", + "createdToast": "Profil \"{{name}}\" ustvarjen", + "settingsSectionTitle": "Konfiguracija", + "defaultSectionTitle": "Privzeti profil", + "personalDefaultDescription": "Samodejno naloženo, ko nobeno skladišče nima pripetega profila.", + "organizationDefaultDescription": "Samodejno naloženo za člane, ki nimajo osebne privzete vrednosti.", + "setAsDefault": "Nastavi kot privzeto", + "removeDefault": "Odstrani kot privzeto", + "saveFailed": "Profila ni bilo mogoče shraniti", + "deleteTitle": "Izbriši profil", + "deleteMessage": "S tem trajno odstranite profil in njegove nastavitve. Tega ni mogoče razveljaviti.", + "deleteFailed": "Profila ni bilo mogoče izbrisati", + "deleteBlocked": "Ta profil uporablja sprožilec webhook. Najprej ga odstranite s teh sprožilcev.", + "deletedToast": "Profil \"{{name}}\" izbrisan", + "variablesTitle": "Spremenljivke okolja", + "addVariable": "Dodajte spremenljivko", + "keyLabel": "Ključ", + "keyTooLong": "Ključ ima lahko največ 256 znakov", + "keyDuplicate": "Ta ključ že obstaja v profilu", + "valueLabel": "Vrednost", + "secretLabel": "Skrivnost", + "secrets": { + "markSecret": "Označi kot skrivnost", + "masked": "Skrita vrednost", + "reveal": "Razkrij vrednost", + "newValuePlaceholder": "Vnesite novo skrivno vrednost", + "valuePlaceholder": "Skrivna vrednost (šifrirana)" + }, + "variablesEmpty": "Ni še spremenljivk", + "variablesSaveFailed": "Spremenljivke ni bilo mogoče shraniti", + "commandsTitle": "Ukazi za nastavitev", + "addCommand": "Dodaj ukaz", + "commandLabel": "Ukaz", + "commandPlaceholder": "e.g. pnpm install", + "commandsEmpty": "Ni še nobenih ukazov za nastavitev", + "commandsSaveFailed": "Nastavitvenih ukazov ni bilo mogoče shraniti", + "moveUp": "Premakni se navzgor", + "moveDown": "Premakni se navzdol", + "skillsTitle": "Spretnosti", + "addSkill": "Dodajte spretnost", + "skillNameLabel": "Ime spretnosti", + "skillMarkdownLabel": "Vsebina spretnosti", + "skillsEmpty": "Ni še veščin", + "skillNameRequired": "Vnesite ime spretnosti", + "skillNameInvalid": "Ime spretnosti se mora začeti s črko ali številko in uporabljati samo male črke, številke in pomišljaje", + "skillContentRequired": "Potrebna je vsebina spretnosti", + "skillSaveFailed": "Spretnosti ni bilo mogoče shraniti", + "skillsHint": "Prilepite SKILL.md in njegovo ime se izpolni; dodajte veščino iz telefona brez povleci in spusti.", + "skillEditTitle": "Spretnost urejanja", + "mcp": { + "title": "MCP strežniki", + "add": "Dodajte strežnik MCP", + "empty": "Strežnikov MCP še ni", + "name": "Ime strežnika", + "type": "Vrsta strežnika", + "localType": "Lokalno", + "remoteType": "Daljinsko", + "command": "Ukazna vrstica", + "url": "URL", + "environment": "Okolje (JSON)", + "headers": "Glave (JSON)", + "timeout": "Časovna omejitev", + "secretCount": "Skrivno štetje", + "namePlaceholder": "my-server", + "commandPlaceholder": "npx @example/mcp-server", + "jsonPlaceholder": "{\n \"API_KEY\": \"sk-...\"\n}", + "nameRequired": "Vnesite ime strežnika", + "nameInvalid": "Ime lahko vsebuje samo črke, številke, podčrtaje in pomišljaje", + "commandRequired": "Vnesite ukaz", + "commandTooLong": "Uporabite največ 50 argumentov po 500 znakov", + "urlRequired": "Vnesite URL", + "urlInvalid": "Vnesite veljaven URL", + "urlTooLong": "URL ima lahko največ 2048 znakov", + "jsonInvalid": "Vnesite veljaven JSON — objekt vrednosti niza", + "jsonTooLarge": "Uporabite največ 50 vnosov, ključi do 128 in vrednosti do 4096 znakov", + "timeoutInvalid": "Časovna omejitev mora biti celo število milisekund", + "edit": "Uredi strežnik MCP", + "saveFailed": "Strežnika MCP ni bilo mogoče shraniti" + }, + "agents": { + "title": "Profilni agenti", + "add": "Dodaj agenta", + "empty": "Zastopnikov še ni", + "name": "Ime agenta", + "slug": "Agent polž", + "description": "Opis agenta", + "model": "Preglasitev modela", + "modelPlaceholder": "Uporabi privzeto sejo", + "prompt": "Sistemski poziv", + "steps": "Najv. korakov", + "stepsPlaceholder": "npr. 50", + "temperature": "Temperatura", + "temperaturePlaceholder": "npr. 0.2", + "topP": "top_p", + "topPPlaceholder": "npr. 0.95", + "slugPlaceholder": "my-agent", + "namePlaceholder": "Moj agent", + "promptPlaceholder": "ti si ...", + "tools": "Orodja", + "visibility": "Vidnost", + "visibilityPrimary": "Primarno — prikazano v izbirniku", + "visibilitySubagent": "Subagent — na klic samo od drugih", + "visibilityAll": "Vse — oboje", + "saveFailed": "Agenta ni bilo mogoče shraniti", + "nameRequired": "Vnesite ime agenta", + "slugRequired": "Vnesite polža agenta", + "slugInvalid": "Polž se mora začeti s črko in uporabljati samo male črke, številke in pomišljaje", + "slugConflict": "Ta polž je rezerviran z vgrajenim agentom", + "edit": "Uredi agenta" + }, + "slashCommands": { + "title": "Ukazi za poševnico", + "add": "Dodaj ukaz za poševnico", + "empty": "Ni še ukazov za poševnico", + "name": "Ime ukaza", + "description": "Opis ukaza", + "template": "Predloga", + "namePlaceholder": "explain", + "templatePlaceholder": "Pojasnite $ARGUMENTS", + "agent": "Agent", + "subtask": "Zaženi kot podopravilo", + "edit": "Uredi ukaz za poševnico", + "saveFailed": "Ukaza ni bilo mogoče shraniti", + "nameRequired": "Vnesite ime ukaza", + "nameInvalid": "Ime se mora začeti z malo začetnico in uporabljati samo male črke, številke in pomišljaje", + "templateRequired": "Vnesite predlogo ukaza", + "nameConflict": "To ime je rezervirano z vgrajenim ukazom" + }, + "repoBindings": { + "title": "Privzeti profili za skladišča", + "add": "Dodaj privzeto", + "empty": "Privzete nastavitve niso konfigurirane", + "emptyDescription": "Dodajte privzeto za samodejno uporabo profila ob zagonu sej za repo.", + "repo": "Izberite repozitorij ...", + "profile": "Izberite profil", + "remove": "Odveži", + "private": "Zasebno", + "search": "Iskanje po skladiščih ...", + "addedToast": "Privzeti profil nastavljen za \"{{repo}}\"", + "addFailed": "Privzetega profila ni bilo mogoče nastaviti", + "removedToast": "Privzeti profil odstranjen za \"{{repo}}\"", + "removeFailed": "Privzetega profila ni bilo mogoče odstraniti", + "pinnedTitle": "Pripeto v repozitorije", + "pinRepo": "Pripni repo", + "pinnedEmpty": "Ni pripet v nobeno skladišče.", + "pinnedDescription": "Samodejno se naloži vsakič, ko seja cilja na eno od teh skladišč.", + "pinnedToast": "Pripeto na »{{repo}}«", + "pinFailed": "Repo ni bilo mogoče pripeti", + "unboundToast": "Repo nevezan", + "unbindFailed": "Ni bilo mogoče razvezati repoja" + } } } diff --git a/apps/mobile/src/i18n/locales/so.json b/apps/mobile/src/i18n/locales/so.json index e108e5d4c3e..31c9cb87ca7 100644 --- a/apps/mobile/src/i18n/locales/so.json +++ b/apps/mobile/src/i18n/locales/so.json @@ -2191,7 +2191,33 @@ "continueFrom": "Waxaa la sii wadaa {{title}}.", "importingSession": "Kalfadhiga waa la soo gelinayaa", "cliCannotContinue": "CLI-kan ma sii wadi karo kalfadhi. Dooro Cloud Agent ama CLI kale.", - "importFailed": "Kalfadhigan lama soo gelin karin." + "importFailed": "Kalfadhigan lama soo gelin karin.", + "pickProfile": "Dooro profile", + "noProfile": "Profile malaha", + "manageProfiles": "Maamul profiles", + "noProfiles": "Weli profiles ma jiraan Abuur mid lagu daro doorsoomayaasha deegaanka, adeegayaasha MCP, iyo xirfadaha.", + "advancedConfig": "Habaynta Sare", + "advancedConfigDescription": "Dooro astaanta si aad ugu shubto qaabaynta kaydsan, oo gacanta ku habayso doorsoomayaal deegaan oo dheeri ah iyo amarrada dejinta", + "saveAsProfile": "U keydi sidii Profile ahaan", + "saveProfileDescription": "Keydi doorsoomayaasha deegaanka ee hadda jira iyo amarrada dejinta sidii muuqaal dib loo isticmaali karo.", + "profileSummary": "{{vars}} doorsoomayaasha deegaanka · {{commands}} amarrada dejinta", + "profileSaved": "Xogta \"{{name}}\" waa la keydiyay", + "organizationProfiles": "Profiles Ururka", + "personalProfiles": "Profiles Shakhsiyeed", + "yourProfiles": "Profileskaaga", + "failedToLoadProfiles": "Ku guuldarraystay in la soo geliyo profiles", + "nameTooLong": "Magaca astaanta ayaa aad u dheer", + "descriptionTooLong": "Sharaxaad aad ayey u dheertahay", + "secretsEncrypted": "Siraha waa la xafidaa kahor kaydinta.", + "profileActive": "Profile firfircoon", + "profilesActive": "Profiles firfircoon", + "customConfigActive": "Habayn gaar ah oo firfircoon", + "configNeedsAttention": "Config wuxuu u baahan yahay fiiro gaar ah", + "repoProfile": "Profile-ka repo", + "selectedProfile": "profile la doortay", + "manualOverrides": "Buug-gudbiyeedka", + "openSettingsToReview": "Fur Settings si aad dib ugu eegto", + "profileSelection": "Doorashada astaanta" }, "partRenderer": { "updatedFileCount_one": "{{displayCount}} fayl ayaa la cusboonaysiiyay.", @@ -3224,5 +3250,180 @@ "multiplierError": "Geli isku-dhufasho 1 ama ka badan", "permissionDenied": "Ma lihid oggolaansho aad ku maamusho digniinaha kharashka.", "pushOffByCategory": "Waa damman Ogeysiisyada" + }, + "profiles": { + "title": "Profile-yada", + "entrySubtitle": "Dib u isticmaal jaangooyooyinka deegaanka ee fadhiyada oo dhan", + "newProfile": "profile cusub", + "emptyTitle": "Weli profiles ma jiraan", + "emptyDescription": "Abuur profile si dib loogu isticmaalo doorsoomayaasha deegaanka, amarrada dejinta, iyo xirfadaha kalfadhiyada oo dhan.", + "loadFailed": "Waa la rari waayay muuqaallada", + "counts": { + "vars": "{{count}} doorsoomayaal", + "mcp": "{{count}} MCP", + "skills": "{{count}} xirfado", + "commands": "{{count}} amar", + "varsShort": "{{count}}d", + "skillsShort": "{{count}}x", + "commandsShort": "{{count}}a" + }, + "nameLabel": "Magaca astaanta", + "namePlaceholder": "tusaale. Dejinta dhabarka", + "descriptionLabel": "Sharaxaada astaanta", + "descriptionPlaceholder": "Sharaxaad ikhtiyaari ah", + "ownerLabel": "Milkiilaha astaanta", + "nameRequired": "Geli magac profile", + "createAction": "Abuur profile", + "createFailed": "Ma abuuri karo astaanta guud", + "createdToast": "Profile \"{{name}}\" waa la sameeyay", + "settingsSectionTitle": "Habaynta", + "defaultSectionTitle": "Muuqaalka asalka ah", + "personalDefaultDescription": "Si otomaatig ah ayaa loo raray marka aysan jirin meel kayd ah oo leh astaan ku dheggan", + "organizationDefaultDescription": "Si otomaatig ah ayaa loogu shubay xubnaha aan lahayn khaladka gaarka ah.", + "setAsDefault": "U deji sidii hore", + "removeDefault": "Ka saar sidii hore", + "saveFailed": "Waa la kaydin kari waayay astaanta guud", + "deleteTitle": "Tirtir astaanta guud", + "deleteMessage": "Tani waxay si joogto ah meesha uga saaraysaa astaanta guud iyo dejinta Tan lama celin karo.", + "deleteFailed": "Waa la tirtiri waayay astaanta guud", + "deleteBlocked": "Muuqaalkan waxa isticmaala kiciya webhook Marka hore ka saar kuwa kiciya.", + "deletedToast": "Xogta \"{{name}}\" waa la tirtiray", + "variablesTitle": "Doorsoomayaasha deegaanka", + "addVariable": "Ku dar doorsoome", + "keyLabel": "Furaha", + "keyTooLong": "Furaha waa inuu ahaadaa 256 xaraf ama ka yar", + "keyDuplicate": "Furahan hore ayuu ugu jiraa borofiilka", + "valueLabel": "Qiimaha", + "secretLabel": "Sirta", + "secrets": { + "markSecret": "Ku calaamadee sir", + "masked": "Qiimaha qarsoon", + "reveal": "Muujinta qiimaha", + "newValuePlaceholder": "Geli qiime sir ah oo cusub", + "valuePlaceholder": "Qiime sir ah (la sireeyay)" + }, + "variablesEmpty": "Wali ma jiro doorsoomayaal", + "variablesSaveFailed": "Ma keydin karin doorsoomayaasha", + "commandsTitle": "Dejinta amarrada", + "addCommand": "Ku dar amar", + "commandLabel": "Taliska", + "commandPlaceholder": "e.g. pnpm install", + "commandsEmpty": "Ma jiro amar dejin weli", + "commandsSaveFailed": "Ma kaydin kari waayay amarrada dejinta", + "moveUp": "Kor u dhaqaaq", + "moveDown": "Hoos u dhaqaaq", + "skillsTitle": "Xirfadaha", + "addSkill": "Ku dar xirfad", + "skillNameLabel": "Magaca xirfada", + "skillMarkdownLabel": "Waxa ku jira xirfad", + "skillsEmpty": "Weli xirfado ma laha", + "skillNameRequired": "Geli magac xirfad", + "skillNameInvalid": "Magaca xirfaddu waa inuu ku bilaabmaa xaraf ama lambar oo waa inuu isticmaalo xarfo yaryar, lambaro, iyo jajab", + "skillContentRequired": "Waxa loo baahan yahay nuxurka xirfadaha", + "skillSaveFailed": "Ma badbaadin karin xirfad", + "skillsHint": "Ku dheji SKILL.md oo magaciisa ayaa buuxiya; ku dar xirfad taleefankaaga adigoon jiidin oo tuurin.", + "skillEditTitle": "Xirfadda wax ka beddel", + "mcp": { + "title": "Adeegayaasha MCP", + "add": "Kudar server-ka MCP", + "empty": "Weli ma jiro adeegayaal MCP ah", + "name": "Magaca adeegaha", + "type": "Nooca adeegaha", + "localType": "Deegaanka", + "remoteType": "Fogfog", + "command": "Khadka taliska", + "url": "URL", + "environment": "Deegaanka (JSON)", + "headers": "Madaxda (JSON)", + "timeout": "Waqtigu", + "secretCount": "Tirada qarsoon", + "namePlaceholder": "my-server", + "commandPlaceholder": "npx @example/mcp-server", + "jsonPlaceholder": "{\n \"API_KEY\": \"sk-...\"\n}", + "nameRequired": "Geli magaca adeegaha", + "nameInvalid": "Magaca waxa uu isticmaali karaa xarfo, lambaro, hoosta, iyo xariijimaha", + "commandRequired": "Gali amar", + "commandTooLong": "Isticmaal ugu badnaan 50 doodood oo mid kastaa 500 xaraf", + "urlRequired": "Gali URL", + "urlInvalid": "Geli URL sax ah", + "urlTooLong": "URL waa inuu noqdaa 2048 xaraf ama ka yar", + "jsonInvalid": "Geli JSON sax ah - waa shay qiimayaal xadhig ah", + "jsonTooLarge": "Isticmaal ugu badnaan 50 gelin, furaha ilaa 128 iyo qiimaha ilaa 4096 xaraf", + "timeoutInvalid": "Wakhtiga kama dambaysta ahi waa inuu noqdaa tiro dhan oo millise seconds", + "edit": "Wax ka beddel serverka MCP", + "saveFailed": "Waa la kaydin kari waayay seerfarka MCP" + }, + "agents": { + "title": "Wakiilada xogta", + "add": "Ku dar wakiilka", + "empty": "Wali ma jiraan wakiilo", + "name": "Magaca wakiilka", + "slug": "Slug wakiilka", + "description": "Sharaxaada wakiilka", + "model": "Model-ka-noqoshada", + "modelPlaceholder": "Isticmaal kalfadhiga caadiga ah", + "prompt": "Degdegta nidaamka", + "steps": "Tallaabooyinka ugu badan", + "stepsPlaceholder": "tus. 50", + "temperature": "Heerkulka", + "temperaturePlaceholder": "tus. 0.2", + "topP": "top_p", + "topPPlaceholder": "tus. 0.95", + "slugPlaceholder": "my-agent", + "namePlaceholder": "Wakiilkayga", + "promptPlaceholder": "Waxaad tahay...", + "tools": "Qalabka", + "visibility": "Muuqashada", + "visibilityPrimary": "Aasaasiga ah - oo lagu muujiyay dooriyaha", + "visibilitySubagent": "Subagent - kaliya kuwa kale looga yeeri karo", + "visibilityAll": "Dhammaan - labadaba", + "saveFailed": "Ma badbaadin karin wakiilka", + "nameRequired": "Geli magaca wakiilka", + "slugRequired": "Geli slug wakiilka", + "slugInvalid": "Slug waa in uu ku bilaabmaa xaraf oo waa in uu isticmaalo xarfo yaryar, lambar, iyo dashes", + "slugConflict": "Dabaqyadaas waxaa xafiday wakiil ku dhex jira", + "edit": "Wakiilka wax ka beddel" + }, + "slashCommands": { + "title": "Amarada jarista", + "add": "Ku dar amarka slash", + "empty": "Weli wax amarro ah ma jiraan", + "name": "Magaca amarka", + "description": "Tilmaanta amarka", + "template": "Tusaale", + "namePlaceholder": "explain", + "templatePlaceholder": "Sharax $ARGUMENTS", + "agent": "Wakiil", + "subtask": "U orda sidii shaqo-hoosaad", + "edit": "Tafatir amarka slash", + "saveFailed": "Way kaydsan kari wayday amarka", + "nameRequired": "Geli magaca amarka", + "nameInvalid": "Magacu waa inuu ku bilowdaa xaraf yar oo uu isticmaalo xarfo yaryar, lambaro, iyo jajab", + "templateRequired": "Geli qaabka amarka", + "nameConflict": "Magacaas waxaa xafiday talis gudaha ku jira" + }, + "repoBindings": { + "title": "Profile-ka ugu tala galay Repos", + "add": "Kudar default", + "empty": "Ma jiro wax khalad ah oo la habeeyey", + "emptyDescription": "Ku dar default si toos ah u codso profile marka la bilaabayo fadhiyada repo.", + "repo": "Dooro kaydka…", + "profile": "Dooro astaanta", + "remove": "Ka fur", + "private": "Gaar ah", + "search": "Raadi meelaha kaydka ah…", + "addedToast": "Astaanta asalka ah waxaa loo dejiyay \"{{repo}}\"", + "addFailed": "Waa la dejin kari waayay astaanta guud", + "removedToast": "Astaanta asalka u ah waa laga saaray \"{{repo}}\"", + "removeFailed": "Waa la saari kari waayay astaanta guud", + "pinnedTitle": "Ku dheggan meelaha kaydka ah", + "pinRepo": "Ku dheji repo", + "pinnedEmpty": "Laguma dhejin wax kayd ah", + "pinnedDescription": "Si otomaatig ah loogu shubo mar kasta oo fadhi la beegsado mid ka mid ah meelahan", + "pinnedToast": "Ku dheggan \"{{repo}}\"", + "pinFailed": "Ma soo saari karin repo", + "unboundToast": "Dib u fur fur", + "unbindFailed": "Waa la furi waayay repo" + } } } diff --git a/apps/mobile/src/i18n/locales/sq.json b/apps/mobile/src/i18n/locales/sq.json index dbb975bbddd..8043a80eab5 100644 --- a/apps/mobile/src/i18n/locales/sq.json +++ b/apps/mobile/src/i18n/locales/sq.json @@ -2191,7 +2191,33 @@ "continueFrom": "Po vazhdon seanca {{title}}.", "importingSession": "Duke importuar seancën", "cliCannotContinue": "Ky CLI nuk mund të vazhdojë një seancë. Zgjidh Cloud Agent ose një CLI tjetër.", - "importFailed": "Nuk u arrit të importohej kjo seancë." + "importFailed": "Nuk u arrit të importohej kjo seancë.", + "pickProfile": "Zgjidh një profil", + "noProfile": "Nuk ka profil", + "manageProfiles": "Menaxho profilet", + "noProfiles": "Nuk ka ende profile. Krijo një për të shtuar variabla mjedisi, serverë MCP dhe aftësi.", + "advancedConfig": "Konfigurimi i avancuar", + "advancedConfigDescription": "Zgjidhni një profil për të ngarkuar konfigurimet e ruajtura dhe konfiguroni manualisht variablat shtesë të mjedisit dhe komandat e konfigurimit", + "saveAsProfile": "Ruaje si profil", + "saveProfileDescription": "Ruani variablat aktuale të mjedisit dhe komandat e konfigurimit si një profil të ripërdorshëm.", + "profileSummary": "{{vars}} variablat e mjedisit · {{commands}} komandat e konfigurimit", + "profileSaved": "Profili \"{{name}}\" u ruajt", + "organizationProfiles": "Profilet e Organizatës", + "personalProfiles": "Profilet Personale", + "yourProfiles": "Profilet tuaja", + "failedToLoadProfiles": "Ngarkimi i profileve dështoi", + "nameTooLong": "Emri i profilit është shumë i gjatë", + "descriptionTooLong": "Përshkrimi është shumë i gjatë", + "secretsEncrypted": "Sekretet janë të koduara para ruajtjes.", + "profileActive": "Profili aktiv", + "profilesActive": "Profilet aktive", + "customConfigActive": "Konfigurimi i personalizuar aktiv", + "configNeedsAttention": "Konfigurimi kërkon vëmendje", + "repoProfile": "Profili i repos", + "selectedProfile": "Profili i zgjedhur", + "manualOverrides": "Anulimet manuale", + "openSettingsToReview": "Hapni Cilësimet për t'i rishikuar", + "profileSelection": "Zgjedhja e profilit" }, "partRenderer": { "updatedFileCount_one": "U përditësua {{displayCount}} skedar.", @@ -3224,5 +3250,180 @@ "multiplierError": "Vendos një shumëzues 1 ose më shumë", "permissionDenied": "Nuk ke leje për të menaxhuar njoftimet për shpenzimet.", "pushOffByCategory": "Fikur në Njoftimet" + }, + "profiles": { + "title": "Profilet", + "entrySubtitle": "Ripërdorni cilësimet e mjedisit nëpër sesione", + "newProfile": "Profili i ri", + "emptyTitle": "Nuk ka ende profile", + "emptyDescription": "Krijo një profil për të ripërdorur variablat e mjedisit, komandat e konfigurimit dhe aftësitë nëpër sesione.", + "loadFailed": "Profilet nuk mund të ngarkoheshin", + "counts": { + "vars": "{{count}} variabla", + "mcp": "{{count}} MCP", + "skills": "{{count}} aftësi", + "commands": "{{count}} komanda", + "varsShort": "{{count}}v", + "skillsShort": "{{count}}a", + "commandsShort": "{{count}}k" + }, + "nameLabel": "Emri i profilit", + "namePlaceholder": "p.sh. Korrigjimi i backend-it", + "descriptionLabel": "Përshkrimi i profilit", + "descriptionPlaceholder": "Përshkrimi opsional", + "ownerLabel": "Pronari i profilit", + "nameRequired": "Futni një emër profili", + "createAction": "Krijo një profil", + "createFailed": "Profili nuk mund të krijohej", + "createdToast": "Profili \"{{name}}\" u krijua", + "settingsSectionTitle": "Konfigurimi", + "defaultSectionTitle": "Profili i parazgjedhur", + "personalDefaultDescription": "Ngarkohet automatikisht kur asnjë depo nuk ka një profil të gozhduar.", + "organizationDefaultDescription": "Ngarkohet automatikisht për anëtarët që nuk kanë parazgjedhje personale.", + "setAsDefault": "Cakto si parazgjedhje", + "removeDefault": "Hiq si parazgjedhje", + "saveFailed": "Profili nuk mund të ruhej", + "deleteTitle": "Fshi profilin", + "deleteMessage": "Kjo heq përgjithmonë profilin dhe cilësimet e tij. Kjo nuk mund të zhbëhet.", + "deleteFailed": "Profili nuk mund të fshihej", + "deleteBlocked": "Ky profil përdoret nga një aktivizues uebhook. Fillimisht hiqeni atë nga ata nxitës.", + "deletedToast": "Profili \"{{name}}\" u fshi", + "variablesTitle": "Variablat e mjedisit", + "addVariable": "Shto variabël", + "keyLabel": "Çelësi", + "keyTooLong": "Çelësi duhet të ketë 256 karaktere ose më pak", + "keyDuplicate": "Ky çelës ekziston tashmë në profil", + "valueLabel": "Vlera", + "secretLabel": "Sekret", + "secrets": { + "markSecret": "Shënoni si sekret", + "masked": "Vlera e fshehur", + "reveal": "Zbuloni vlerën", + "newValuePlaceholder": "Vendos një vlerë të re sekrete", + "valuePlaceholder": "Vlerë sekrete (e kriptuar)" + }, + "variablesEmpty": "Ende nuk ka variabla", + "variablesSaveFailed": "Variabla nuk mund të ruhej", + "commandsTitle": "Komandat e konfigurimit", + "addCommand": "Shto komandë", + "commandLabel": "Komanda", + "commandPlaceholder": "e.g. pnpm install", + "commandsEmpty": "Nuk ka ende komanda konfigurimi", + "commandsSaveFailed": "Komandat e konfigurimit nuk mund të ruheshin", + "moveUp": "Lëvizni lart", + "moveDown": "Lëvizni poshtë", + "skillsTitle": "Aftësitë", + "addSkill": "Shtoni aftësi", + "skillNameLabel": "Emri i aftësisë", + "skillMarkdownLabel": "Përmbajtja e aftësive", + "skillsEmpty": "Ende nuk ka aftësi", + "skillNameRequired": "Futni një emër aftësie", + "skillNameInvalid": "Emri i aftësisë duhet të fillojë me një shkronjë ose shifër dhe të përdorë vetëm shkronja të vogla, shifra dhe viza", + "skillContentRequired": "Kërkohet përmbajtja e aftësive", + "skillSaveFailed": "Aftësia nuk mund të kursehej", + "skillsHint": "Ngjit një SKILL.md dhe emri i saj plotëson; shtoni një aftësi nga telefoni juaj pa zvarritje dhe lëshim.", + "skillEditTitle": "Shkathtësia e redaktimit", + "mcp": { + "title": "serverët MCP", + "add": "Shto server MCP", + "empty": "Nuk ka ende serverë MCP", + "name": "Emri i serverit", + "type": "Lloji i serverit", + "localType": "Lokale", + "remoteType": "Telekomanda", + "command": "Linja e komandës", + "url": "URL", + "environment": "Mjedisi (JSON)", + "headers": "Titujt (JSON)", + "timeout": "Kohëzgjatja", + "secretCount": "Numërimi sekret", + "namePlaceholder": "my-server", + "commandPlaceholder": "npx @example/mcp-server", + "jsonPlaceholder": "{\n \"API_KEY\": \"sk-...\"\n}", + "nameRequired": "Futni një emër serveri", + "nameInvalid": "Emri mund të përdorë vetëm shkronja, numra, nënvizime dhe viza", + "commandRequired": "Futni një komandë", + "commandTooLong": "Përdor maksimumi 50 argumente nga 500 karaktere secili", + "urlRequired": "Fut një URL", + "urlInvalid": "Fut një URL të vlefshme", + "urlTooLong": "URL-ja duhet të ketë 2048 karaktere ose më pak", + "jsonInvalid": "Fut JSON të vlefshëm - një objekt vlerash vargu", + "jsonTooLarge": "Përdor maksimumi 50 hyrje, çelësa deri në 128 dhe vlera deri në 4096 karaktere", + "timeoutInvalid": "Kohëzgjatja duhet të jetë një numër i plotë milisekondash", + "edit": "Ndrysho serverin MCP", + "saveFailed": "Serveri MCP nuk mund të ruhej" + }, + "agents": { + "title": "Agjentët e profilit", + "add": "Shto agjent", + "empty": "Nuk ka ende agjentë", + "name": "Emri i agjentit", + "slug": "Slug agjent", + "description": "Përshkrimi i agjentit", + "model": "Mbështetja e modelit", + "modelPlaceholder": "Përdorni parazgjedhjen e sesionit", + "prompt": "Njoftimi i sistemit", + "steps": "Hapa maksimale", + "stepsPlaceholder": "p.sh. 50", + "temperature": "Temperatura", + "temperaturePlaceholder": "p.sh. 0.2", + "topP": "top_p", + "topPPlaceholder": "p.sh. 0.95", + "slugPlaceholder": "my-agent", + "namePlaceholder": "Agjenti im", + "promptPlaceholder": "Ju jeni një...", + "tools": "Mjetet", + "visibility": "Dukshmëria", + "visibilityPrimary": "Primar - tregohet në zgjedhës", + "visibilitySubagent": "Subagent - i thirrur vetëm nga të tjerët", + "visibilityAll": "Të gjitha - të dyja", + "saveFailed": "Agjenti nuk mund të ruhej", + "nameRequired": "Futni një emër agjenti", + "slugRequired": "Fut një slug agjenti", + "slugInvalid": "Slug duhet të fillojë me një shkronjë dhe të përdorë vetëm shkronja të vogla, shifra dhe viza", + "slugConflict": "Ai slug është i rezervuar nga një agjent i integruar", + "edit": "Përpunoni agjent" + }, + "slashCommands": { + "title": "Komandat me prerje", + "add": "Shto komandën e pjerrët", + "empty": "Nuk ka ende komanda me prerje", + "name": "Emri i komandës", + "description": "Përshkrimi i komandës", + "template": "shabllon", + "namePlaceholder": "explain", + "templatePlaceholder": "Shpjegoni $ARGUMENTS", + "agent": "Agjenti", + "subtask": "Ekzekuto si nëndetyrë", + "edit": "Redakto komandën e pjerrët", + "saveFailed": "Komanda nuk mund të ruhej", + "nameRequired": "Futni një emër komande", + "nameInvalid": "Emri duhet të fillojë me një shkronjë të vogël dhe të përdorë vetëm shkronja të vogla, shifra dhe viza", + "templateRequired": "Futni një shabllon komandimi", + "nameConflict": "Ky emër është i rezervuar nga një komandë e integruar" + }, + "repoBindings": { + "title": "Profilet e parazgjedhura për repos", + "add": "Shto parazgjedhje", + "empty": "Asnjë parazgjedhje nuk është konfiguruar", + "emptyDescription": "Shto një parazgjedhje për të aplikuar automatikisht një profil kur nis seancat për një depo.", + "repo": "Zgjidhni depo…", + "profile": "Zgjidhni profilin", + "remove": "Çlidhe", + "private": "Privat", + "search": "Kërko në depo…", + "addedToast": "Profili i parazgjedhur është caktuar për \"{{repo}}\"", + "addFailed": "Profili i parazgjedhur nuk mund të caktohet", + "removedToast": "Profili i parazgjedhur u hoq për \"{{repo}}\"", + "removeFailed": "Profili i parazgjedhur nuk mund të hiqej", + "pinnedTitle": "E ngjitur në depo", + "pinRepo": "Pin një depo", + "pinnedEmpty": "Nuk është ngjitur në asnjë depo.", + "pinnedDescription": "Ngarkohet automatikisht sa herë që një seancë synon një nga këto depo.", + "pinnedToast": "Gozhduar te \"{{repo}}\"", + "pinFailed": "Depo nuk mund të gozhdohej", + "unboundToast": "Repo e palidhur", + "unbindFailed": "Repo nuk mund të shkëputej" + } } } diff --git a/apps/mobile/src/i18n/locales/sr.json b/apps/mobile/src/i18n/locales/sr.json index a1ed98117b7..da66f855a5c 100644 --- a/apps/mobile/src/i18n/locales/sr.json +++ b/apps/mobile/src/i18n/locales/sr.json @@ -2219,7 +2219,33 @@ "continueFrom": "Nastavlja se sesija {{title}}.", "importingSession": "Uvoz sesije", "cliCannotContinue": "Ovaj CLI ne može da nastavi sesiju. Izaberi Cloud Agent ili drugi CLI.", - "importFailed": "Uvoz ove sesije nije uspeo." + "importFailed": "Uvoz ove sesije nije uspeo.", + "pickProfile": "Izaberite profil", + "noProfile": "Nema profila", + "manageProfiles": "Upravljajte profilima", + "noProfiles": "Još nema profila. Napravite jedan da biste dodali promenljive okruženja, MCP servere i veštine.", + "advancedConfig": "Napredna konfiguracija", + "advancedConfigDescription": "Izaberite profil za učitavanje sačuvanih konfiguracija i ručno konfigurišite dodatne promenljive okruženja i komande za podešavanje", + "saveAsProfile": "Sačuvaj kao profil", + "saveProfileDescription": "Sačuvajte trenutne promenljive okruženja i komande za podešavanje kao profil za višekratnu upotrebu.", + "profileSummary": "{{vars}} promenljivih okruženja · {{commands}} komandi za podešavanje", + "profileSaved": "Profil \"{{name}}\" je sačuvan", + "organizationProfiles": "Profili organizacije", + "personalProfiles": "Lični profili", + "yourProfiles": "Vaši profili", + "failedToLoadProfiles": "Učitavanje profila nije uspelo", + "nameTooLong": "Ime profila je predugačko", + "descriptionTooLong": "Opis je predugačak", + "secretsEncrypted": "Tajne su šifrovane pre skladištenja.", + "profileActive": "Profil aktivan", + "profilesActive": "Profili aktivni", + "customConfigActive": "Prilagođena konfiguracija je aktivna", + "configNeedsAttention": "Konfiguraciji treba obratiti pažnju", + "repoProfile": "Repo profil", + "selectedProfile": "Izabrani profil", + "manualOverrides": "Ručno zamena", + "openSettingsToReview": "Otvorite Podešavanja za pregled", + "profileSelection": "Izbor profila" }, "partRenderer": { "updatedFileCount_one": "Ažuriran je {{displayCount}} fajl.", @@ -3267,5 +3293,180 @@ "multiplierError": "Unesi množilac 1 ili veći", "permissionDenied": "Nemaš dozvolu da upravljaš obaveštenjima o potrošnji.", "pushOffByCategory": "Isključeno u Obaveštenjima" + }, + "profiles": { + "title": "Profili", + "entrySubtitle": "Ponovo koristite podešavanja okruženja u svim sesijama", + "newProfile": "Novi profil", + "emptyTitle": "Još nema profila", + "emptyDescription": "Napravite profil da biste ponovo koristili promenljive okruženja, komande za podešavanje i veštine u svim sesijama.", + "loadFailed": "Učitavanje profila nije uspelo", + "counts": { + "vars": "{{count}} promenljivi", + "mcp": "{{count}} MCP", + "skills": "{{count}} veština", + "commands": "{{count}} komandi", + "varsShort": "{{count}}pro", + "skillsShort": "{{count}}veš", + "commandsShort": "{{count}}kom" + }, + "nameLabel": "Ime profila", + "namePlaceholder": "npr. Backend otklanjanje grešaka", + "descriptionLabel": "Opis profila", + "descriptionPlaceholder": "Opcioni opis", + "ownerLabel": "Vlasnik profila", + "nameRequired": "Unesite ime profila", + "createAction": "Kreirajte profil", + "createFailed": "Nije moguće napraviti profil", + "createdToast": "Profil \"{{name}}\" je kreiran", + "settingsSectionTitle": "Konfiguracija", + "defaultSectionTitle": "Podrazumevani profil", + "personalDefaultDescription": "Automatski se učitava kada nijedno spremište nema zakačeni profil.", + "organizationDefaultDescription": "Automatski se učitava za članove koji nemaju lične podrazumevane vrednosti.", + "setAsDefault": "Postavi kao podrazumevano", + "removeDefault": "Ukloni kao podrazumevano", + "saveFailed": "Nije moguće sačuvati profil", + "deleteTitle": "Izbriši profil", + "deleteMessage": "Ovo trajno uklanja profil i njegova podešavanja. Ovo se ne može poništiti.", + "deleteFailed": "Brisanje profila nije uspelo", + "deleteBlocked": "Ovaj profil koristi pokretač veb-huka. Prvo ga uklonite sa okidača.", + "deletedToast": "Profil \"{{name}}\" je izbrisan", + "variablesTitle": "Promenljive okruženja", + "addVariable": "Dodaj promenljivu", + "keyLabel": "Ključ", + "keyTooLong": "Ključ može imati najviše 256 znakova", + "keyDuplicate": "Ovaj ključ već postoji u profilu", + "valueLabel": "Vrednost", + "secretLabel": "Tajna", + "secrets": { + "markSecret": "Označi kao tajnu", + "masked": "Skrivena vrednost", + "reveal": "Otkrijte vrednost", + "newValuePlaceholder": "Unesite novu tajnu vrednost", + "valuePlaceholder": "Tajna vrednost (šifrovana)" + }, + "variablesEmpty": "Još nema promenljivih", + "variablesSaveFailed": "Nije moguće sačuvati promenljivu", + "commandsTitle": "Komande za podešavanje", + "addCommand": "Dodaj komandu", + "commandLabel": "Komanda", + "commandPlaceholder": "e.g. pnpm install", + "commandsEmpty": "Još nema komandi za podešavanje", + "commandsSaveFailed": "Nije moguće sačuvati komande za podešavanje", + "moveUp": "Pomeri se gore", + "moveDown": "Pomeri se dole", + "skillsTitle": "Veštine", + "addSkill": "Dodajte veštinu", + "skillNameLabel": "Naziv veštine", + "skillMarkdownLabel": "Sadržaj veština", + "skillsEmpty": "Još nema veština", + "skillNameRequired": "Unesite naziv veštine", + "skillNameInvalid": "Ime veštine mora da počinje slovom ili cifrom i koristi samo mala slova, cifre i crtice", + "skillContentRequired": "Neophodan je sadržaj veština", + "skillSaveFailed": "Nije moguće sačuvati veštinu", + "skillsHint": "Nalepite SKILL.md i njegovo ime se popuni; dodajte veštinu sa svog telefona bez prevlačenja i ispuštanja.", + "skillEditTitle": "Uredi veštinu", + "mcp": { + "title": "MCP serveri", + "add": "Dodajte MCP server", + "empty": "Još nema MCP servera", + "name": "Ime servera", + "type": "Tip servera", + "localType": "Lokalni", + "remoteType": "Udaljeni", + "command": "Komandna linija", + "url": "URL", + "environment": "Okruženje (JSON)", + "headers": "Zaglavlja (JSON)", + "timeout": "Vreme čekanja", + "secretCount": "Broj tajni", + "namePlaceholder": "my-server", + "commandPlaceholder": "npx @example/mcp-server", + "jsonPlaceholder": "{\n \"API_KEY\": \"sk-...\"\n}", + "nameRequired": "Unesite ime servera", + "nameInvalid": "Ime može da koristi samo slova, brojeve, donje crte i crtice", + "commandRequired": "Unesite komandu", + "commandTooLong": "Koristite najviše 50 argumenata od po 500 znakova", + "urlRequired": "Unesite URL", + "urlInvalid": "Unesite važeći URL", + "urlTooLong": "URL može imati najviše 2048 znakova", + "jsonInvalid": "Unesite važeći JSON — objekat vrednosti stringova", + "jsonTooLarge": "Koristite najviše 50 unosa, ključevi do 128 i vrednosti do 4096 znakova", + "timeoutInvalid": "Vreme čekanja mora biti ceo broj milisekundi", + "edit": "Uredite MCP server", + "saveFailed": "Nije moguće sačuvati MCP server" + }, + "agents": { + "title": "Profil agenti", + "add": "Dodajte agenta", + "empty": "Još nema agenata", + "name": "Ime agenta", + "slug": "Slug agenta", + "description": "Opis agenta", + "model": "Zamena modela", + "modelPlaceholder": "Koristi podrazumevanu sesiju", + "prompt": "Sistemski prompt", + "steps": "Maks. koraka", + "stepsPlaceholder": "npr. 50", + "temperature": "Temperatura", + "temperaturePlaceholder": "npr. 0.2", + "topP": "top_p", + "topPPlaceholder": "npr. 0.95", + "slugPlaceholder": "my-agent", + "namePlaceholder": "Moj agent", + "promptPlaceholder": "ti si...", + "tools": "Alati", + "visibility": "Vidljivost", + "visibilityPrimary": "Primarni — prikazuje se u biraču", + "visibilitySubagent": "Subagent — može se pozvati samo od drugih", + "visibilityAll": "Svi — oboje", + "saveFailed": "Nije moguće sačuvati agenta", + "nameRequired": "Unesite ime agenta", + "slugRequired": "Unesite slug agenta", + "slugInvalid": "Slug mora početi slovom i koristiti samo mala slova, cifre i crtice", + "slugConflict": "Taj slug je rezervisan od strane ugrađenog agenta", + "edit": "Uredi agenta" + }, + "slashCommands": { + "title": "Slash komande", + "add": "Dodaj komandu kose crte", + "empty": "Još nema komandi kosih crta", + "name": "Ime komande", + "description": "Opis komande", + "template": "Šablon", + "namePlaceholder": "explain", + "templatePlaceholder": "Objasnite $ARGUMENTS", + "agent": "Agent", + "subtask": "Pokreni kao podzadatak", + "edit": "Naredba za uređivanje kose crte", + "saveFailed": "Nije moguće sačuvati komandu", + "nameRequired": "Unesite ime komande", + "nameInvalid": "Ime mora da počinje malim slovom i da koristi samo mala slova, cifre i crtice", + "templateRequired": "Unesite šablon komande", + "nameConflict": "To ime je rezervisano ugrađenom komandom" + }, + "repoBindings": { + "title": "Podrazumevani profili za Repos", + "add": "Dodaj podrazumevano", + "empty": "Nema konfigurisanih podrazumevanih vrednosti", + "emptyDescription": "Dodajte podrazumevano za automatsku primenu profila pri pokretanju sesija za repo.", + "repo": "Izaberite spremište…", + "profile": "Izaberite profil", + "remove": "Odveži", + "private": "Privatno", + "search": "Pretražite spremišta…", + "addedToast": "Podrazumevani profil je postavljen za \"{{repo}}\"", + "addFailed": "Nije moguće postaviti podrazumevani profil", + "removedToast": "Podrazumevani profil je uklonjen za \"{{repo}}\"", + "removeFailed": "Uklanjanje podrazumevanog profila nije uspelo", + "pinnedTitle": "Zakačeno za spremišta", + "pinRepo": "Zakačite repo", + "pinnedEmpty": "Nije zakačeno ni za jedno spremište.", + "pinnedDescription": "Automatski se učitava kad god sesija cilja jedno od ovih spremišta.", + "pinnedToast": "Zakačeno za \"{{repo}}\"", + "pinFailed": "Nije moguće zakačiti repo", + "unboundToast": "Repo je odvezan", + "unbindFailed": "Nije moguće opozvati vezivanje repo-a" + } } } diff --git a/apps/mobile/src/i18n/locales/sv.json b/apps/mobile/src/i18n/locales/sv.json index e232b72d137..fe6b4fdffaf 100644 --- a/apps/mobile/src/i18n/locales/sv.json +++ b/apps/mobile/src/i18n/locales/sv.json @@ -2191,7 +2191,33 @@ "continueFrom": "Sessionen {{title}} fortsätter.", "importingSession": "Importerar sessionen", "cliCannotContinue": "Den här CLI-versionen kan inte fortsätta en session. Välj Cloud Agent eller en annan CLI-instans.", - "importFailed": "Det gick inte att importera den här sessionen." + "importFailed": "Det gick inte att importera den här sessionen.", + "pickProfile": "Välj en profil", + "noProfile": "Ingen profil", + "manageProfiles": "Hantera profiler", + "noProfiles": "Inga profiler än. Skapa en för att lägga till miljövariabler, MCP-servrar och färdigheter.", + "advancedConfig": "Avancerad konfiguration", + "advancedConfigDescription": "Välj en profil för att ladda sparade konfigurationer och manuellt konfigurera ytterligare miljövariabler och inställningskommandon", + "saveAsProfile": "Spara som profil", + "saveProfileDescription": "Spara de aktuella miljövariablerna och inställningskommandona som en återanvändbar profil.", + "profileSummary": "{{vars}} miljövariabler · {{commands}} inställningskommandon", + "profileSaved": "Profilen \"{{name}}\" har sparats", + "organizationProfiles": "Organisationsprofiler", + "personalProfiles": "Personliga profiler", + "yourProfiles": "Dina profiler", + "failedToLoadProfiles": "Det gick inte att läsa in profiler", + "nameTooLong": "Profilnamnet är för långt", + "descriptionTooLong": "Beskrivningen är för lång", + "secretsEncrypted": "Hemligheter krypteras innan lagring.", + "profileActive": "Profil aktiv", + "profilesActive": "Profiler aktiva", + "customConfigActive": "Anpassad konfiguration aktiv", + "configNeedsAttention": "Config behöver uppmärksamhet", + "repoProfile": "Repo profil", + "selectedProfile": "Vald profil", + "manualOverrides": "Manuella åsidosättningar", + "openSettingsToReview": "Öppna Inställningar för att granska", + "profileSelection": "Profilval" }, "partRenderer": { "updatedFileCount_one": "{{displayCount}} fil har uppdaterats.", @@ -3224,5 +3250,180 @@ "multiplierError": "Ange en multiplikator på 1 eller mer", "permissionDenied": "Du har inte behörighet att hantera utgiftsvarningar.", "pushOffByCategory": "Av i Aviseringar" + }, + "profiles": { + "title": "Profiler", + "entrySubtitle": "Återanvänd miljöinställningar över sessioner", + "newProfile": "Ny profil", + "emptyTitle": "Inga profiler än", + "emptyDescription": "Skapa en profil för att återanvända miljövariabler, inställningskommandon och färdigheter över sessioner.", + "loadFailed": "Det gick inte att läsa in profiler", + "counts": { + "vars": "{{count}} variabler", + "mcp": "{{count}} MCP", + "skills": "{{count}} färdigheter", + "commands": "{{count}} kommandon", + "varsShort": "{{count}}var", + "skillsShort": "{{count}}fär", + "commandsShort": "{{count}}kom" + }, + "nameLabel": "Profilnamn", + "namePlaceholder": "till exempel Backend-felsökning", + "descriptionLabel": "Profilbeskrivning", + "descriptionPlaceholder": "Valfri beskrivning", + "ownerLabel": "Profilägare", + "nameRequired": "Ange ett profilnamn", + "createAction": "Skapa profil", + "createFailed": "Det gick inte att skapa profil", + "createdToast": "Profilen \"{{name}}\" har skapats", + "settingsSectionTitle": "Konfiguration", + "defaultSectionTitle": "Standardprofil", + "personalDefaultDescription": "Laddas automatiskt när inget arkiv har en fäst profil.", + "organizationDefaultDescription": "Laddas automatiskt för medlemmar som inte har någon personlig standard.", + "setAsDefault": "Ställ in som standard", + "removeDefault": "Ta bort som standard", + "saveFailed": "Det gick inte att spara profilen", + "deleteTitle": "Ta bort profil", + "deleteMessage": "Detta tar bort profilen och dess inställningar permanent. Detta kan inte ångras.", + "deleteFailed": "Det gick inte att ta bort profilen", + "deleteBlocked": "Den här profilen används av en webhook-utlösare. Ta bort den från dessa utlösare först.", + "deletedToast": "Profilen {{name}} har tagits bort", + "variablesTitle": "Miljövariabler", + "addVariable": "Lägg till variabel", + "keyLabel": "Nyckel", + "keyTooLong": "Nyckeln får innehålla högst 256 tecken", + "keyDuplicate": "Den här nyckeln finns redan i profilen", + "valueLabel": "Värde", + "secretLabel": "Hemlighet", + "secrets": { + "markSecret": "Markera som hemlig", + "masked": "Dolt värde", + "reveal": "Avslöja värde", + "newValuePlaceholder": "Ange ett nytt hemligt värde", + "valuePlaceholder": "Hemligt värde (krypterat)" + }, + "variablesEmpty": "Inga variabler ännu", + "variablesSaveFailed": "Det gick inte att spara variabeln", + "commandsTitle": "Inställningskommandon", + "addCommand": "Lägg till kommando", + "commandLabel": "Kommando", + "commandPlaceholder": "e.g. pnpm install", + "commandsEmpty": "Inga inställningskommandon ännu", + "commandsSaveFailed": "Det gick inte att spara installationskommandon", + "moveUp": "Flytta upp", + "moveDown": "Flytta ner", + "skillsTitle": "Färdigheter", + "addSkill": "Lägg till skicklighet", + "skillNameLabel": "Skicklighetsnamn", + "skillMarkdownLabel": "Skicklighetsinnehåll", + "skillsEmpty": "Inga färdigheter än", + "skillNameRequired": "Ange ett färdighetsnamn", + "skillNameInvalid": "Färdighetsnamn måste börja med en bokstav eller siffra och endast använda små bokstäver, siffror och bindestreck", + "skillContentRequired": "Skicklighetsinnehåll krävs", + "skillSaveFailed": "Kunde inte rädda skickligheten", + "skillsHint": "Klistra in en SKILL.md och dess namn fylls i; lägg till en färdighet från din telefon utan att dra och släppa.", + "skillEditTitle": "Redigera färdighet", + "mcp": { + "title": "MCP-servrar", + "add": "Lägg till MCP-server", + "empty": "Inga MCP-servrar ännu", + "name": "Servernamn", + "type": "Servertyp", + "localType": "Lokal", + "remoteType": "Avlägsen", + "command": "Kommandorad", + "url": "URL", + "environment": "Miljö (JSON)", + "headers": "Rubriker (JSON)", + "timeout": "Timeout", + "secretCount": "Hemlig räkning", + "namePlaceholder": "my-server", + "commandPlaceholder": "npx @example/mcp-server", + "jsonPlaceholder": "{\n \"API_KEY\": \"sk-...\"\n}", + "nameRequired": "Ange ett servernamn", + "nameInvalid": "Namn kan endast använda bokstäver, siffror, understreck och bindestreck", + "commandRequired": "Ange ett kommando", + "commandTooLong": "Använd högst 50 argument med 500 tecken vardera", + "urlRequired": "Ange en URL", + "urlInvalid": "Ange en giltig URL", + "urlTooLong": "URL:en får innehålla högst 2048 tecken", + "jsonInvalid": "Ange giltig JSON — ett objekt med strängvärden", + "jsonTooLarge": "Använd högst 50 poster, nycklar upp till 128 och värden upp till 4096 tecken", + "timeoutInvalid": "Timeout måste vara ett helt antal millisekunder", + "edit": "Redigera MCP-server", + "saveFailed": "Det gick inte att spara MCP-servern" + }, + "agents": { + "title": "Profilagenter", + "add": "Lägg till agent", + "empty": "Inga agenter än", + "name": "Agentens namn", + "slug": "Agentsnigel", + "description": "Agent beskrivning", + "model": "Modell åsidosättande", + "modelPlaceholder": "Använd sessionsstandard", + "prompt": "Systemuppmaning", + "steps": "Max. steg", + "stepsPlaceholder": "t.ex. 50", + "temperature": "Temperatur", + "temperaturePlaceholder": "t.ex. 0.2", + "topP": "top_p", + "topPPlaceholder": "t.ex. 0.95", + "slugPlaceholder": "my-agent", + "namePlaceholder": "Min agent", + "promptPlaceholder": "Du är en...", + "tools": "Verktyg", + "visibility": "Synlighet", + "visibilityPrimary": "Primär — visas i väljaren", + "visibilitySubagent": "Subagent — endast anropsbar från andra", + "visibilityAll": "Alla - båda", + "saveFailed": "Det gick inte att spara agenten", + "nameRequired": "Ange ett agentnamn", + "slugRequired": "Ange en agentsnigel", + "slugInvalid": "Slug måste börja med en bokstav och endast använda små bokstäver, siffror och bindestreck", + "slugConflict": "Den snigeln är reserverad av en inbyggd agent", + "edit": "Redigera agent" + }, + "slashCommands": { + "title": "Slash kommandon", + "add": "Lägg till ett snedstreck kommando", + "empty": "Inga snedstreck kommandon ännu", + "name": "Kommandonamn", + "description": "Kommandobeskrivning", + "template": "Mall", + "namePlaceholder": "explain", + "templatePlaceholder": "Förklara $ARGUMENTS", + "agent": "Ombud", + "subtask": "Kör som deluppgift", + "edit": "Redigera snedstreck kommando", + "saveFailed": "Det gick inte att spara kommandot", + "nameRequired": "Ange ett kommandonamn", + "nameInvalid": "Namnet måste börja med en liten bokstav och endast använda små bokstäver, siffror och bindestreck", + "templateRequired": "Ange en kommandomall", + "nameConflict": "Det namnet är reserverat av ett inbyggt kommando" + }, + "repoBindings": { + "title": "Standardprofiler för Repos", + "add": "Lägg till standard", + "empty": "Inga standardinställningar konfigurerade", + "emptyDescription": "Lägg till en standard för att automatiskt tillämpa en profil när du startar sessioner för ett repo.", + "repo": "Välj förråd...", + "profile": "Välj profil", + "remove": "Avbinda", + "private": "Privat", + "search": "Sök arkiv...", + "addedToast": "Standardprofil inställd för \"{{repo}}\"", + "addFailed": "Det gick inte att ange standardprofil", + "removedToast": "Standardprofilen har tagits bort för \"{{repo}}\"", + "removeFailed": "Det gick inte att ta bort standardprofilen", + "pinnedTitle": "Fäst till arkiv", + "pinRepo": "Fäst ett repo", + "pinnedEmpty": "Inte fäst vid några förråd.", + "pinnedDescription": "Laddas automatiskt när en session riktar sig mot ett av dessa förråd.", + "pinnedToast": "Fäst till \"{{repo}}\"", + "pinFailed": "Det gick inte att fästa repo", + "unboundToast": "Repo obundet", + "unbindFailed": "Det gick inte att koppla upp repor" + } } } diff --git a/apps/mobile/src/i18n/locales/sw.json b/apps/mobile/src/i18n/locales/sw.json index f84bc29a40a..cb704d1c036 100644 --- a/apps/mobile/src/i18n/locales/sw.json +++ b/apps/mobile/src/i18n/locales/sw.json @@ -2191,7 +2191,33 @@ "continueFrom": "Kikao {{title}} kinaendelea.", "importingSession": "Inaingiza kikao", "cliCannotContinue": "CLI hii haiwezi kuendelea na kikao. Chagua Cloud Agent au CLI nyingine.", - "importFailed": "Imeshindikana kuingiza kikao hiki." + "importFailed": "Imeshindikana kuingiza kikao hiki.", + "pickProfile": "Chagua wasifu", + "noProfile": "Hakuna wasifu", + "manageProfiles": "Dhibiti wasifu", + "noProfiles": "Bado hakuna wasifu. Unda moja ili kuongeza vigezo vya mazingira, seva za MCP na ujuzi.", + "advancedConfig": "Usanidi wa hali ya juu", + "advancedConfigDescription": "Teua wasifu ili kupakia usanidi uliohifadhiwa, na usanidi mwenyewe vigeu vya ziada vya mazingira na amri za usanidi", + "saveAsProfile": "Hifadhi kama Wasifu", + "saveProfileDescription": "Hifadhi vigezo vya sasa vya mazingira na amri za usanidi kama wasifu unaoweza kutumika tena.", + "profileSummary": "{{vars}} vigezo vya mazingira · {{commands}} amri za usanidi", + "profileSaved": "Wasifu \"{{name}}\" umehifadhiwa", + "organizationProfiles": "Wasifu wa Shirika", + "personalProfiles": "Wasifu wa Kibinafsi", + "yourProfiles": "Wasifu Wako", + "failedToLoadProfiles": "Imeshindwa kupakia wasifu", + "nameTooLong": "Jina la wasifu ni refu sana", + "descriptionTooLong": "Maelezo ni marefu sana", + "secretsEncrypted": "Siri husimbwa kwa njia fiche kabla ya kuhifadhi.", + "profileActive": "Wasifu unatumika", + "profilesActive": "Wasifu unatumika", + "customConfigActive": "Usanidi maalum unatumika", + "configNeedsAttention": "Usanidi unahitaji umakini", + "repoProfile": "Repo wasifu", + "selectedProfile": "Wasifu uliochaguliwa", + "manualOverrides": "Ubatilishaji wa mikono", + "openSettingsToReview": "Fungua Mipangilio ili kukagua", + "profileSelection": "Uteuzi wa wasifu" }, "partRenderer": { "updatedFileCount_one": "Faili {{displayCount}} imesasishwa.", @@ -3224,5 +3250,180 @@ "multiplierError": "Weka kizidishi cha 1 au zaidi", "permissionDenied": "Huna ruhusa ya kusimamia tahadhari za matumizi.", "pushOffByCategory": "Imezimwa katika Arifa" + }, + "profiles": { + "title": "Wasifu", + "entrySubtitle": "Tumia tena mipangilio ya mazingira katika vipindi vyote", + "newProfile": "Wasifu mpya", + "emptyTitle": "Bado hakuna wasifu", + "emptyDescription": "Unda wasifu ili kutumia tena vigezo vya mazingira, amri za usanidi na ujuzi katika vipindi vyote.", + "loadFailed": "Haikuweza kupakia wasifu", + "counts": { + "vars": "{{count}} vigezo", + "mcp": "{{count}} MCP", + "skills": "{{count}} ujuzi", + "commands": "{{count}} amri", + "varsShort": "{{count}}v", + "skillsShort": "{{count}}u", + "commandsShort": "{{count}}a" + }, + "nameLabel": "Jina la wasifu", + "namePlaceholder": "k.m. Utatuzi wa nyuma", + "descriptionLabel": "Maelezo ya wasifu", + "descriptionPlaceholder": "Maelezo ya hiari", + "ownerLabel": "Mmiliki wa wasifu", + "nameRequired": "Ingiza jina la wasifu", + "createAction": "Unda wasifu", + "createFailed": "Haikuweza kuunda wasifu", + "createdToast": "Wasifu \"{{name}}\" umeundwa", + "settingsSectionTitle": "Usanidi", + "defaultSectionTitle": "Wasifu chaguomsingi", + "personalDefaultDescription": "Imepakia kiotomatiki wakati hakuna hazina iliyo na wasifu uliobandikwa.", + "organizationDefaultDescription": "Imepakiwa kiotomatiki kwa wanachama ambao hawana chaguo-msingi la kibinafsi.", + "setAsDefault": "Weka kama chaguomsingi", + "removeDefault": "Ondoa kama chaguomsingi", + "saveFailed": "Haikuweza kuhifadhi wasifu", + "deleteTitle": "Futa wasifu", + "deleteMessage": "Hii huondoa kabisa wasifu na mipangilio yake. Hili haliwezi kutenduliwa.", + "deleteFailed": "Haikuweza kufuta wasifu", + "deleteBlocked": "Wasifu huu unatumiwa na kichochezi cha mtandao. Iondoe kutoka kwa vichochezi hivyo kwanza.", + "deletedToast": "Wasifu \"{{name}}\" umefutwa", + "variablesTitle": "Vigezo vya mazingira", + "addVariable": "Ongeza tofauti", + "keyLabel": "Ufunguo", + "keyTooLong": "Ufunguo lazima uwe na vibambo 256 au chini yake", + "keyDuplicate": "Ufunguo huu tayari upo katika wasifu", + "valueLabel": "Thamani", + "secretLabel": "Siri", + "secrets": { + "markSecret": "Weka alama kuwa siri", + "masked": "Thamani iliyofichwa", + "reveal": "Onyesha thamani", + "newValuePlaceholder": "Weka thamani mpya ya siri", + "valuePlaceholder": "Thamani ya siri (imesimbwa)" + }, + "variablesEmpty": "Bado hakuna vigeu", + "variablesSaveFailed": "Haikuweza kuhifadhi tofauti", + "commandsTitle": "Kuanzisha amri", + "addCommand": "Ongeza amri", + "commandLabel": "Amri", + "commandPlaceholder": "e.g. pnpm install", + "commandsEmpty": "Bado hakuna amri za usanidi", + "commandsSaveFailed": "Haikuweza kuhifadhi amri za usanidi", + "moveUp": "Sogeza juu", + "moveDown": "Sogeza chini", + "skillsTitle": "Ujuzi", + "addSkill": "Ongeza ujuzi", + "skillNameLabel": "Jina la ujuzi", + "skillMarkdownLabel": "Maudhui ya ujuzi", + "skillsEmpty": "Hakuna ujuzi bado", + "skillNameRequired": "Weka jina la ujuzi", + "skillNameInvalid": "Jina la ustadi lazima lianze na herufi au tarakimu na litumie herufi ndogo, tarakimu na deshi pekee.", + "skillContentRequired": "Maudhui ya ujuzi yanahitajika", + "skillSaveFailed": "Haikuweza kuhifadhi ujuzi", + "skillsHint": "Bandika SKILL.md na jina lake lijazwe; ongeza ujuzi kutoka kwa simu yako bila kuburuta na kuangusha.", + "skillEditTitle": "Hariri ujuzi", + "mcp": { + "title": "seva za MCP", + "add": "Ongeza seva ya MCP", + "empty": "Bado hakuna seva za MCP", + "name": "Jina la seva", + "type": "Aina ya seva", + "localType": "Ndani", + "remoteType": "Mbali", + "command": "Mstari wa amri", + "url": "URL", + "environment": "Mazingira (JSON)", + "headers": "Vijajuu (JSON)", + "timeout": "Muda umekwisha", + "secretCount": "Hesabu ya siri", + "namePlaceholder": "my-server", + "commandPlaceholder": "npx @example/mcp-server", + "jsonPlaceholder": "{\n \"API_KEY\": \"sk-...\"\n}", + "nameRequired": "Ingiza jina la seva", + "nameInvalid": "Jina linaweza kutumia herufi, nambari, mistari chini na deshi pekee", + "commandRequired": "Ingiza amri", + "commandTooLong": "Tumia hoja 50 au chini, kila moja vibambo 500", + "urlRequired": "Weka URL", + "urlInvalid": "Weka URL halali", + "urlTooLong": "URL lazima iwe na vibambo 2048 au chini", + "jsonInvalid": "Weka JSON halali - kitu cha thamani za mfuatano", + "jsonTooLarge": "Tumia maingizo 50 au chini, funguo hadi 128 na thamani hadi vibambo 4096", + "timeoutInvalid": "Muda umekwisha lazima iwe idadi nzima ya milisekunde", + "edit": "Badilisha seva ya MCP", + "saveFailed": "Haikuweza kuhifadhi seva ya MCP" + }, + "agents": { + "title": "Wakala wa wasifu", + "add": "Ongeza wakala", + "empty": "Bado hakuna mawakala", + "name": "Jina la wakala", + "slug": "Koa wa wakala", + "description": "Maelezo ya wakala", + "model": "Kubatilisha mfano", + "modelPlaceholder": "Tumia chaguo-msingi la kipindi", + "prompt": "Agizo la mfumo", + "steps": "Hatua za juu", + "stepsPlaceholder": "kmf. 50", + "temperature": "Halijoto", + "temperaturePlaceholder": "kmf. 0.2", + "topP": "top_p", + "topPPlaceholder": "kmf. 0.95", + "slugPlaceholder": "my-agent", + "namePlaceholder": "Wakala Wangu", + "promptPlaceholder": "Wewe ni...", + "tools": "Zana", + "visibility": "Mwonekano", + "visibilityPrimary": "Msingi - imeonyeshwa kwenye kiteua", + "visibilitySubagent": "Subagent - inaitwa tu kutoka kwa wengine", + "visibilityAll": "Wote - wote wawili", + "saveFailed": "Haikuweza kuhifadhi wakala", + "nameRequired": "Weka jina la wakala", + "slugRequired": "Ingiza koa wa wakala", + "slugInvalid": "Slug lazima aanze na herufi na atumie herufi ndogo, tarakimu na deshi pekee", + "slugConflict": "Koa hiyo imehifadhiwa na wakala aliyejengwa", + "edit": "Hariri wakala" + }, + "slashCommands": { + "title": "Amri za kufyeka", + "add": "Ongeza amri ya kufyeka", + "empty": "Bado hakuna amri za kufyeka", + "name": "Jina la amri", + "description": "Maelezo ya amri", + "template": "Kiolezo", + "namePlaceholder": "explain", + "templatePlaceholder": "Eleza $ARGUMENTS", + "agent": "Wakala", + "subtask": "Endesha kama kazi ndogo", + "edit": "Hariri amri ya kufyeka", + "saveFailed": "Haikuweza kuhifadhi amri", + "nameRequired": "Ingiza jina la amri", + "nameInvalid": "Jina lazima lianze na herufi ndogo na litumie herufi ndogo, tarakimu na deshi pekee", + "templateRequired": "Ingiza kiolezo cha amri", + "nameConflict": "Jina hilo limehifadhiwa na amri iliyojengwa" + }, + "repoBindings": { + "title": "Profaili Chaguomsingi za Repos", + "add": "Ongeza chaguomsingi", + "empty": "Hakuna chaguo-msingi zilizosanidiwa", + "emptyDescription": "Ongeza chaguomsingi ili kutumia wasifu kiotomatiki unapoanzisha vipindi vya repo.", + "repo": "Chagua hazina...", + "profile": "Chagua wasifu", + "remove": "Tendua", + "private": "Privat", + "search": "Tafuta hazina...", + "addedToast": "Wasifu chaguomsingi umewekwa kwa ajili ya \"{{repo}}\"", + "addFailed": "Haikuweza kuweka wasifu chaguomsingi", + "removedToast": "Wasifu chaguo-msingi umeondolewa kwa \"{{repo}}\"", + "removeFailed": "Haikuweza kuondoa wasifu chaguomsingi", + "pinnedTitle": "Imebandikwa kwenye hazina", + "pinRepo": "Bandika repo", + "pinnedEmpty": "Haijabandikwa kwenye hazina zozote.", + "pinnedDescription": "Inapakia kiotomatiki wakati wowote kipindi kinalenga moja ya hazina hizi.", + "pinnedToast": "Imebandikwa kwa \"{{repo}}\"", + "pinFailed": "Haikuweza kubandika repo", + "unboundToast": "Repo huru", + "unbindFailed": "Haikuweza kubandua repo" + } } } diff --git a/apps/mobile/src/i18n/locales/ta.json b/apps/mobile/src/i18n/locales/ta.json index d736d12bdff..44314977313 100644 --- a/apps/mobile/src/i18n/locales/ta.json +++ b/apps/mobile/src/i18n/locales/ta.json @@ -2191,7 +2191,33 @@ "continueFrom": "{{title}} தொடரப்படுகிறது.", "importingSession": "அமர்வு இறக்குமதி செய்யப்படுகிறது", "cliCannotContinue": "இந்த CLI-இல் அமர்வைத் தொடர முடியாது. Cloud Agent அல்லது வேறொரு CLI-ஐத் தேர்ந்தெடுக்கவும்.", - "importFailed": "இந்த அமர்வை இறக்குமதி செய்ய முடியவில்லை." + "importFailed": "இந்த அமர்வை இறக்குமதி செய்ய முடியவில்லை.", + "pickProfile": "சுயவிவரத்தைத் தேர்ந்தெடுக்கவும்", + "noProfile": "சுயவிவரம் இல்லை", + "manageProfiles": "சுயவிவரங்களை நிர்வகிக்கவும்", + "noProfiles": "இதுவரை சுயவிவரங்கள் இல்லை. சூழல் மாறிகள், MCP சேவையகங்கள் மற்றும் திறன்களைச் சேர்க்க ஒன்றை உருவாக்கவும்.", + "advancedConfig": "மேம்பட்ட கட்டமைப்பு", + "advancedConfigDescription": "சேமிக்கப்பட்ட உள்ளமைவுகளை ஏற்ற ஒரு சுயவிவரத்தைத் தேர்ந்தெடுக்கவும், மேலும் சூழல் மாறிகள் மற்றும் அமைவு கட்டளைகளை கைமுறையாக உள்ளமைக்கவும்", + "saveAsProfile": "சுயவிவரமாக சேமிக்கவும்", + "saveProfileDescription": "தற்போதைய சூழல் மாறிகள் மற்றும் அமைவு கட்டளைகளை மீண்டும் பயன்படுத்தக்கூடிய சுயவிவரமாக சேமிக்கவும்.", + "profileSummary": "{{vars}} சூழல் மாறிகள் · {{commands}} அமைவு கட்டளைகள்", + "profileSaved": "சுயவிவரம் \"{{name}}\" சேமிக்கப்பட்டது", + "organizationProfiles": "நிறுவன சுயவிவரங்கள்", + "personalProfiles": "தனிப்பட்ட சுயவிவரங்கள்", + "yourProfiles": "உங்கள் சுயவிவரங்கள்", + "failedToLoadProfiles": "சுயவிவரங்களை ஏற்றுவதில் தோல்வி", + "nameTooLong": "சுயவிவரப் பெயர் மிக நீளமாக உள்ளது", + "descriptionTooLong": "விளக்கம் மிக நீண்டது", + "secretsEncrypted": "சேமிப்பிற்கு முன் ரகசியங்கள் குறியாக்கம் செய்யப்படுகின்றன.", + "profileActive": "சுயவிவரம் செயலில் உள்ளது", + "profilesActive": "சுயவிவரங்கள் செயலில் உள்ளன", + "customConfigActive": "தனிப்பயன் கட்டமைப்பு செயலில் உள்ளது", + "configNeedsAttention": "கட்டமைப்பு கவனம் தேவை", + "repoProfile": "ரெப்போ சுயவிவரம்", + "selectedProfile": "தேர்ந்தெடுக்கப்பட்ட சுயவிவரம்", + "manualOverrides": "கைமுறை மேலெழுதுகிறது", + "openSettingsToReview": "மதிப்பாய்வு செய்ய அமைப்புகளைத் திறக்கவும்", + "profileSelection": "சுயவிவரத் தேர்வு" }, "partRenderer": { "updatedFileCount_one": "{{displayCount}} கோப்பு புதுப்பிக்கப்பட்டது.", @@ -3224,5 +3250,180 @@ "multiplierError": "1 அல்லது அதற்கு மேற்பட்ட பெருக்கியை உள்ளிடு", "permissionDenied": "செலவு எச்சரிக்கைகளை நிர்வகிக்க உங்களுக்கு அனுமதி இல்லை.", "pushOffByCategory": "அறிவிப்புகளில் அணைக்கப்பட்டது" + }, + "profiles": { + "title": "சுயவிவரங்கள்", + "entrySubtitle": "அமர்வுகள் முழுவதும் சூழல் அமைப்புகளை மீண்டும் பயன்படுத்தவும்", + "newProfile": "புதிய சுயவிவரம்", + "emptyTitle": "இதுவரை சுயவிவரங்கள் இல்லை", + "emptyDescription": "சூழல் மாறிகள், அமைவு கட்டளைகள் மற்றும் அமர்வுகள் முழுவதும் திறன்களை மீண்டும் பயன்படுத்த சுயவிவரத்தை உருவாக்கவும்.", + "loadFailed": "சுயவிவரங்களை ஏற்ற முடியவில்லை", + "counts": { + "vars": "{{count}} மாறிகள்", + "mcp": "{{count}} MCP", + "skills": "{{count}} திறன்கள்", + "commands": "{{count}} கட்டளைகள்", + "varsShort": "{{count}}மா", + "skillsShort": "{{count}}தி", + "commandsShort": "{{count}}க" + }, + "nameLabel": "சுயவிவரப் பெயர்", + "namePlaceholder": "எ.கா. பின்தளத்தில் பிழைத்திருத்தம்", + "descriptionLabel": "சுயவிவர விளக்கம்", + "descriptionPlaceholder": "விருப்ப விளக்கம்", + "ownerLabel": "சுயவிவர உரிமையாளர்", + "nameRequired": "சுயவிவரப் பெயரை உள்ளிடவும்", + "createAction": "சுயவிவரத்தை உருவாக்கவும்", + "createFailed": "சுயவிவரத்தை உருவாக்க முடியவில்லை", + "createdToast": "சுயவிவரம் \"{{name}}\" உருவாக்கப்பட்டது", + "settingsSectionTitle": "கட்டமைப்பு", + "defaultSectionTitle": "இயல்புநிலை சுயவிவரம்", + "personalDefaultDescription": "எந்த களஞ்சியத்திலும் பின் செய்யப்பட்ட சுயவிவரம் இல்லாதபோது தானாக ஏற்றப்படும்.", + "organizationDefaultDescription": "தனிப்பட்ட இயல்புநிலை இல்லாத உறுப்பினர்களுக்கு தானாக ஏற்றப்பட்டது.", + "setAsDefault": "இயல்புநிலையாக அமைக்கவும்", + "removeDefault": "இயல்புநிலையாக அகற்று", + "saveFailed": "சுயவிவரத்தைச் சேமிக்க முடியவில்லை", + "deleteTitle": "சுயவிவரத்தை நீக்கு", + "deleteMessage": "இது சுயவிவரத்தையும் அதன் அமைப்புகளையும் நிரந்தரமாக நீக்குகிறது. இதை செயல்தவிர்க்க முடியாது.", + "deleteFailed": "சுயவிவரத்தை நீக்க முடியவில்லை", + "deleteBlocked": "இந்த சுயவிவரம் webhook தூண்டுதலால் பயன்படுத்தப்படுகிறது. முதலில் அந்த தூண்டுதல்களிலிருந்து அதை அகற்றவும்.", + "deletedToast": "சுயவிவரம் \"{{name}}\" நீக்கப்பட்டது", + "variablesTitle": "சுற்றுச்சூழல் மாறிகள்", + "addVariable": "மாறியைச் சேர்க்கவும்", + "keyLabel": "முக்கிய", + "keyTooLong": "முக்கிய 256 எழுத்துகள் அல்லது அதற்குக் குறைவாக இருக்க வேண்டும்", + "keyDuplicate": "இந்த முக்கிய ஏற்கனவே சுயவிவரத்தில் உள்ளது", + "valueLabel": "மதிப்பு", + "secretLabel": "இரகசியம்", + "secrets": { + "markSecret": "ரகசியம் எனக் குறிக்கவும்", + "masked": "மறைக்கப்பட்ட மதிப்பு", + "reveal": "மதிப்பை வெளிப்படுத்துங்கள்", + "newValuePlaceholder": "புதிய ரகசிய மதிப்பை உள்ளிடவும்", + "valuePlaceholder": "ரகசிய மதிப்பு (மறையாக்கப்பட்டது)" + }, + "variablesEmpty": "இதுவரை மாறிகள் இல்லை", + "variablesSaveFailed": "மாறியைச் சேமிக்க முடியவில்லை", + "commandsTitle": "கட்டளைகளை அமைக்கவும்", + "addCommand": "கட்டளையைச் சேர்க்கவும்", + "commandLabel": "கட்டளை", + "commandPlaceholder": "e.g. pnpm install", + "commandsEmpty": "இதுவரை அமைவு கட்டளைகள் இல்லை", + "commandsSaveFailed": "அமைவு கட்டளைகளைச் சேமிக்க முடியவில்லை", + "moveUp": "மேலே செல்லவும்", + "moveDown": "கீழே நகர்த்தவும்", + "skillsTitle": "திறன்கள்", + "addSkill": "திறமையைச் சேர்க்கவும்", + "skillNameLabel": "திறமை பெயர்", + "skillMarkdownLabel": "திறன் உள்ளடக்கம்", + "skillsEmpty": "இன்னும் திறமை இல்லை", + "skillNameRequired": "திறன் பெயரை உள்ளிடவும்", + "skillNameInvalid": "திறன் பெயர் ஒரு எழுத்து அல்லது இலக்கத்துடன் தொடங்க வேண்டும் மற்றும் சிறிய எழுத்துக்கள், இலக்கங்கள் மற்றும் கோடுகளை மட்டுமே பயன்படுத்த வேண்டும்", + "skillContentRequired": "திறன் உள்ளடக்கம் தேவை", + "skillSaveFailed": "திறமையைச் சேமிக்க முடியவில்லை", + "skillsHint": "ஒரு SKILL.md ஐ ஒட்டவும், அதன் பெயர் நிரப்பப்படும்; இழுத்து விடாமல் உங்கள் ஃபோனிலிருந்து திறமையைச் சேர்க்கவும்.", + "skillEditTitle": "திருத்தும் திறமை", + "mcp": { + "title": "MCP சேவையகங்கள்", + "add": "MCP சேவையகத்தைச் சேர்க்கவும்", + "empty": "இன்னும் MCP சேவையகங்கள் இல்லை", + "name": "சர்வர் பெயர்", + "type": "சேவையக வகை", + "localType": "உள்ளூர்", + "remoteType": "ரிமோட்", + "command": "கட்டளை வரி", + "url": "URL", + "environment": "சுற்றுச்சூழல் (JSON)", + "headers": "தலைப்புகள் (JSON)", + "timeout": "நேரம் முடிந்தது", + "secretCount": "இரகசிய எண்ணிக்கை", + "namePlaceholder": "my-server", + "commandPlaceholder": "npx @example/mcp-server", + "jsonPlaceholder": "{\n \"API_KEY\": \"sk-...\"\n}", + "nameRequired": "சர்வர் பெயரை உள்ளிடவும்", + "nameInvalid": "பெயர் எழுத்துகள், எண்கள், அடிக்கோடுகள் மற்றும் கோடுகளை மட்டுமே பயன்படுத்த முடியும்", + "commandRequired": "கட்டளையை உள்ளிடவும்", + "commandTooLong": "அதிகபட்சம் 50 வாதங்களைப் பயன்படுத்தவும், ஒவ்வொன்றும் 500 எழுத்துகள்", + "urlRequired": "URL ஐ உள்ளிடவும்", + "urlInvalid": "சரியான URL ஐ உள்ளிடவும்", + "urlTooLong": "URL 2048 எழுத்துகள் அல்லது அதற்குக் குறைவாக இருக்க வேண்டும்", + "jsonInvalid": "செல்லுபடியாகும் JSON ஐ உள்ளிடவும் — சரம் மதிப்புகளின் பொருள்", + "jsonTooLarge": "அதிகபட்சம் 50 உள்ளீடுகளைப் பயன்படுத்தவும், முக்கிய 128 வரை மற்றும் மதிப்புகள் 4096 எழுத்துகள் வரை", + "timeoutInvalid": "காலாவதியானது மில்லி விநாடிகளின் முழு எண்ணாக இருக்க வேண்டும்", + "edit": "MCP சேவையகத்தைத் திருத்தவும்", + "saveFailed": "MCP சேவையகத்தைச் சேமிக்க முடியவில்லை" + }, + "agents": { + "title": "சுயவிவர முகவர்கள்", + "add": "முகவரைச் சேர்க்கவும்", + "empty": "இதுவரை முகவர்கள் இல்லை", + "name": "முகவர் பெயர்", + "slug": "முகவர் ஸ்லக்", + "description": "முகவர் விளக்கம்", + "model": "மாதிரி மேலெழுதுதல்", + "modelPlaceholder": "அமர்வு இயல்புநிலையைப் பயன்படுத்தவும்", + "prompt": "சிஸ்டம் ப்ராம்ட்", + "steps": "அதிகபட்ச படிகள்", + "stepsPlaceholder": "எ.கா. 50", + "temperature": "வெப்பநிலை", + "temperaturePlaceholder": "எ.கா. 0.2", + "topP": "top_p", + "topPPlaceholder": "எ.கா. 0.95", + "slugPlaceholder": "my-agent", + "namePlaceholder": "என் முகவர்", + "promptPlaceholder": "நீங்கள் ஒரு ...", + "tools": "கருவிகள்", + "visibility": "தெரிவுநிலை", + "visibilityPrimary": "முதன்மை - பிக்கரில் காட்டப்பட்டுள்ளது", + "visibilitySubagent": "துணை - மற்றவர்களிடமிருந்து மட்டுமே அழைக்க முடியும்", + "visibilityAll": "அனைத்தும் - இரண்டும்", + "saveFailed": "ஏஜென்ட்டைச் சேமிக்க முடியவில்லை", + "nameRequired": "முகவர் பெயரை உள்ளிடவும்", + "slugRequired": "ஏஜென்ட் ஸ்லக்கை உள்ளிடவும்", + "slugInvalid": "ஸ்லக் ஒரு எழுத்தில் தொடங்க வேண்டும் மற்றும் சிறிய எழுத்துக்கள், இலக்கங்கள் மற்றும் கோடுகளை மட்டுமே பயன்படுத்த வேண்டும்", + "slugConflict": "அந்த ஸ்லக் ஒரு உள்ளமைக்கப்பட்ட முகவரால் ஒதுக்கப்பட்டுள்ளது", + "edit": "முகவரைத் திருத்தவும்" + }, + "slashCommands": { + "title": "ஸ்லாஷ் கட்டளைகள்", + "add": "ஸ்லாஷ் கட்டளையைச் சேர்க்கவும்", + "empty": "ஸ்லாஷ் கட்டளைகள் இல்லை", + "name": "கட்டளை பெயர்", + "description": "கட்டளை விளக்கம்", + "template": "டெம்ப்ளேட்", + "namePlaceholder": "explain", + "templatePlaceholder": "$ARGUMENTSஐ விளக்கவும்", + "agent": "முகவர்", + "subtask": "துணைப் பணியாக இயக்கவும்", + "edit": "ஸ்லாஷ் கட்டளையைத் திருத்தவும்", + "saveFailed": "கட்டளையைச் சேமிக்க முடியவில்லை", + "nameRequired": "கட்டளை பெயரை உள்ளிடவும்", + "nameInvalid": "பெயர் சிறிய எழுத்துடன் தொடங்க வேண்டும் மற்றும் சிறிய எழுத்துக்கள், இலக்கங்கள் மற்றும் கோடுகளை மட்டுமே பயன்படுத்த வேண்டும்", + "templateRequired": "கட்டளை டெம்ப்ளேட்டை உள்ளிடவும்", + "nameConflict": "அந்த பெயர் ஒரு உள்ளமைக்கப்பட்ட கட்டளையால் ஒதுக்கப்பட்டுள்ளது" + }, + "repoBindings": { + "title": "Repos க்கான இயல்புநிலை சுயவிவரங்கள்", + "add": "இயல்புநிலையைச் சேர்க்கவும்", + "empty": "இயல்புநிலைகள் எதுவும் கட்டமைக்கப்படவில்லை", + "emptyDescription": "ரெப்போவுக்கான அமர்வுகளைத் தொடங்கும்போது சுயவிவரத்தைத் தானாகப் பயன்படுத்த இயல்புநிலையைச் சேர்க்கவும்.", + "repo": "களஞ்சியத்தைத் தேர்ந்தெடு…", + "profile": "சுயவிவரத்தைத் தேர்ந்தெடுக்கவும்", + "remove": "அவிழ்த்துவிடு", + "private": "தனியார்", + "search": "களஞ்சியங்களைத் தேடு…", + "addedToast": "\"{{repo}}\" க்கான இயல்புநிலை சுயவிவரம் அமைக்கப்பட்டது", + "addFailed": "இயல்புநிலை சுயவிவரத்தை அமைக்க முடியவில்லை", + "removedToast": "\"{{repo}}\" க்கான இயல்புநிலை சுயவிவரம் அகற்றப்பட்டது", + "removeFailed": "இயல்புநிலை சுயவிவரத்தை அகற்ற முடியவில்லை", + "pinnedTitle": "களஞ்சியங்களில் பின் செய்யப்பட்டது", + "pinRepo": "ரெப்போவை பின் செய்யவும்", + "pinnedEmpty": "எந்த களஞ்சியங்களிலும் பின் செய்யப்படவில்லை.", + "pinnedDescription": "ஒரு அமர்வு இந்தக் களஞ்சியங்களில் ஒன்றைக் குறிவைக்கும் போதெல்லாம் தானாக ஏற்றப்படும்.", + "pinnedToast": "\"{{repo}}\" இல் பின் செய்யப்பட்டது", + "pinFailed": "ரெப்போவை பின் செய்ய முடியவில்லை", + "unboundToast": "ரெப்போ கட்டுப்படாதது", + "unbindFailed": "ரெப்போவை அவிழ்க்க முடியவில்லை" + } } } diff --git a/apps/mobile/src/i18n/locales/te.json b/apps/mobile/src/i18n/locales/te.json index 89d386c84b6..b6703ca91b4 100644 --- a/apps/mobile/src/i18n/locales/te.json +++ b/apps/mobile/src/i18n/locales/te.json @@ -2191,7 +2191,33 @@ "continueFrom": "{{title}}ను కొనసాగిస్తోంది.", "importingSession": "సెషన్‌ను దిగుమతి చేస్తోంది", "cliCannotContinue": "ఈ CLIతో సెషన్‌ను కొనసాగించడం సాధ్యం కాదు. Cloud Agent లేదా మరో CLIను ఎంచుకోండి.", - "importFailed": "ఈ సెషన్‌ను దిగుమతి చేయలేకపోయాము." + "importFailed": "ఈ సెషన్‌ను దిగుమతి చేయలేకపోయాము.", + "pickProfile": "ప్రొఫైల్‌ని ఎంచుకోండి", + "noProfile": "ప్రొఫైల్ లేదు", + "manageProfiles": "ప్రొఫైల్‌లను నిర్వహించండి", + "noProfiles": "ఇంకా ప్రొఫైల్‌లు లేవు. ఎన్విరాన్మెంట్ వేరియబుల్స్, MCP సర్వర్‌లు మరియు నైపుణ్యాలను జోడించడానికి ఒకదాన్ని సృష్టించండి.", + "advancedConfig": "అధునాతన కాన్ఫిగరేషన్", + "advancedConfigDescription": "సేవ్ చేసిన కాన్ఫిగరేషన్‌లను లోడ్ చేయడానికి ప్రొఫైల్‌ను ఎంచుకోండి మరియు అదనపు ఎన్విరాన్‌మెంట్ వేరియబుల్స్ మరియు సెటప్ ఆదేశాలను మాన్యువల్‌గా కాన్ఫిగర్ చేయండి", + "saveAsProfile": "ప్రొఫైల్‌గా సేవ్ చేయండి", + "saveProfileDescription": "ప్రస్తుత ఎన్విరాన్మెంట్ వేరియబుల్స్ మరియు సెటప్ ఆదేశాలను పునర్వినియోగ ప్రొఫైల్‌గా సేవ్ చేయండి.", + "profileSummary": "{{vars}} ఎన్విరాన్మెంట్ వేరియబుల్స్ · {{commands}} సెటప్ ఆదేశాలు", + "profileSaved": "ప్రొఫైల్ \"{{name}}\" సేవ్ చేయబడింది", + "organizationProfiles": "సంస్థ ప్రొఫైల్స్", + "personalProfiles": "వ్యక్తిగత ప్రొఫైల్‌లు", + "yourProfiles": "మీ ప్రొఫైల్‌లు", + "failedToLoadProfiles": "ప్రొఫైల్‌లను లోడ్ చేయడంలో విఫలమైంది", + "nameTooLong": "ప్రొఫైల్ పేరు చాలా పొడవుగా ఉంది", + "descriptionTooLong": "వివరణ చాలా పొడవుగా ఉంది", + "secretsEncrypted": "నిల్వకు ముందు రహస్యాలు గుప్తీకరించబడతాయి.", + "profileActive": "ప్రొఫైల్ సక్రియంగా ఉంది", + "profilesActive": "ప్రొఫైల్‌లు సక్రియంగా ఉన్నాయి", + "customConfigActive": "అనుకూల కాన్ఫిగరేషన్ సక్రియంగా ఉంది", + "configNeedsAttention": "ఆకృతీకరణకు శ్రద్ధ అవసరం", + "repoProfile": "రెపో ప్రొఫైల్", + "selectedProfile": "ఎంచుకున్న ప్రొఫైల్", + "manualOverrides": "మాన్యువల్ ఓవర్‌రైడ్‌లు", + "openSettingsToReview": "సమీక్షించడానికి సెట్టింగ్‌లను తెరవండి", + "profileSelection": "ప్రొఫైల్ ఎంపిక" }, "partRenderer": { "updatedFileCount_one": "{{displayCount}} ఫైల్‌ను నవీకరించాము.", @@ -3224,5 +3250,180 @@ "multiplierError": "1 లేదా అంతకంటే ఎక్కువ గుణకాన్ని నమోదు చేయండి", "permissionDenied": "ఖర్చు హెచ్చరికలను నిర్వహించడానికి మీకు అనుమతి లేదు.", "pushOffByCategory": "నోటిఫికేషన్లలో ఆఫ్" + }, + "profiles": { + "title": "ప్రొఫైల్స్", + "entrySubtitle": "సెషన్‌లలో పర్యావరణ సెట్టింగ్‌లను మళ్లీ ఉపయోగించండి", + "newProfile": "కొత్త ప్రొఫైల్", + "emptyTitle": "ఇంకా ప్రొఫైల్‌లు లేవు", + "emptyDescription": "సెషన్‌లలో ఎన్విరాన్‌మెంట్ వేరియబుల్స్, సెటప్ కమాండ్‌లు మరియు స్కిల్స్‌ని మళ్లీ ఉపయోగించేందుకు ప్రొఫైల్‌ను సృష్టించండి.", + "loadFailed": "ప్రొఫైల్‌లను లోడ్ చేయడం సాధ్యపడలేదు", + "counts": { + "vars": "{{count}} వేరియబుల్స్", + "mcp": "{{count}} MCP", + "skills": "{{count}} నైపుణ్యాలు", + "commands": "{{count}} ఆదేశాలు", + "varsShort": "{{count}}వే", + "skillsShort": "{{count}}నై", + "commandsShort": "{{count}}ఆ" + }, + "nameLabel": "ప్రొఫైల్ పేరు", + "namePlaceholder": "ఉదా బ్యాకెండ్ డీబగ్గింగ్", + "descriptionLabel": "ప్రొఫైల్ వివరణ", + "descriptionPlaceholder": "ఐచ్ఛిక వివరణ", + "ownerLabel": "ప్రొఫైల్ యజమాని", + "nameRequired": "ప్రొఫైల్ పేరును నమోదు చేయండి", + "createAction": "ప్రొఫైల్ సృష్టించండి", + "createFailed": "ప్రొఫైల్‌ని సృష్టించడం సాధ్యపడలేదు", + "createdToast": "ప్రొఫైల్ \"{{name}}\" సృష్టించబడింది", + "settingsSectionTitle": "ఆకృతీకరణ", + "defaultSectionTitle": "డిఫాల్ట్ ప్రొఫైల్", + "personalDefaultDescription": "ఏ రిపోజిటరీలో పిన్ చేయబడిన ప్రొఫైల్ లేనప్పుడు స్వయంచాలకంగా లోడ్ చేయబడుతుంది.", + "organizationDefaultDescription": "వ్యక్తిగత డిఫాల్ట్ లేని సభ్యుల కోసం ఆటో-లోడ్ చేయబడింది.", + "setAsDefault": "డిఫాల్ట్‌గా సెట్ చేయండి", + "removeDefault": "డిఫాల్ట్‌గా తీసివేయండి", + "saveFailed": "ప్రొఫైల్‌ను సేవ్ చేయడం సాధ్యపడలేదు", + "deleteTitle": "ప్రొఫైల్‌ను తొలగించండి", + "deleteMessage": "ఇది ప్రొఫైల్ మరియు దాని సెట్టింగ్‌లను శాశ్వతంగా తొలగిస్తుంది. ఇది రద్దు చేయబడదు.", + "deleteFailed": "ప్రొఫైల్‌ను తొలగించడం సాధ్యపడలేదు", + "deleteBlocked": "ఈ ప్రొఫైల్ వెబ్‌హుక్ ట్రిగ్గర్ ద్వారా ఉపయోగించబడుతుంది. ముందుగా ఆ ట్రిగ్గర్‌ల నుండి దాన్ని తీసివేయండి.", + "deletedToast": "ప్రొఫైల్ \"{{name}}\" తొలగించబడింది", + "variablesTitle": "పర్యావరణ వేరియబుల్స్", + "addVariable": "వేరియబుల్ జోడించండి", + "keyLabel": "కీ", + "keyTooLong": "కీ 256 అక్షరాలు లేదా తక్కువ ఉండాలి", + "keyDuplicate": "ఈ కీ ఇప్పటికే ప్రొఫైల్లో ఉంది", + "valueLabel": "విలువ", + "secretLabel": "రహస్యం", + "secrets": { + "markSecret": "రహస్యంగా గుర్తించండి", + "masked": "దాచిన విలువ", + "reveal": "విలువను బహిర్గతం చేయండి", + "newValuePlaceholder": "కొత్త రహస్య విలువను నమోదు చేయండి", + "valuePlaceholder": "రహస్య విలువ (ఎన్క్రిప్ట్ చేయబడింది)" + }, + "variablesEmpty": "ఇంకా వేరియబుల్స్ లేవు", + "variablesSaveFailed": "వేరియబుల్‌ని సేవ్ చేయడం సాధ్యపడలేదు", + "commandsTitle": "సెటప్ ఆదేశాలు", + "addCommand": "ఆదేశాన్ని జోడించండి", + "commandLabel": "ఆదేశం", + "commandPlaceholder": "e.g. pnpm install", + "commandsEmpty": "ఇంకా సెటప్ ఆదేశాలు లేవు", + "commandsSaveFailed": "సెటప్ ఆదేశాలను సేవ్ చేయడం సాధ్యపడలేదు", + "moveUp": "పైకి కదలండి", + "moveDown": "క్రిందికి తరలించు", + "skillsTitle": "నైపుణ్యాలు", + "addSkill": "నైపుణ్యాన్ని జోడించండి", + "skillNameLabel": "నైపుణ్యం పేరు", + "skillMarkdownLabel": "నైపుణ్యం కంటెంట్", + "skillsEmpty": "ఇంకా నైపుణ్యాలు లేవు", + "skillNameRequired": "నైపుణ్యం పేరును నమోదు చేయండి", + "skillNameInvalid": "నైపుణ్యం పేరు తప్పనిసరిగా అక్షరం లేదా అంకెతో ప్రారంభం కావాలి మరియు చిన్న అక్షరాలు, అంకెలు మరియు డాష్‌లను మాత్రమే ఉపయోగించాలి", + "skillContentRequired": "నైపుణ్యం కంటెంట్ అవసరం", + "skillSaveFailed": "నైపుణ్యాన్ని సేవ్ చేయడం సాధ్యపడలేదు", + "skillsHint": "SKILL.mdని అతికించండి మరియు దాని పేరు నింపబడుతుంది; డ్రాగ్ మరియు డ్రాప్ లేకుండా మీ ఫోన్ నుండి నైపుణ్యాన్ని జోడించండి.", + "skillEditTitle": "నైపుణ్యాన్ని సవరించండి", + "mcp": { + "title": "MCP సర్వర్లు", + "add": "MCP సర్వర్‌ని జోడించండి", + "empty": "ఇంకా MCP సర్వర్లు లేవు", + "name": "సర్వర్ పేరు", + "type": "సర్వర్ రకం", + "localType": "స్థానిక", + "remoteType": "రిమోట్", + "command": "కమాండ్ లైన్", + "url": "URL", + "environment": "పర్యావరణం (JSON)", + "headers": "శీర్షికలు (JSON)", + "timeout": "గడువు ముగిసింది", + "secretCount": "రహస్య గణన", + "namePlaceholder": "my-server", + "commandPlaceholder": "npx @example/mcp-server", + "jsonPlaceholder": "{\n \"API_KEY\": \"sk-...\"\n}", + "nameRequired": "సర్వర్ పేరును నమోదు చేయండి", + "nameInvalid": "పేరు అక్షరాలు, సంఖ్యలు, అండర్‌స్కోర్‌లు మరియు డాష్‌లను మాత్రమే ఉపయోగించగలదు", + "commandRequired": "ఆదేశాన్ని నమోదు చేయండి", + "commandTooLong": "గరిష్ఠంగా 50 ఆర్గ్యుమెంట్లను ఉపయోగించండి, ప్రతి ఒక్కటి 500 అక్షరాలు", + "urlRequired": "URLని నమోదు చేయండి", + "urlInvalid": "చెల్లుబాటు అయ్యే URLని నమోదు చేయండి", + "urlTooLong": "URL 2048 అక్షరాలు లేదా తక్కువ ఉండాలి", + "jsonInvalid": "చెల్లుబాటు అయ్యే JSONని నమోదు చేయండి — స్ట్రింగ్ విలువల వస్తువు", + "jsonTooLarge": "గరిష్ఠంగా 50 ఎంట్రీలను ఉపయోగించండి, కీలు 128 వరకు మరియు విలువలు 4096 అక్షరాల వరకు", + "timeoutInvalid": "సమయం ముగియడం తప్పనిసరిగా మిల్లీసెకన్ల సంఖ్య అయి ఉండాలి", + "edit": "MCP సర్వర్‌ని సవరించండి", + "saveFailed": "MCP సర్వర్‌ని సేవ్ చేయడం సాధ్యపడలేదు" + }, + "agents": { + "title": "ప్రొఫైల్ ఏజెంట్లు", + "add": "ఏజెంట్‌ని జోడించండి", + "empty": "ఇంకా ఏజెంట్లు లేరు", + "name": "ఏజెంట్ పేరు", + "slug": "ఏజెంట్ స్లగ్", + "description": "ఏజెంట్ వివరణ", + "model": "మోడల్ ఓవర్‌రైడ్", + "modelPlaceholder": "సెషన్ డిఫాల్ట్‌ని ఉపయోగించండి", + "prompt": "సిస్టమ్ ప్రాంప్ట్", + "steps": "గరిష్ఠ దశలు", + "stepsPlaceholder": "ఉదా. 50", + "temperature": "ఉష్ణోగ్రత", + "temperaturePlaceholder": "ఉదా. 0.2", + "topP": "top_p", + "topPPlaceholder": "ఉదా. 0.95", + "slugPlaceholder": "my-agent", + "namePlaceholder": "నా ఏజెంట్", + "promptPlaceholder": "మీరు ఒక ...", + "tools": "ఉపకరణాలు", + "visibility": "దృశ్యమానత", + "visibilityPrimary": "ప్రాథమిక — పికర్‌లో చూపబడింది", + "visibilitySubagent": "సబ్జెక్ట్ — ఇతరుల నుండి మాత్రమే పిలవబడుతుంది", + "visibilityAll": "అన్నీ - రెండూ", + "saveFailed": "ఏజెంట్‌ని సేవ్ చేయడం సాధ్యపడలేదు", + "nameRequired": "ఏజెంట్ పేరును నమోదు చేయండి", + "slugRequired": "ఏజెంట్ స్లగ్‌ని నమోదు చేయండి", + "slugInvalid": "స్లగ్ తప్పనిసరిగా అక్షరంతో ప్రారంభం కావాలి మరియు చిన్న అక్షరాలు, అంకెలు మరియు డాష్‌లను మాత్రమే ఉపయోగించాలి", + "slugConflict": "ఆ స్లగ్ అంతర్నిర్మిత ఏజెంట్ ద్వారా రిజర్వ్ చేయబడింది", + "edit": "ఏజెంట్‌ని సవరించండి" + }, + "slashCommands": { + "title": "ఆదేశాలను స్లాష్ చేయండి", + "add": "స్లాష్ ఆదేశాన్ని జోడించండి", + "empty": "ఇంకా స్లాష్ ఆదేశాలు లేవు", + "name": "కమాండ్ పేరు", + "description": "కమాండ్ వివరణ", + "template": "మూస", + "namePlaceholder": "explain", + "templatePlaceholder": "$ARGUMENTSని వివరించండి", + "agent": "ఏజెంట్", + "subtask": "సబ్‌టాస్క్‌గా అమలు చేయండి", + "edit": "స్లాష్ ఆదేశాన్ని సవరించండి", + "saveFailed": "ఆదేశాన్ని సేవ్ చేయడం సాధ్యపడలేదు", + "nameRequired": "కమాండ్ పేరును నమోదు చేయండి", + "nameInvalid": "పేరు తప్పనిసరిగా చిన్న అక్షరంతో ప్రారంభం కావాలి మరియు చిన్న అక్షరాలు, అంకెలు మరియు డాష్‌లను మాత్రమే ఉపయోగించాలి", + "templateRequired": "కమాండ్ టెంప్లేట్‌ను నమోదు చేయండి", + "nameConflict": "ఆ పేరు అంతర్నిర్మిత కమాండ్ ద్వారా రిజర్వ్ చేయబడింది" + }, + "repoBindings": { + "title": "Repos కోసం డిఫాల్ట్ ప్రొఫైల్‌లు", + "add": "డిఫాల్ట్‌ని జోడించండి", + "empty": "డిఫాల్ట్‌లు ఏవీ కాన్ఫిగర్ చేయబడలేదు", + "emptyDescription": "రెపో కోసం సెషన్‌లను ప్రారంభించేటప్పుడు ప్రొఫైల్‌ను స్వయంచాలకంగా వర్తింపజేయడానికి డిఫాల్ట్‌ను జోడించండి.", + "repo": "రిపోజిటరీని ఎంచుకోండి...", + "profile": "ప్రొఫైల్‌ని ఎంచుకోండి", + "remove": "బైండ్ చేయి", + "private": "ప్రైవేట్", + "search": "రిపోజిటరీలను శోధించండి...", + "addedToast": "\"{{repo}}\" కోసం డిఫాల్ట్ ప్రొఫైల్ సెట్ చేయబడింది", + "addFailed": "డిఫాల్ట్ ప్రొఫైల్‌ను సెట్ చేయడం సాధ్యపడలేదు", + "removedToast": "\"{{repo}}\" కోసం డిఫాల్ట్ ప్రొఫైల్ తీసివేయబడింది", + "removeFailed": "డిఫాల్ట్ ప్రొఫైల్‌ని తీసివేయడం సాధ్యపడలేదు", + "pinnedTitle": "రిపోజిటరీలకు పిన్ చేయబడింది", + "pinRepo": "రెపోను పిన్ చేయండి", + "pinnedEmpty": "ఏ రిపోజిటరీలకు పిన్ చేయబడలేదు.", + "pinnedDescription": "సెషన్ ఈ రిపోజిటరీలలో ఒకదానిని లక్ష్యంగా చేసుకున్నప్పుడు స్వయంచాలకంగా లోడ్ అవుతుంది.", + "pinnedToast": "\"{{repo}}\"కి పిన్ చేయబడింది", + "pinFailed": "రెపోను పిన్ చేయడం సాధ్యపడలేదు", + "unboundToast": "రెపో అన్‌బౌండ్", + "unbindFailed": "రెపోను అన్‌బైండ్ చేయడం సాధ్యపడలేదు" + } } } diff --git a/apps/mobile/src/i18n/locales/th.json b/apps/mobile/src/i18n/locales/th.json index 8e429182a0c..7a5ede1e84b 100644 --- a/apps/mobile/src/i18n/locales/th.json +++ b/apps/mobile/src/i18n/locales/th.json @@ -2191,7 +2191,33 @@ "continueFrom": "กำลังทำงานต่อจาก {{title}}", "importingSession": "กำลังนำเข้าเซสชัน", "cliCannotContinue": "CLI นี้ไม่สามารถทำงานต่อจากเซสชันเดิมได้ เลือก Cloud Agent หรือ CLI อื่น", - "importFailed": "ไม่สามารถนำเข้าเซสชันนี้ได้" + "importFailed": "ไม่สามารถนำเข้าเซสชันนี้ได้", + "pickProfile": "เลือกโปรไฟล์", + "noProfile": "ไม่มีโปรไฟล์", + "manageProfiles": "จัดการโปรไฟล์", + "noProfiles": "ยังไม่มีโปรไฟล์ สร้างรายการเพื่อเพิ่มตัวแปรสภาพแวดล้อม เซิร์ฟเวอร์ MCP และทักษะ", + "advancedConfig": "การกำหนดค่าขั้นสูง", + "advancedConfigDescription": "เลือกโปรไฟล์เพื่อโหลดการกำหนดค่าที่บันทึกไว้ และกำหนดค่าตัวแปรสภาพแวดล้อมเพิ่มเติมและคำสั่งการตั้งค่าด้วยตนเอง", + "saveAsProfile": "บันทึกเป็นโปรไฟล์", + "saveProfileDescription": "บันทึกตัวแปรสภาพแวดล้อมปัจจุบันและคำสั่งการตั้งค่าเป็นโปรไฟล์ที่นำมาใช้ซ้ำได้", + "profileSummary": "ตัวแปรสภาพแวดล้อม {{vars}} · คำสั่งการตั้งค่า {{commands}}", + "profileSaved": "บันทึกโปรไฟล์ \"{{name}}\" แล้ว", + "organizationProfiles": "โปรไฟล์องค์กร", + "personalProfiles": "โปรไฟล์ส่วนบุคคล", + "yourProfiles": "โปรไฟล์ของคุณ", + "failedToLoadProfiles": "โหลดโปรไฟล์ไม่สำเร็จ", + "nameTooLong": "ชื่อโปรไฟล์ยาวเกินไป", + "descriptionTooLong": "คำอธิบายยาวเกินไป", + "secretsEncrypted": "ข้อมูลลับจะถูกเข้ารหัสก่อนจัดเก็บข้อมูล", + "profileActive": "โปรไฟล์ใช้งานอยู่", + "profilesActive": "โปรไฟล์ใช้งานอยู่", + "customConfigActive": "การกำหนดค่าแบบกำหนดเองทำงานอยู่", + "configNeedsAttention": "การกำหนดค่าจำเป็นต้องได้รับการดูแล", + "repoProfile": "โปรไฟล์ซื้อคืน", + "selectedProfile": "โปรไฟล์ที่เลือก", + "manualOverrides": "การแทนที่ด้วยตนเอง", + "openSettingsToReview": "เปิดการตั้งค่าเพื่อตรวจสอบ", + "profileSelection": "การเลือกโปรไฟล์" }, "partRenderer": { "updatedFileCount_one": "อัปเดตไฟล์แล้ว {{displayCount}} ไฟล์", @@ -3224,5 +3250,180 @@ "multiplierError": "ป้อนตัวคูณ 1 หรือมากกว่า", "permissionDenied": "คุณไม่มีสิทธิ์จัดการการแจ้งเตือนการใช้จ่าย", "pushOffByCategory": "ปิดในการแจ้งเตือน" + }, + "profiles": { + "title": "โปรไฟล์", + "entrySubtitle": "นำการตั้งค่าสภาพแวดล้อมมาใช้ซ้ำในเซสชันต่างๆ", + "newProfile": "โปรไฟล์ใหม่", + "emptyTitle": "ยังไม่มีโปรไฟล์", + "emptyDescription": "สร้างโปรไฟล์เพื่อนำตัวแปรสภาพแวดล้อม คำสั่งการตั้งค่า และทักษะมาใช้ซ้ำในเซสชันต่างๆ", + "loadFailed": "ไม่สามารถโหลดโปรไฟล์ได้", + "counts": { + "vars": "{{count}} ตัวแปร", + "mcp": "{{count}} MCP", + "skills": "{{count}} ทักษะ", + "commands": "{{count}} คำสั่ง", + "varsShort": "{{count}}ต", + "skillsShort": "{{count}}ท", + "commandsShort": "{{count}}ค" + }, + "nameLabel": "ชื่อโปรไฟล์", + "namePlaceholder": "เช่น การแก้ไขข้อบกพร่องแบ็กเอนด์", + "descriptionLabel": "คำอธิบายโปรไฟล์", + "descriptionPlaceholder": "คำอธิบายเพิ่มเติม", + "ownerLabel": "เจ้าของโปรไฟล์", + "nameRequired": "ป้อนชื่อโปรไฟล์", + "createAction": "สร้างโปรไฟล์", + "createFailed": "ไม่สามารถสร้างโปรไฟล์ได้", + "createdToast": "สร้างโปรไฟล์ \"{{name}}\" แล้ว", + "settingsSectionTitle": "การกำหนดค่า", + "defaultSectionTitle": "โปรไฟล์เริ่มต้น", + "personalDefaultDescription": "โหลดอัตโนมัติเมื่อไม่มีพื้นที่เก็บข้อมูลที่มีโปรไฟล์ที่ปักหมุดไว้", + "organizationDefaultDescription": "โหลดอัตโนมัติสำหรับสมาชิกที่ไม่มีค่าเริ่มต้นส่วนบุคคล", + "setAsDefault": "ตั้งเป็นค่าเริ่มต้น", + "removeDefault": "ลบออกตามค่าเริ่มต้น", + "saveFailed": "ไม่สามารถบันทึกโปรไฟล์ได้", + "deleteTitle": "ลบโปรไฟล์", + "deleteMessage": "การดำเนินการนี้จะลบโปรไฟล์และการตั้งค่าออกอย่างถาวร สิ่งนี้ไม่สามารถยกเลิกได้", + "deleteFailed": "ไม่สามารถลบโปรไฟล์ได้", + "deleteBlocked": "โปรไฟล์นี้ถูกใช้โดยทริกเกอร์เว็บฮุค ลบออกจากทริกเกอร์เหล่านั้นก่อน", + "deletedToast": "ลบโปรไฟล์ \"{{name}}\" แล้ว", + "variablesTitle": "ตัวแปรสภาพแวดล้อม", + "addVariable": "เพิ่มตัวแปร", + "keyLabel": "สำคัญ", + "keyTooLong": "คีย์ต้องมี 256 อักขระหรือน้อยกว่า", + "keyDuplicate": "คีย์นี้มีอยู่ในโปรไฟล์แล้ว", + "valueLabel": "ค่า", + "secretLabel": "ความลับ", + "secrets": { + "markSecret": "ทำเครื่องหมายว่าเป็นความลับ", + "masked": "คุณค่าที่ซ่อนอยู่", + "reveal": "เผยคุณค่า", + "newValuePlaceholder": "ป้อนค่าเป็นความลับใหม่", + "valuePlaceholder": "ค่าเป็นความลับ (เข้ารหัสแล้ว)" + }, + "variablesEmpty": "ยังไม่มีตัวแปร", + "variablesSaveFailed": "ไม่สามารถบันทึกตัวแปรได้", + "commandsTitle": "คำสั่งการตั้งค่า", + "addCommand": "เพิ่มคำสั่ง", + "commandLabel": "สั่งการ", + "commandPlaceholder": "e.g. pnpm install", + "commandsEmpty": "ยังไม่มีคำสั่งการตั้งค่า", + "commandsSaveFailed": "ไม่สามารถบันทึกคำสั่งการตั้งค่าได้", + "moveUp": "เลื่อนขึ้น", + "moveDown": "เลื่อนลง", + "skillsTitle": "ทักษะ", + "addSkill": "เพิ่มทักษะ", + "skillNameLabel": "ชื่อทักษะ", + "skillMarkdownLabel": "เนื้อหาทักษะ", + "skillsEmpty": "ยังไม่มีทักษะเลย", + "skillNameRequired": "ป้อนชื่อทักษะ", + "skillNameInvalid": "ชื่อทักษะต้องขึ้นต้นด้วยตัวอักษรหรือตัวเลข และใช้เฉพาะตัวอักษรพิมพ์เล็ก ตัวเลข และขีดกลางเท่านั้น", + "skillContentRequired": "จำเป็นต้องมีเนื้อหาทักษะ", + "skillSaveFailed": "ไม่สามารถบันทึกทักษะได้", + "skillsHint": "วาง SKILL.md และกรอกชื่อลงไป เพิ่มทักษะจากโทรศัพท์ของคุณโดยไม่ต้องลากและวาง", + "skillEditTitle": "แก้ไขทักษะ", + "mcp": { + "title": "เซิร์ฟเวอร์ MCP", + "add": "เพิ่มเซิร์ฟเวอร์ MCP", + "empty": "ยังไม่มีเซิร์ฟเวอร์ MCP", + "name": "ชื่อเซิร์ฟเวอร์", + "type": "ประเภทเซิร์ฟเวอร์", + "localType": "ท้องถิ่น", + "remoteType": "ระยะไกล", + "command": "บรรทัดคำสั่ง", + "url": "URL", + "environment": "สิ่งแวดล้อม (JSON)", + "headers": "ส่วนหัว (JSON)", + "timeout": "หมดเวลา", + "secretCount": "การนับความลับ", + "namePlaceholder": "my-server", + "commandPlaceholder": "npx @example/mcp-server", + "jsonPlaceholder": "{\n \"API_KEY\": \"sk-...\"\n}", + "nameRequired": "ป้อนชื่อเซิร์ฟเวอร์", + "nameInvalid": "ชื่อใช้ได้เฉพาะตัวอักษร ตัวเลข ขีดล่าง และขีดกลางเท่านั้น", + "commandRequired": "ป้อนคำสั่ง", + "commandTooLong": "ใช้อาร์กิวเมนต์ไม่เกิน 50 รายการ รายการละ 500 อักขระ", + "urlRequired": "ป้อน URL", + "urlInvalid": "ป้อน URL ที่ถูกต้อง", + "urlTooLong": "URL ต้องมี 2048 อักขระหรือน้อยกว่า", + "jsonInvalid": "ป้อน JSON ที่ถูกต้อง — ออบเจ็กต์ของค่าสตริง", + "jsonTooLarge": "ใช้รายการไม่เกิน 50 รายการ คีย์ไม่เกิน 128 และค่าไม่เกิน 4096 อักขระ", + "timeoutInvalid": "การหมดเวลาต้องเป็นจำนวนมิลลิวินาทีทั้งหมด", + "edit": "แก้ไขเซิร์ฟเวอร์ MCP", + "saveFailed": "ไม่สามารถบันทึกเซิร์ฟเวอร์ MCP" + }, + "agents": { + "title": "ตัวแทนโปรไฟล์", + "add": "เพิ่มตัวแทน", + "empty": "ยังไม่มีตัวแทน", + "name": "ชื่อตัวแทน", + "slug": "ตัวแทนทาก", + "description": "คำอธิบายตัวแทน", + "model": "การแทนที่โมเดล", + "modelPlaceholder": "ใช้ค่าเริ่มต้นของเซสชัน", + "prompt": "พร้อมท์ระบบ", + "steps": "ขั้นตอนสูงสุด", + "stepsPlaceholder": "เช่น 50", + "temperature": "อุณหภูมิ", + "temperaturePlaceholder": "เช่น 0.2", + "topP": "top_p", + "topPPlaceholder": "เช่น 0.95", + "slugPlaceholder": "my-agent", + "namePlaceholder": "ตัวแทนของฉัน", + "promptPlaceholder": "คุณเป็น...", + "tools": "เครื่องมือ", + "visibility": "การมองเห็น", + "visibilityPrimary": "หลัก — แสดงในเครื่องมือเลือก", + "visibilitySubagent": "Subagent — สามารถเรียกได้จากผู้อื่นเท่านั้น", + "visibilityAll": "ทั้งหมด - ทั้งสองอย่าง", + "saveFailed": "ไม่สามารถบันทึกตัวแทนได้", + "nameRequired": "ป้อนชื่อตัวแทน", + "slugRequired": "ใส่ตัวแทนทาก", + "slugInvalid": "Slug ต้องขึ้นต้นด้วยตัวอักษรและใช้เฉพาะตัวอักษรพิมพ์เล็ก ตัวเลข และขีดกลางเท่านั้น", + "slugConflict": "ทากนั้นถูกสงวนไว้โดยตัวแทนในตัว", + "edit": "แก้ไขตัวแทน" + }, + "slashCommands": { + "title": "คำสั่งสแลช", + "add": "เพิ่มคำสั่งสแลช", + "empty": "ยังไม่มีคำสั่งสแลช", + "name": "ชื่อคำสั่ง", + "description": "คำอธิบายคำสั่ง", + "template": "แม่แบบ", + "namePlaceholder": "explain", + "templatePlaceholder": "อธิบาย $ARGUMENTS", + "agent": "ตัวแทน", + "subtask": "ทำงานเป็นงานย่อย", + "edit": "แก้ไขคำสั่งสแลช", + "saveFailed": "ไม่สามารถบันทึกคำสั่งได้", + "nameRequired": "ป้อนชื่อคำสั่ง", + "nameInvalid": "ชื่อต้องขึ้นต้นด้วยตัวอักษรพิมพ์เล็กและใช้เฉพาะตัวอักษรพิมพ์เล็ก ตัวเลข และขีดกลางเท่านั้น", + "templateRequired": "ป้อนเทมเพลตคำสั่ง", + "nameConflict": "ชื่อนั้นถูกสงวนไว้โดยคำสั่งในตัว" + }, + "repoBindings": { + "title": "โปรไฟล์เริ่มต้นสำหรับ Repos", + "add": "เพิ่มค่าเริ่มต้น", + "empty": "ไม่มีการกำหนดค่าเริ่มต้น", + "emptyDescription": "เพิ่มค่าเริ่มต้นเพื่อใช้โปรไฟล์โดยอัตโนมัติเมื่อเริ่มต้นเซสชันสำหรับ repo", + "repo": "เลือกที่เก็บ...", + "profile": "เลือกโปรไฟล์", + "remove": "เลิกผูก", + "private": "ส่วนตัว", + "search": "ค้นหาที่เก็บ...", + "addedToast": "โปรไฟล์เริ่มต้นที่ตั้งไว้สำหรับ \"{{repo}}\"", + "addFailed": "ไม่สามารถตั้งค่าโปรไฟล์เริ่มต้นได้", + "removedToast": "โปรไฟล์เริ่มต้นถูกลบสำหรับ \"{{repo}}\"", + "removeFailed": "ไม่สามารถลบโปรไฟล์เริ่มต้นได้", + "pinnedTitle": "ปักหมุดไว้ที่คลังแล้ว", + "pinRepo": "ปักหมุด repo", + "pinnedEmpty": "ไม่ถูกตรึงไว้ที่ที่เก็บใด ๆ", + "pinnedDescription": "โหลดอัตโนมัติทุกครั้งที่เซสชันกำหนดเป้าหมายหนึ่งในที่เก็บเหล่านี้", + "pinnedToast": "ปักหมุดไว้ที่ \"{{repo}}\"", + "pinFailed": "ปักหมุด repo ไม่ได้", + "unboundToast": "Repo ไม่ถูกผูกไว้", + "unbindFailed": "ไม่สามารถเลิกผูก repo ได้" + } } } diff --git a/apps/mobile/src/i18n/locales/tr.json b/apps/mobile/src/i18n/locales/tr.json index 453eedaebc3..1cd4e64ffa2 100644 --- a/apps/mobile/src/i18n/locales/tr.json +++ b/apps/mobile/src/i18n/locales/tr.json @@ -1687,7 +1687,33 @@ "continueFrom": "{{title}} oturumuna devam ediliyor.", "importingSession": "Oturum içe aktarılıyor", "cliCannotContinue": "Bu CLI bir oturuma devam etmeyi desteklemiyor. Cloud Agent veya başka bir CLI seç.", - "importFailed": "Bu oturum içe aktarılamadı." + "importFailed": "Bu oturum içe aktarılamadı.", + "pickProfile": "Bir profil seç", + "noProfile": "Profil yok", + "manageProfiles": "Profilleri yönet", + "noProfiles": "Henüz profil yok. Ortam değişkenleri, MCP sunucuları ve beceriler eklemek için bir tane oluşturun.", + "advancedConfig": "Gelişmiş Yapılandırma", + "advancedConfigDescription": "Kaydedilen yapılandırmaları yüklemek için bir profil seçin ve ek ortam değişkenlerini ve kurulum komutlarını manuel olarak yapılandırın", + "saveAsProfile": "Profil olarak kaydet", + "saveProfileDescription": "Geçerli ortam değişkenlerini ve kurulum komutlarını yeniden kullanılabilir bir profil olarak kaydedin.", + "profileSummary": "{{vars}} ortam değişkenleri · {{commands}} kurulum komutları", + "profileSaved": "\"{{name}}\" profili kaydedildi", + "organizationProfiles": "Organizasyon Profilleri", + "personalProfiles": "Kişisel Profiller", + "yourProfiles": "Profilleriniz", + "failedToLoadProfiles": "Profiller yüklenemedi", + "nameTooLong": "Profil adı çok uzun", + "descriptionTooLong": "Açıklama çok uzun", + "secretsEncrypted": "Sırlar saklanmadan önce şifrelenir.", + "profileActive": "Profil etkin", + "profilesActive": "Profiller etkin", + "customConfigActive": "Özel yapılandırma etkin", + "configNeedsAttention": "Yapılandırmaya dikkat edilmesi gerekiyor", + "repoProfile": "Repo profili", + "selectedProfile": "Seçilen profil", + "manualOverrides": "Manuel geçersiz kılmalar", + "openSettingsToReview": "İncelemek için Ayarları açın", + "profileSelection": "Profil seçimi" }, "partRenderer": { "updatedFileCount_one": "{{displayCount}} dosya güncellendi.", @@ -3224,5 +3250,180 @@ "multiplierError": "1 veya daha büyük bir çarpan gir", "permissionDenied": "Harcama uyarılarını yönetme iznin yok.", "pushOffByCategory": "Bildirimler'de kapalı" + }, + "profiles": { + "title": "Profiller", + "entrySubtitle": "Oturumlar arasında ortam ayarlarını yeniden kullanma", + "newProfile": "Yeni profil", + "emptyTitle": "Henüz profil yok", + "emptyDescription": "Oturumlar arasında ortam değişkenlerini, kurulum komutlarını ve becerileri yeniden kullanmak için bir profil oluşturun.", + "loadFailed": "Profiller yüklenemedi", + "counts": { + "vars": "{{count}} değişken", + "mcp": "{{count}} MCP", + "skills": "{{count}} yetenek", + "commands": "{{count}} komut", + "varsShort": "{{count}}d", + "skillsShort": "{{count}}y", + "commandsShort": "{{count}}k" + }, + "nameLabel": "Profil adı", + "namePlaceholder": "örneğin Arka uç hata ayıklaması", + "descriptionLabel": "Profil açıklaması", + "descriptionPlaceholder": "İsteğe bağlı açıklama", + "ownerLabel": "Profil sahibi", + "nameRequired": "Bir profil adı girin", + "createAction": "Profil oluştur", + "createFailed": "Profil oluşturulamadı", + "createdToast": "\"{{name}}\" profili oluşturuldu", + "settingsSectionTitle": "Yapılandırma", + "defaultSectionTitle": "Varsayılan profil", + "personalDefaultDescription": "Hiçbir deponun sabitlenmiş profili olmadığında otomatik olarak yüklenir.", + "organizationDefaultDescription": "Kişisel varsayılanı olmayan üyeler için otomatik olarak yüklenir.", + "setAsDefault": "Varsayılan olarak ayarla", + "removeDefault": "Varsayılan olarak kaldır", + "saveFailed": "Profil kaydedilemedi", + "deleteTitle": "Profili sil", + "deleteMessage": "Bu, profili ve ayarlarını kalıcı olarak kaldırır. Bu geri alınamaz.", + "deleteFailed": "Profil silinemedi", + "deleteBlocked": "Bu profil bir web kancası tetikleyicisi tarafından kullanılır. Önce onu bu tetikleyicilerden kaldırın.", + "deletedToast": "\"{{name}}\" profili silindi", + "variablesTitle": "Ortam değişkenleri", + "addVariable": "Değişken ekle", + "keyLabel": "Anahtar", + "keyTooLong": "Anahtar en fazla 256 karakter olmalıdır", + "keyDuplicate": "Bu anahtar profilde zaten var", + "valueLabel": "Değer", + "secretLabel": "Gizli", + "secrets": { + "markSecret": "Gizli olarak işaretle", + "masked": "Gizli değer", + "reveal": "Değeri ortaya çıkarın", + "newValuePlaceholder": "Yeni gizli değer girin", + "valuePlaceholder": "Gizli değer (şifrelenmiş)" + }, + "variablesEmpty": "Henüz değişken yok", + "variablesSaveFailed": "Değişken kaydedilemedi", + "commandsTitle": "Kurulum komutları", + "addCommand": "Komut ekle", + "commandLabel": "Emretmek", + "commandPlaceholder": "e.g. pnpm install", + "commandsEmpty": "Henüz kurulum komutu yok", + "commandsSaveFailed": "Kurulum komutları kaydedilemedi", + "moveUp": "Yukarı taşı", + "moveDown": "Aşağı taşı", + "skillsTitle": "Yetenekler", + "addSkill": "Beceri ekle", + "skillNameLabel": "Beceri adı", + "skillMarkdownLabel": "Beceri içeriği", + "skillsEmpty": "Henüz beceri yok", + "skillNameRequired": "Bir beceri adı girin", + "skillNameInvalid": "Beceri adı bir harf veya rakamla başlamalı ve yalnızca küçük harfler, rakamlar ve kısa çizgiler kullanmalıdır", + "skillContentRequired": "Beceri içeriği gerekli", + "skillSaveFailed": "Beceri kaydedilemedi", + "skillsHint": "Bir SKILL.md yapıştırın ve adı doldurun; Sürükleyip bırakmadan telefonunuzdan bir beceri ekleyin.", + "skillEditTitle": "Beceriyi düzenle", + "mcp": { + "title": "MCP sunucuları", + "add": "MCP sunucusu ekle", + "empty": "Henüz MCP sunucusu yok", + "name": "Sunucu adı", + "type": "Sunucu türü", + "localType": "Yerel", + "remoteType": "Uzak", + "command": "Komut satırı", + "url": "URL'si", + "environment": "Çevre (JSON)", + "headers": "Başlıklar (JSON)", + "timeout": "Zaman aşımı", + "secretCount": "Gizli sayım", + "namePlaceholder": "my-server", + "commandPlaceholder": "npx @example/mcp-server", + "jsonPlaceholder": "{\n \"API_KEY\": \"sk-...\"\n}", + "nameRequired": "Bir sunucu adı girin", + "nameInvalid": "Adda yalnızca harf, rakam, alt çizgi ve kısa çizgi kullanılabilir", + "commandRequired": "Bir komut girin", + "commandTooLong": "En fazla 500 karakterlik 50 argüman kullanın", + "urlRequired": "Bir URL girin", + "urlInvalid": "Geçerli bir URL girin", + "urlTooLong": "URL en fazla 2048 karakter olmalıdır", + "jsonInvalid": "Geçerli JSON girin — dize değerlerinden oluşan bir nesne", + "jsonTooLarge": "En fazla 50 giriş, 128'e kadar anahtar ve 4096 karaktere kadar değer kullanın", + "timeoutInvalid": "Zaman aşımı tam sayı milisaniye olmalıdır", + "edit": "MCP sunucusunu düzenle", + "saveFailed": "MCP sunucusu kaydedilemedi" + }, + "agents": { + "title": "Profil aracıları", + "add": "Temsilci ekle", + "empty": "Henüz temsilci yok", + "name": "Temsilci adı", + "slug": "Ajan sümüklüböcek", + "description": "Temsilci açıklaması", + "model": "Modeli geçersiz kılma", + "modelPlaceholder": "Oturum varsayılanını kullan", + "prompt": "Sistem istemi", + "steps": "Maks. adım", + "stepsPlaceholder": "örn. 50", + "temperature": "Sıcaklık", + "temperaturePlaceholder": "örn. 0.2", + "topP": "top_p", + "topPPlaceholder": "örn. 0.95", + "slugPlaceholder": "my-agent", + "namePlaceholder": "Temsilcim", + "promptPlaceholder": "Sen bir...", + "tools": "Aletler", + "visibility": "Görünürlük", + "visibilityPrimary": "Birincil — seçicide gösterilir", + "visibilitySubagent": "Alt temsilci — yalnızca başkaları tarafından aranabilir", + "visibilityAll": "Hepsi - ikisi de", + "saveFailed": "Temsilci kaydedilemedi", + "nameRequired": "Bir temsilci adı girin", + "slugRequired": "Bir temsilci bilgisi girin", + "slugInvalid": "Bilgi bir harfle başlamalı ve yalnızca küçük harfler, rakamlar ve kısa çizgiler kullanmalıdır", + "slugConflict": "Bu bilgi yerleşik bir aracı tarafından ayrılmıştır", + "edit": "Temsilciyi düzenle" + }, + "slashCommands": { + "title": "Eğik çizgi komutları", + "add": "Eğik çizgi komutu ekle", + "empty": "Henüz eğik çizgi komutu yok", + "name": "Komut adı", + "description": "Komut açıklaması", + "template": "Şablon", + "namePlaceholder": "explain", + "templatePlaceholder": "$ARGUMENTS'ı açıklayın", + "agent": "Ajan", + "subtask": "Alt görev olarak çalıştır", + "edit": "Eğik çizgi komutunu düzenle", + "saveFailed": "Komut kaydedilemedi", + "nameRequired": "Bir komut adı girin", + "nameInvalid": "Ad küçük harfle başlamalı ve yalnızca küçük harfler, rakamlar ve kısa çizgiler kullanmalıdır", + "templateRequired": "Bir komut şablonu girin", + "nameConflict": "Bu ad yerleşik bir komut tarafından ayrılmıştır" + }, + "repoBindings": { + "title": "Depolar için Varsayılan Profiller", + "add": "Varsayılan ekle", + "empty": "Varsayılan ayar yapılandırılmadı", + "emptyDescription": "Bir depo için oturum başlatırken bir profili otomatik olarak uygulamak için bir varsayılan ekleyin.", + "repo": "Depoyu seçin…", + "profile": "Profil seç", + "remove": "Bağlantıyı kaldır", + "private": "Özel", + "search": "Depolarda ara…", + "addedToast": "\"{{repo}}\" için varsayılan profil ayarlandı", + "addFailed": "Varsayılan profil ayarlanamadı", + "removedToast": "\"{{repo}}\" için varsayılan profil kaldırıldı", + "removeFailed": "Varsayılan profil kaldırılamadı", + "pinnedTitle": "Depolara sabitlendi", + "pinRepo": "Bir repoyu sabitle", + "pinnedEmpty": "Herhangi bir depoya sabitlenmedi.", + "pinnedDescription": "Bir oturum bu depolardan birini hedeflediğinde otomatik olarak yüklenir.", + "pinnedToast": "\"{{repo}}\" öğesine sabitlendi", + "pinFailed": "Repo sabitlenemedi", + "unboundToast": "Repo ilişkisiz", + "unbindFailed": "Deponun bağlantısı kaldırılamadı" + } } } diff --git a/apps/mobile/src/i18n/locales/uk.json b/apps/mobile/src/i18n/locales/uk.json index b7bc5d94a56..c635d365133 100644 --- a/apps/mobile/src/i18n/locales/uk.json +++ b/apps/mobile/src/i18n/locales/uk.json @@ -1711,7 +1711,33 @@ "continueFrom": "Продовжується сеанс «{{title}}».", "importingSession": "Імпортування сеансу", "cliCannotContinue": "Цей CLI не підтримує продовження сеансів. Виберіть Cloud Agent або інший CLI.", - "importFailed": "Не вдалося імпортувати цей сеанс." + "importFailed": "Не вдалося імпортувати цей сеанс.", + "pickProfile": "Виберіть профіль", + "noProfile": "Без профілю", + "manageProfiles": "Керувати профілями", + "noProfiles": "Ще немає профілів. Створіть його, щоб додати змінні середовища, сервери MCP і навички.", + "advancedConfig": "Розширена конфігурація", + "advancedConfigDescription": "Виберіть профіль для завантаження збережених конфігурацій і вручну налаштуйте додаткові змінні середовища та команди налаштування", + "saveAsProfile": "Зберегти як профіль", + "saveProfileDescription": "Збережіть поточні змінні середовища та команди налаштування як багаторазовий профіль.", + "profileSummary": "{{vars}} змінні середовища · {{commands}} команди налаштування", + "profileSaved": "Профіль \"{{name}}\" збережено", + "organizationProfiles": "Профілі організації", + "personalProfiles": "Персональні профілі", + "yourProfiles": "Ваші профілі", + "failedToLoadProfiles": "Не вдалося завантажити профілі", + "nameTooLong": "Назва профілю задовга", + "descriptionTooLong": "Опис задовгий", + "secretsEncrypted": "Секрети шифруються перед зберіганням.", + "profileActive": "Профіль активний", + "profilesActive": "Профілі активні", + "customConfigActive": "Спеціальна конфігурація активна", + "configNeedsAttention": "Конфігурація потребує уваги", + "repoProfile": "Профіль репо", + "selectedProfile": "Вибраний профіль", + "manualOverrides": "Ручні перевизначення", + "openSettingsToReview": "Щоб переглянути, відкрийте налаштування", + "profileSelection": "Вибір профілю" }, "partRenderer": { "updatedFileCount_one": "Оновлено {{displayCount}} файл.", @@ -3310,5 +3336,180 @@ "multiplierError": "Введіть множник 1 або більше", "permissionDenied": "У вас немає дозволу керувати сповіщеннями про витрати.", "pushOffByCategory": "Вимкнено у сповіщеннях" + }, + "profiles": { + "title": "Профілі", + "entrySubtitle": "Повторне використання налаштувань середовища протягом сеансів", + "newProfile": "Новий профіль", + "emptyTitle": "Ще немає профілів", + "emptyDescription": "Створіть профіль, щоб повторно використовувати змінні середовища, команди налаштування та навички під час сеансів.", + "loadFailed": "Не вдалося завантажити профілі", + "counts": { + "vars": "{{count}} змінних", + "mcp": "{{count}} MCP", + "skills": "{{count}} навичок", + "commands": "{{count}} команд", + "varsShort": "{{count}}зм", + "skillsShort": "{{count}}нав", + "commandsShort": "{{count}}ком" + }, + "nameLabel": "Ім'я профілю", + "namePlaceholder": "напр. Бекенд налагодження", + "descriptionLabel": "Опис профілю", + "descriptionPlaceholder": "Додатковий опис", + "ownerLabel": "Власник профілю", + "nameRequired": "Введіть назву профілю", + "createAction": "Створити профіль", + "createFailed": "Не вдалося створити профіль", + "createdToast": "Профіль \"{{name}}\" створено", + "settingsSectionTitle": "Конфігурація", + "defaultSectionTitle": "Профіль за замовчуванням", + "personalDefaultDescription": "Автоматично завантажується, якщо жодне сховище не має закріпленого профілю.", + "organizationDefaultDescription": "Автоматично завантажується для учасників, які не мають особистого значення за умовчанням.", + "setAsDefault": "Встановити за умовчанням", + "removeDefault": "Видалити за умовчанням", + "saveFailed": "Не вдалося зберегти профіль", + "deleteTitle": "Видалити профіль", + "deleteMessage": "Це назавжди видаляє профіль і його налаштування. Це неможливо скасувати.", + "deleteFailed": "Не вдалося видалити профіль", + "deleteBlocked": "Цей профіль використовується тригером webhook. Спочатку видаліть його з цих тригерів.", + "deletedToast": "Профіль \"{{name}}\" видалено", + "variablesTitle": "Змінні середовища", + "addVariable": "Додайте змінну", + "keyLabel": "ключ", + "keyTooLong": "Ключ має містити не більше 256 символів", + "keyDuplicate": "Цей ключ уже існує в профілі", + "valueLabel": "Значення", + "secretLabel": "Секрет", + "secrets": { + "markSecret": "Позначити як секрет", + "masked": "Прихована цінність", + "reveal": "Розкрийте значення", + "newValuePlaceholder": "Введіть нове секретне значення", + "valuePlaceholder": "Секретне значення (зашифровано)" + }, + "variablesEmpty": "Ще немає змінних", + "variablesSaveFailed": "Не вдалося зберегти змінну", + "commandsTitle": "Команди налаштування", + "addCommand": "Додати команду", + "commandLabel": "Команда", + "commandPlaceholder": "e.g. pnpm install", + "commandsEmpty": "Ще немає команд налаштування", + "commandsSaveFailed": "Не вдалося зберегти команди налаштування", + "moveUp": "Рухатися вгору", + "moveDown": "Рухатися вниз", + "skillsTitle": "Навички", + "addSkill": "Додайте навички", + "skillNameLabel": "Назва навички", + "skillMarkdownLabel": "Зміст навичок", + "skillsEmpty": "Ще немає навичок", + "skillNameRequired": "Введіть назву навички", + "skillNameInvalid": "Назва навички має починатися з літери або цифри та використовувати лише малі літери, цифри та тире", + "skillContentRequired": "Зміст навичок є обов’язковим", + "skillSaveFailed": "Не вдалося зберегти навички", + "skillsHint": "Вставте SKILL.md і заповніть його назву; додайте навички зі свого телефону без перетягування.", + "skillEditTitle": "Редагувати вміння", + "mcp": { + "title": "MCP сервери", + "add": "Додайте сервер MCP", + "empty": "Серверів MCP ще немає", + "name": "Ім'я сервера", + "type": "Тип сервера", + "localType": "Місцевий", + "remoteType": "Дистанційний", + "command": "Командний рядок", + "url": "URL", + "environment": "Середовище (JSON)", + "headers": "Заголовки (JSON)", + "timeout": "Час очікування", + "secretCount": "Таємний відлік", + "namePlaceholder": "my-server", + "commandPlaceholder": "npx @example/mcp-server", + "jsonPlaceholder": "{\n \"API_KEY\": \"sk-...\"\n}", + "nameRequired": "Введіть назву сервера", + "nameInvalid": "В назві можна використовувати лише літери, цифри, знаки підкреслення та тире", + "commandRequired": "Введіть команду", + "commandTooLong": "Використовуйте не більше 50 аргументів по 500 символів кожен", + "urlRequired": "Введіть URL", + "urlInvalid": "Введіть дійсну URL-адресу", + "urlTooLong": "URL має містити не більше 2048 символів", + "jsonInvalid": "Введіть дійсний JSON — об’єкт рядкових значень", + "jsonTooLarge": "Використовуйте не більше 50 записів, ключі до 128 і значення до 4096 символів", + "timeoutInvalid": "Час очікування має становити цілу кількість мілісекунд", + "edit": "Редагувати сервер MCP", + "saveFailed": "Не вдалося зберегти сервер MCP" + }, + "agents": { + "title": "Профільні агенти", + "add": "Додати агента", + "empty": "Агентів ще немає", + "name": "Ім'я агента", + "slug": "Агент слизень", + "description": "Опис агента", + "model": "Перевизначення моделі", + "modelPlaceholder": "Використовувати сеанс за умовчанням", + "prompt": "Системна підказка", + "steps": "Макс. кроків", + "stepsPlaceholder": "напр. 50", + "temperature": "Температура", + "temperaturePlaceholder": "напр. 0.2", + "topP": "top_p", + "topPPlaceholder": "напр. 0.95", + "slugPlaceholder": "my-agent", + "namePlaceholder": "Мій агент", + "promptPlaceholder": "Ви...", + "tools": "Інструменти", + "visibility": "Видимість", + "visibilityPrimary": "Основний — відображається в панелі вибору", + "visibilitySubagent": "Субагент — тільки для виклику від інших", + "visibilityAll": "Всі — обидва", + "saveFailed": "Не вдалося зберегти агента", + "nameRequired": "Введіть ім'я агента", + "slugRequired": "Введіть слизь агента", + "slugInvalid": "Слизень має починатися з літери та використовувати лише малі літери, цифри та тире", + "slugConflict": "Цей слаг зарезервований вбудованим агентом", + "edit": "Агент редагування" + }, + "slashCommands": { + "title": "Команди косої риски", + "add": "Додати команду косої риски", + "empty": "Ще немає команд косою рискою", + "name": "Назва команди", + "description": "Опис команди", + "template": "Шаблон", + "namePlaceholder": "explain", + "templatePlaceholder": "Поясніть $ARGUMENTS", + "agent": "Агент", + "subtask": "Запуск як підзавдання", + "edit": "Редагувати команду косої риски", + "saveFailed": "Не вдалося зберегти команду", + "nameRequired": "Введіть назву команди", + "nameInvalid": "Ім’я має починатися з малої літери та використовувати лише малі літери, цифри та тире", + "templateRequired": "Введіть шаблон команди", + "nameConflict": "Це ім’я зарезервовано вбудованою командою" + }, + "repoBindings": { + "title": "Профілі за замовчуванням для репозиторіїв", + "add": "Додати за умовчанням", + "empty": "Немає параметрів за замовчуванням", + "emptyDescription": "Додайте параметр за замовчуванням для автоматичного застосування профілю під час запуску сеансів для репо.", + "repo": "Виберіть сховище…", + "profile": "Виберіть профіль", + "remove": "Розв'язати", + "private": "Приватний", + "search": "Пошук у сховищах…", + "addedToast": "Для \"{{repo}}\" встановлено профіль за умовчанням", + "addFailed": "Не вдалося встановити профіль за умовчанням", + "removedToast": "Профіль за умовчанням видалено для \"{{repo}}\"", + "removeFailed": "Не вдалося видалити профіль за умовчанням", + "pinnedTitle": "Закріплено в сховищах", + "pinRepo": "Закріпити репо", + "pinnedEmpty": "Не закріплено в жодному сховищі.", + "pinnedDescription": "Автоматично завантажується щоразу, коли сеанс націлений на одне з цих сховищ.", + "pinnedToast": "Закріплено на \"{{repo}}\"", + "pinFailed": "Не вдалося закріпити репо", + "unboundToast": "Репо без зв'язку", + "unbindFailed": "Не вдалося скасувати прив’язку репо" + } } } diff --git a/apps/mobile/src/i18n/locales/ur.json b/apps/mobile/src/i18n/locales/ur.json index d18156f953f..65920246bc3 100644 --- a/apps/mobile/src/i18n/locales/ur.json +++ b/apps/mobile/src/i18n/locales/ur.json @@ -2191,7 +2191,33 @@ "continueFrom": "{{title}} جاری رکھا جا رہا ہے۔", "importingSession": "سیشن درآمد ہو رہا ہے", "cliCannotContinue": "یہ CLI سیشن جاری نہیں رکھ سکتا۔ Cloud Agent یا کوئی اور CLI منتخب کریں۔", - "importFailed": "یہ سیشن درآمد نہیں ہو سکا۔" + "importFailed": "یہ سیشن درآمد نہیں ہو سکا۔", + "pickProfile": "ایک پروفائل منتخب کریں۔", + "noProfile": "کوئی پروفائل نہیں۔", + "manageProfiles": "پروفائلز کا نظم کریں۔", + "noProfiles": "ابھی تک کوئی پروفائلز نہیں ہیں۔ ماحولیاتی متغیرات، MCP سرورز، اور مہارتیں شامل کرنے کے لیے ایک بنائیں۔", + "advancedConfig": "اعلی درجے کی ترتیب", + "advancedConfigDescription": "محفوظ کردہ کنفیگریشنز کو لوڈ کرنے کے لیے پروفائل کا انتخاب کریں، اور اضافی ماحولیاتی متغیرات اور سیٹ اپ کمانڈز کو دستی طور پر ترتیب دیں۔", + "saveAsProfile": "بطور پروفائل محفوظ کریں۔", + "saveProfileDescription": "موجودہ ماحولیاتی متغیرات اور سیٹ اپ کمانڈز کو دوبارہ قابل استعمال پروفائل کے طور پر محفوظ کریں۔", + "profileSummary": "{{vars}} ماحولیاتی متغیرات · {{commands}} سیٹ اپ کمانڈز", + "profileSaved": "پروفائل \"{{name}}\" محفوظ ہو گیا۔", + "organizationProfiles": "تنظیمی پروفائلز", + "personalProfiles": "ذاتی پروفائلز", + "yourProfiles": "آپ کے پروفائلز", + "failedToLoadProfiles": "پروفائلز لوڈ کرنے میں ناکام", + "nameTooLong": "پروفائل کا نام بہت لمبا ہے۔", + "descriptionTooLong": "تفصیل بہت لمبی ہے۔", + "secretsEncrypted": "رازوں کو ذخیرہ کرنے سے پہلے خفیہ کیا جاتا ہے۔", + "profileActive": "پروفائل فعال ہے۔", + "profilesActive": "پروفائلز فعال ہیں۔", + "customConfigActive": "حسب ضرورت تشکیل فعال", + "configNeedsAttention": "کنفیگریشن کو توجہ کی ضرورت ہے۔", + "repoProfile": "ریپو پروفائل", + "selectedProfile": "منتخب پروفائل", + "manualOverrides": "دستی اوور رائیڈز", + "openSettingsToReview": "جائزہ لینے کے لیے ترتیبات کھولیں۔", + "profileSelection": "پروفائل کا انتخاب" }, "partRenderer": { "updatedFileCount_one": "{{displayCount}} فائل اپ ڈیٹ ہو گئی ہے۔", @@ -3224,5 +3250,180 @@ "multiplierError": "1 یا اس سے زیادہ کا ضرب درج کریں", "permissionDenied": "آپ کو اخراجات کی تنبیہات کا انتظام کرنے کی اجازت نہیں ہے۔", "pushOffByCategory": "اطلاعات میں بند" + }, + "profiles": { + "title": "پروفائلز", + "entrySubtitle": "سیشنوں میں ماحول کی ترتیبات کو دوبارہ استعمال کریں۔", + "newProfile": "نیا پروفائل", + "emptyTitle": "ابھی تک کوئی پروفائلز نہیں ہیں۔", + "emptyDescription": "سیشنوں میں ماحولیاتی متغیرات، سیٹ اپ کمانڈز، اور مہارتوں کو دوبارہ استعمال کرنے کے لیے ایک پروفائل بنائیں۔", + "loadFailed": "پروفائلز لوڈ نہیں ہو سکے۔", + "counts": { + "vars": "{{count}} متغیرات", + "mcp": "{{count}} MCP", + "skills": "{{count}} ہنر", + "commands": "{{count}} کمانڈز", + "varsShort": "{{count}}م", + "skillsShort": "{{count}}ہ", + "commandsShort": "{{count}}ک" + }, + "nameLabel": "پروفائل کا نام", + "namePlaceholder": "جیسے بیک اینڈ ڈیبگنگ", + "descriptionLabel": "پروفائل کی تفصیل", + "descriptionPlaceholder": "اختیاری تفصیل", + "ownerLabel": "پروفائل کا مالک", + "nameRequired": "پروفائل کا نام درج کریں۔", + "createAction": "پروفائل بنائیں", + "createFailed": "پروفائل نہیں بنایا جا سکا", + "createdToast": "پروفائل \"{{name}}\" بنایا گیا۔", + "settingsSectionTitle": "کنفیگریشن", + "defaultSectionTitle": "ڈیفالٹ پروفائل", + "personalDefaultDescription": "جب کسی ریپوزٹری میں پن شدہ پروفائل نہ ہو تو خودکار طور پر لوڈ ہوتا ہے۔", + "organizationDefaultDescription": "ان اراکین کے لیے خودکار طور پر لوڈ کیا گیا جن کا کوئی ذاتی ڈیفالٹ نہیں ہے۔", + "setAsDefault": "بطور ڈیفالٹ سیٹ کریں۔", + "removeDefault": "بطور ڈیفالٹ ہٹا دیں۔", + "saveFailed": "پروفائل محفوظ نہیں ہو سکا", + "deleteTitle": "پروفائل حذف کریں۔", + "deleteMessage": "یہ پروفائل اور اس کی ترتیبات کو مستقل طور پر ہٹا دیتا ہے۔ اسے کالعدم نہیں کیا جا سکتا۔", + "deleteFailed": "پروفائل کو حذف نہیں کیا جا سکا", + "deleteBlocked": "یہ پروفائل ویب ہک ٹرگر کے ذریعہ استعمال کیا جاتا ہے۔ پہلے اسے ان محرکات سے ہٹا دیں۔", + "deletedToast": "پروفائل \"{{name}}\" حذف کر دیا گیا۔", + "variablesTitle": "ماحولیاتی متغیرات", + "addVariable": "متغیر شامل کریں۔", + "keyLabel": "چابی", + "keyTooLong": "چابی 256 حروف یا اس سے کم ہونی چاہیے", + "keyDuplicate": "یہ چابی پہلے سے پروفائل میں موجود ہے", + "valueLabel": "قدر", + "secretLabel": "خفیہ", + "secrets": { + "markSecret": "خفیہ کے طور پر نشان زد کریں۔", + "masked": "پوشیدہ قدر", + "reveal": "قدر ظاہر کریں۔", + "newValuePlaceholder": "نیا خفیہ قدر درج کریں", + "valuePlaceholder": "خفیہ قدر (خفیہ کردہ)" + }, + "variablesEmpty": "ابھی تک کوئی متغیر نہیں ہے۔", + "variablesSaveFailed": "متغیر کو محفوظ نہیں کیا جا سکا", + "commandsTitle": "سیٹ اپ کمانڈز", + "addCommand": "کمانڈ شامل کریں۔", + "commandLabel": "حکم", + "commandPlaceholder": "e.g. pnpm install", + "commandsEmpty": "ابھی تک کوئی سیٹ اپ کمانڈ نہیں ہے۔", + "commandsSaveFailed": "سیٹ اپ کمانڈز کو محفوظ نہیں کیا جا سکا", + "moveUp": "اوپر بڑھیں۔", + "moveDown": "نیچے کی طرف بڑھیں۔", + "skillsTitle": "ہنر", + "addSkill": "مہارت شامل کریں۔", + "skillNameLabel": "ہنر کا نام", + "skillMarkdownLabel": "مہارت کا مواد", + "skillsEmpty": "ابھی تک کوئی ہنر نہیں ہے۔", + "skillNameRequired": "مہارت کا نام درج کریں۔", + "skillNameInvalid": "ہنر کا نام ایک حرف یا ہندسے سے شروع ہونا چاہیے اور صرف چھوٹے حروف، ہندسوں اور ڈیشز کا استعمال کریں", + "skillContentRequired": "مہارت کا مواد درکار ہے۔", + "skillSaveFailed": "مہارت نہیں بچا سکا", + "skillsHint": "ایک SKILL.md چسپاں کریں اور اس کا نام بھر جائے؛ ڈریگ اینڈ ڈراپ کے بغیر اپنے فون سے مہارت شامل کریں۔", + "skillEditTitle": "مہارت میں ترمیم کریں۔", + "mcp": { + "title": "MCP سرورز", + "add": "MCP سرور شامل کریں۔", + "empty": "ابھی تک کوئی MCP سرور نہیں ہے۔", + "name": "سرور کا نام", + "type": "سرور کی قسم", + "localType": "مقامی", + "remoteType": "ریموٹ", + "command": "کمانڈ لائن", + "url": "URL", + "environment": "ماحولیات (JSON)", + "headers": "ہیڈرز (JSON)", + "timeout": "ٹائم آؤٹ", + "secretCount": "خفیہ گنتی", + "namePlaceholder": "my-server", + "commandPlaceholder": "npx @example/mcp-server", + "jsonPlaceholder": "{\n \"API_KEY\": \"sk-...\"\n}", + "nameRequired": "سرور کا نام درج کریں۔", + "nameInvalid": "نام صرف حروف، اعداد، انڈر سکور اور ڈیشز کا استعمال کر سکتا ہے۔", + "commandRequired": "ایک کمانڈ درج کریں۔", + "commandTooLong": "زیادہ سے زیادہ 50 دلائل استعمال کریں، ہر ایک 500 حروف کا", + "urlRequired": "ایک URL درج کریں۔", + "urlInvalid": "ایک درست URL درج کریں۔", + "urlTooLong": "URL 2048 حروف یا اس سے کم ہونا چاہیے", + "jsonInvalid": "درست JSON درج کریں - سٹرنگ ویلیوز کا ایک آبجیکٹ", + "jsonTooLarge": "زیادہ سے زیادہ 50 اندراجات استعمال کریں، چابیاں 128 تک اور اقدار 4096 حروف تک", + "timeoutInvalid": "ٹائم آؤٹ ملی سیکنڈز کی پوری تعداد میں ہونا چاہیے۔", + "edit": "MCP سرور میں ترمیم کریں۔", + "saveFailed": "MCP سرور محفوظ نہیں ہو سکا" + }, + "agents": { + "title": "پروفائل ایجنٹس", + "add": "ایجنٹ شامل کریں۔", + "empty": "ابھی تک کوئی ایجنٹ نہیں ہے۔", + "name": "ایجنٹ کا نام", + "slug": "ایجنٹ سلگ", + "description": "ایجنٹ کی تفصیل", + "model": "ماڈل اوور رائڈ", + "modelPlaceholder": "سیشن ڈیفالٹ استعمال کریں۔", + "prompt": "سسٹم پرامپٹ", + "steps": "زیادہ سے زیادہ مراحل", + "stepsPlaceholder": "مثلاً 50", + "temperature": "درجہ حرارت", + "temperaturePlaceholder": "مثلاً 0.2", + "topP": "top_p", + "topPPlaceholder": "مثلاً 0.95", + "slugPlaceholder": "my-agent", + "namePlaceholder": "میرا ایجنٹ", + "promptPlaceholder": "آپ ایک...", + "tools": "اوزار", + "visibility": "مرئیت", + "visibilityPrimary": "پرائمری - چننے والے میں دکھایا گیا ہے۔", + "visibilitySubagent": "Subagent — صرف دوسروں سے کال کی جا سکتی ہے۔", + "visibilityAll": "سبھی — دونوں", + "saveFailed": "ایجنٹ کو محفوظ نہیں کیا جا سکا", + "nameRequired": "ایجنٹ کا نام درج کریں۔", + "slugRequired": "ایک ایجنٹ سلگ درج کریں۔", + "slugInvalid": "سلگ کا آغاز ایک حرف سے ہونا چاہیے اور صرف چھوٹے حروف، ہندسوں اور ڈیشز کا استعمال کرنا چاہیے۔", + "slugConflict": "وہ سلگ ایک بلٹ ان ایجنٹ کے ذریعہ محفوظ ہے۔", + "edit": "ایجنٹ میں ترمیم کریں۔" + }, + "slashCommands": { + "title": "سلیش کمانڈز", + "add": "سلیش کمانڈ شامل کریں۔", + "empty": "ابھی تک کوئی سلیش کمانڈ نہیں ہے۔", + "name": "کمانڈ کا نام", + "description": "کمانڈ کی تفصیل", + "template": "سانچہ", + "namePlaceholder": "explain", + "templatePlaceholder": "$ARGUMENTS کی وضاحت کریں۔", + "agent": "ایجنٹ", + "subtask": "ذیلی کام کے طور پر چلائیں۔", + "edit": "سلیش کمانڈ میں ترمیم کریں۔", + "saveFailed": "کمانڈ کو محفوظ نہیں کیا جا سکا", + "nameRequired": "کمانڈ کا نام درج کریں۔", + "nameInvalid": "نام کا آغاز چھوٹے حروف سے ہونا چاہیے اور صرف چھوٹے حروف، ہندسوں اور ڈیشز کا استعمال کرنا چاہیے۔", + "templateRequired": "کمانڈ ٹیمپلیٹ درج کریں۔", + "nameConflict": "یہ نام بلٹ ان کمانڈ کے ذریعہ محفوظ ہے۔" + }, + "repoBindings": { + "title": "Repos کے لیے پہلے سے طے شدہ پروفائلز", + "add": "ڈیفالٹ شامل کریں۔", + "empty": "کوئی ڈیفالٹ کنفیگر نہیں ہے۔", + "emptyDescription": "ریپو کے لیے سیشن شروع کرتے وقت پروفائل کو خود بخود لاگو کرنے کے لیے ڈیفالٹ شامل کریں۔", + "repo": "ذخیرہ منتخب کریں…", + "profile": "پروفائل منتخب کریں۔", + "remove": "بند باندھنا", + "private": "نجی", + "search": "ذخیرے تلاش کریں…", + "addedToast": "\"{{repo}}\" کے لیے ڈیفالٹ پروفائل سیٹ", + "addFailed": "ڈیفالٹ پروفائل سیٹ نہیں کیا جا سکا", + "removedToast": "\"{{repo}}\" کے لیے ڈیفالٹ پروفائل ہٹا دیا گیا", + "removeFailed": "ڈیفالٹ پروفائل کو ہٹایا نہیں جا سکا", + "pinnedTitle": "ریپوزٹریز میں پن کیا گیا۔", + "pinRepo": "ایک ریپو پن کریں۔", + "pinnedEmpty": "کسی بھی ذخیرے میں پن نہیں کیا گیا ہے۔", + "pinnedDescription": "جب بھی کوئی سیشن ان ذخیروں میں سے کسی کو نشانہ بناتا ہے تو خودکار طور پر لوڈ ہوتا ہے۔", + "pinnedToast": "\"{{repo}}\" پر پن کیا گیا", + "pinFailed": "ریپو کو پن نہیں کیا جا سکا", + "unboundToast": "ریپو انباؤنڈ", + "unbindFailed": "ریپو کو بند نہیں کیا جا سکا" + } } } diff --git a/apps/mobile/src/i18n/locales/uz.json b/apps/mobile/src/i18n/locales/uz.json index f258bdbb3f3..f024b4e9d8b 100644 --- a/apps/mobile/src/i18n/locales/uz.json +++ b/apps/mobile/src/i18n/locales/uz.json @@ -2191,7 +2191,33 @@ "continueFrom": "{{title}} sessiyasi davom ettirilmoqda.", "importingSession": "Sessiya import qilinmoqda", "cliCannotContinue": "Bu CLI sessiyani davom ettira olmaydi. Cloud Agent xizmatini yoki boshqa CLI dasturini tanlang.", - "importFailed": "Bu sessiyani import qilib bo'lmadi." + "importFailed": "Bu sessiyani import qilib bo'lmadi.", + "pickProfile": "Profilni tanlang", + "noProfile": "Profil yo'q", + "manageProfiles": "Profillarni boshqarish", + "noProfiles": "Hozircha profil yo'q. Atrof-muhit o'zgaruvchilari, MCP serverlari va ko'nikmalarini qo'shish uchun bitta yarating.", + "advancedConfig": "Kengaytirilgan konfiguratsiya", + "advancedConfigDescription": "Saqlangan konfiguratsiyalarni yuklash va qoʻshimcha muhit oʻzgaruvchilari va sozlash buyruqlarini qoʻlda sozlash uchun profilni tanlang", + "saveAsProfile": "Profil sifatida saqlash", + "saveProfileDescription": "Joriy muhit o'zgaruvchilari va sozlash buyruqlarini qayta foydalanish mumkin bo'lgan profil sifatida saqlang.", + "profileSummary": "{{vars}} muhit o'zgaruvchilari · {{commands}} sozlash buyruqlari", + "profileSaved": "“{{name}}” profili saqlandi", + "organizationProfiles": "Tashkilot profillari", + "personalProfiles": "Shaxsiy profillar", + "yourProfiles": "Profillaringiz", + "failedToLoadProfiles": "Profillarni yuklab bo‘lmadi", + "nameTooLong": "Profil nomi juda uzun", + "descriptionTooLong": "Tavsif juda uzun", + "secretsEncrypted": "Sirlar saqlashdan oldin shifrlangan.", + "profileActive": "Profil faol", + "profilesActive": "Profillar faol", + "customConfigActive": "Maxsus konfiguratsiya faol", + "configNeedsAttention": "Konfiguratsiyaga e'tibor kerak", + "repoProfile": "Repo profili", + "selectedProfile": "Tanlangan profil", + "manualOverrides": "Qo'lda bekor qilish", + "openSettingsToReview": "Ko‘rib chiqish uchun Sozlamalarni oching", + "profileSelection": "Profil tanlash" }, "partRenderer": { "updatedFileCount_one": "{{displayCount}} ta fayl yangilandi.", @@ -3224,5 +3250,180 @@ "multiplierError": "1 yoki undan katta koeffitsiyent kiriting", "permissionDenied": "Xarajat bildirishnomalarini boshqarishga ruxsatingiz yo'q.", "pushOffByCategory": "Bildirishnomalarda o'chirilgan" + }, + "profiles": { + "title": "Profillar", + "entrySubtitle": "Seanslar davomida muhit sozlamalaridan qayta foydalaning", + "newProfile": "Yangi profil", + "emptyTitle": "Hozircha profil yo'q", + "emptyDescription": "Seanslar davomida muhit o'zgaruvchilari, sozlash buyruqlari va ko'nikmalarni qayta ishlatish uchun profil yarating.", + "loadFailed": "Profillarni yuklab bo‘lmadi", + "counts": { + "vars": "{{count}} o'zgaruvchi", + "mcp": "{{count}} MCP", + "skills": "{{count}} ko'nikma", + "commands": "{{count}} buyruq", + "varsShort": "{{count}}o'", + "skillsShort": "{{count}}k", + "commandsShort": "{{count}}b" + }, + "nameLabel": "Profil nomi", + "namePlaceholder": "masalan. Backend disk raskadrovka", + "descriptionLabel": "Profil tavsifi", + "descriptionPlaceholder": "Ixtiyoriy tavsif", + "ownerLabel": "Profil egasi", + "nameRequired": "Profil nomini kiriting", + "createAction": "Profil yaratish", + "createFailed": "Profil yaratib bo‘lmadi", + "createdToast": "“{{name}}” profili yaratildi", + "settingsSectionTitle": "Konfiguratsiya", + "defaultSectionTitle": "Standart profil", + "personalDefaultDescription": "Hech bir omborda mahkamlangan profil bo'lmasa, avtomatik yuklanadi.", + "organizationDefaultDescription": "Shaxsiy standarti bo'lmagan a'zolar uchun avtomatik yuklanadi.", + "setAsDefault": "Standart sifatida o'rnating", + "removeDefault": "Standart sifatida olib tashlang", + "saveFailed": "Profilni saqlab bo‘lmadi", + "deleteTitle": "Profilni o'chirish", + "deleteMessage": "Bu profil va uning sozlamalarini butunlay olib tashlaydi. Buni ortga qaytarib bo‘lmaydi.", + "deleteFailed": "Profilni oʻchirib boʻlmadi", + "deleteBlocked": "Bu profil webhook trigger tomonidan ishlatiladi. Avval uni o'sha triggerlardan olib tashlang.", + "deletedToast": "“{{name}}” profili oʻchirib tashlandi", + "variablesTitle": "Atrof-muhit o'zgaruvchilari", + "addVariable": "O'zgaruvchini qo'shish", + "keyLabel": "Kalit", + "keyTooLong": "Kalit 256 ta belgi yoki undan kam bo'lishi kerak", + "keyDuplicate": "Bu kalit profilda allaqachon mavjud", + "valueLabel": "Qiymat", + "secretLabel": "Sir", + "secrets": { + "markSecret": "Sir sifatida belgilang", + "masked": "Yashirin qiymat", + "reveal": "Qiymatni oshkor qilish", + "newValuePlaceholder": "Yangi maxfiy qiymatni kiriting", + "valuePlaceholder": "Maxfiy qiymat (shifrlangan)" + }, + "variablesEmpty": "Hali oʻzgaruvchilar yoʻq", + "variablesSaveFailed": "Oʻzgaruvchini saqlab boʻlmadi", + "commandsTitle": "O'rnatish buyruqlari", + "addCommand": "Buyruq qo'shish", + "commandLabel": "Buyruq", + "commandPlaceholder": "e.g. pnpm install", + "commandsEmpty": "Hozircha sozlash buyruqlari yo'q", + "commandsSaveFailed": "Sozlash buyruqlarini saqlab bo‘lmadi", + "moveUp": "Yuqoriga siljiting", + "moveDown": "Pastga siljiting", + "skillsTitle": "Ko'nikmalar", + "addSkill": "Qobiliyat qo'shing", + "skillNameLabel": "Malaka nomi", + "skillMarkdownLabel": "Ko'nikma tarkibi", + "skillsEmpty": "Hali mahorat yo'q", + "skillNameRequired": "Qobiliyat nomini kiriting", + "skillNameInvalid": "Malaka nomi harf yoki raqam bilan boshlanishi va faqat kichik harflar, raqamlar va tirelardan foydalanishi kerak", + "skillContentRequired": "Ko'nikma tarkibi talab qilinadi", + "skillSaveFailed": "Mahorat saqlanmadi", + "skillsHint": "SKILL.md-ni joylashtiring va uning nomi kiritiladi; sudrab tashlamasdan telefoningizdan mahorat qo'shing.", + "skillEditTitle": "Tahrirlash mahorati", + "mcp": { + "title": "MCP serverlari", + "add": "MCP serverini qo'shing", + "empty": "MCP serverlari hali yo'q", + "name": "Server nomi", + "type": "Server turi", + "localType": "Mahalliy", + "remoteType": "Masofadan", + "command": "Buyruqlar qatori", + "url": "URL", + "environment": "Atrof-muhit (JSON)", + "headers": "Sarlavhalar (JSON)", + "timeout": "Taym-aut; turib qolish; tanaffus", + "secretCount": "Yashirin hisob", + "namePlaceholder": "my-server", + "commandPlaceholder": "npx @example/mcp-server", + "jsonPlaceholder": "{\n \"API_KEY\": \"sk-...\"\n}", + "nameRequired": "Server nomini kiriting", + "nameInvalid": "Ism faqat harflar, raqamlar, pastki chiziq va chiziqlardan foydalanishi mumkin", + "commandRequired": "Buyruqni kiriting", + "commandTooLong": "Ko'pi bilan 500 belgidan iborat 50 ta argumentdan foydalaning", + "urlRequired": "URL manzilini kiriting", + "urlInvalid": "Yaroqli URL manzilini kiriting", + "urlTooLong": "URL 2048 ta belgi yoki undan kam bo'lishi kerak", + "jsonInvalid": "To'g'ri JSON kiriting - satr qiymatlari ob'ekti", + "jsonTooLarge": "Ko'pi bilan 50 ta yozuv, kalitlar 128 tagacha va qiymatlar 4096 belgigacha foydalaning", + "timeoutInvalid": "Vaqt tugashi butun son millisekund bo'lishi kerak", + "edit": "MCP serverini tahrirlash", + "saveFailed": "MCP serverini saqlab boʻlmadi" + }, + "agents": { + "title": "Profil agentlari", + "add": "Agent qo'shing", + "empty": "Hali agentlar yo'q", + "name": "Agent nomi", + "slug": "Agent slug", + "description": "Agent tavsifi", + "model": "Modelni bekor qilish", + "modelPlaceholder": "Standart seansdan foydalaning", + "prompt": "Tizim so'rovi", + "steps": "Maks. qadam", + "stepsPlaceholder": "mas. 50", + "temperature": "Harorat", + "temperaturePlaceholder": "mas. 0.2", + "topP": "top_p", + "topPPlaceholder": "mas. 0.95", + "slugPlaceholder": "my-agent", + "namePlaceholder": "Mening agentim", + "promptPlaceholder": "Siz ...", + "tools": "Asboblar", + "visibility": "Ko'rinish", + "visibilityPrimary": "Asosiy - tanlovchida ko'rsatilgan", + "visibilitySubagent": "Subagent - faqat boshqalar tomonidan chaqirilishi mumkin", + "visibilityAll": "Hammasi - ikkalasi ham", + "saveFailed": "Agentni saqlab boʻlmadi", + "nameRequired": "Agent nomini kiriting", + "slugRequired": "Agent slug'ini kiriting", + "slugInvalid": "Slug harf bilan boshlanishi va faqat kichik harflar, raqamlar va tirelardan foydalanishi kerak", + "slugConflict": "Bu slug o'rnatilgan agent tomonidan zahiralangan", + "edit": "Agentni tahrirlash" + }, + "slashCommands": { + "title": "Slash buyruqlari", + "add": "Slash buyrug'ini qo'shing", + "empty": "Hali slash buyruqlari yo'q", + "name": "Buyruq nomi", + "description": "Buyruq tavsifi", + "template": "Shablon", + "namePlaceholder": "explain", + "templatePlaceholder": "$ARGUMENTS ni tushuntiring", + "agent": "Agent", + "subtask": "Quyi vazifa sifatida ishga tushirish", + "edit": "Slash buyrug'ini tahrirlash", + "saveFailed": "Buyruq saqlanmadi", + "nameRequired": "Buyruq nomini kiriting", + "nameInvalid": "Ism kichik harf bilan boshlanishi va faqat kichik harflar, raqamlar va tirelardan foydalanishi kerak", + "templateRequired": "Buyruq shablonini kiriting", + "nameConflict": "Bu nom o'rnatilgan buyruq bilan saqlangan" + }, + "repoBindings": { + "title": "Repos uchun standart profillar", + "add": "Standart qo‘shish", + "empty": "Hech qanday standart sozlanmagan", + "emptyDescription": "Repo uchun seanslarni boshlashda profilni avtomatik ravishda qo'llash uchun standartni qo'shing.", + "repo": "Repozitoriyni tanlang…", + "profile": "Profilni tanlang", + "remove": "Bog'lash", + "private": "Shaxsiy", + "search": "Repozitariylarni qidirish…", + "addedToast": "“{{repo}}” uchun birlamchi profil oʻrnatilgan", + "addFailed": "Standart profilni o‘rnatib bo‘lmadi", + "removedToast": "“{{repo}}” uchun birlamchi profil olib tashlandi", + "removeFailed": "Standart profilni olib tashlab bo‘lmadi", + "pinnedTitle": "Repozitariylarga mahkamlangan", + "pinRepo": "Reponi mahkamlang", + "pinnedEmpty": "Hech qanday omborga mahkamlanmagan.", + "pinnedDescription": "Seans ushbu omborlardan birini maqsad qilganda avtomatik yuklanadi.", + "pinnedToast": "“{{repo}}”ga mahkamlangan", + "pinFailed": "Reponi mahkamlab bo‘lmadi", + "unboundToast": "Repo cheklanmagan", + "unbindFailed": "Reponi ajratib boʻlmadi" + } } } diff --git a/apps/mobile/src/i18n/locales/vi.json b/apps/mobile/src/i18n/locales/vi.json index 4c97150c5ce..9d07761d617 100644 --- a/apps/mobile/src/i18n/locales/vi.json +++ b/apps/mobile/src/i18n/locales/vi.json @@ -1687,7 +1687,33 @@ "continueFrom": "Đang tiếp tục phiên {{title}}.", "importingSession": "Đang nhập phiên", "cliCannotContinue": "CLI này không hỗ trợ tiếp tục phiên. Hãy chọn Cloud Agent hoặc CLI khác.", - "importFailed": "Không thể nhập phiên này." + "importFailed": "Không thể nhập phiên này.", + "pickProfile": "Chọn một hồ sơ", + "noProfile": "Không có hồ sơ", + "manageProfiles": "Quản lý hồ sơ", + "noProfiles": "Chưa có hồ sơ nào. Tạo một cái để thêm các biến môi trường, máy chủ MCP và kỹ năng.", + "advancedConfig": "Cấu hình nâng cao", + "advancedConfigDescription": "Chọn một cấu hình để tải các cấu hình đã lưu và định cấu hình thủ công các biến môi trường và lệnh thiết lập bổ sung", + "saveAsProfile": "Lưu làm hồ sơ", + "saveProfileDescription": "Lưu các biến môi trường hiện tại và các lệnh thiết lập dưới dạng cấu hình có thể sử dụng lại.", + "profileSummary": "{{vars}} biến môi trường · {{commands}} lệnh thiết lập", + "profileSaved": "Đã lưu hồ sơ \"{{name}}\"", + "organizationProfiles": "Hồ sơ tổ chức", + "personalProfiles": "Hồ sơ cá nhân", + "yourProfiles": "Hồ sơ của bạn", + "failedToLoadProfiles": "Không tải được hồ sơ", + "nameTooLong": "Tên hồ sơ quá dài", + "descriptionTooLong": "Mô tả quá dài", + "secretsEncrypted": "Bí mật được mã hóa trước khi lưu trữ.", + "profileActive": "Hồ sơ đang hoạt động", + "profilesActive": "Hồ sơ đang hoạt động", + "customConfigActive": "Cấu hình tùy chỉnh đang hoạt động", + "configNeedsAttention": "Cấu hình cần chú ý", + "repoProfile": "Hồ sơ repo", + "selectedProfile": "Hồ sơ đã chọn", + "manualOverrides": "Ghi đè thủ công", + "openSettingsToReview": "Mở Cài đặt để xem lại", + "profileSelection": "Lựa chọn hồ sơ" }, "partRenderer": { "updatedFileCount_one": "Đã cập nhật {{displayCount}} tệp.", @@ -3224,5 +3250,180 @@ "multiplierError": "Nhập hệ số từ 1 trở lên", "permissionDenied": "Bạn không có quyền quản lý cảnh báo chi tiêu.", "pushOffByCategory": "Đã tắt trong Thông báo" + }, + "profiles": { + "title": "Hồ sơ", + "entrySubtitle": "Sử dụng lại cài đặt môi trường qua các phiên", + "newProfile": "Hồ sơ mới", + "emptyTitle": "Chưa có hồ sơ nào", + "emptyDescription": "Tạo hồ sơ để sử dụng lại các biến môi trường, lệnh thiết lập và kỹ năng trong các phiên.", + "loadFailed": "Không thể tải hồ sơ", + "counts": { + "vars": "{{count}} biến", + "mcp": "{{count}} MCP", + "skills": "{{count}} kỹ năng", + "commands": "{{count}} lệnh", + "varsShort": "{{count}}b", + "skillsShort": "{{count}}k", + "commandsShort": "{{count}}l" + }, + "nameLabel": "Tên hồ sơ", + "namePlaceholder": "ví dụ. Gỡ lỗi phụ trợ", + "descriptionLabel": "Mô tả hồ sơ", + "descriptionPlaceholder": "Mô tả tùy chọn", + "ownerLabel": "Chủ sở hữu hồ sơ", + "nameRequired": "Nhập tên hồ sơ", + "createAction": "Tạo hồ sơ", + "createFailed": "Không thể tạo hồ sơ", + "createdToast": "Đã tạo hồ sơ \"{{name}}\"", + "settingsSectionTitle": "Cấu hình", + "defaultSectionTitle": "Hồ sơ mặc định", + "personalDefaultDescription": "Tự động tải khi không có kho lưu trữ nào có hồ sơ được ghim.", + "organizationDefaultDescription": "Tự động tải cho các thành viên không có mặc định cá nhân.", + "setAsDefault": "Đặt làm mặc định", + "removeDefault": "Xóa làm mặc định", + "saveFailed": "Không thể lưu hồ sơ", + "deleteTitle": "Xóa hồ sơ", + "deleteMessage": "Thao tác này sẽ xóa vĩnh viễn hồ sơ và cài đặt của nó. Điều này không thể hoàn tác được.", + "deleteFailed": "Không thể xóa hồ sơ", + "deleteBlocked": "Cấu hình này được sử dụng bởi trình kích hoạt webhook. Trước tiên hãy loại bỏ nó khỏi những trình kích hoạt đó.", + "deletedToast": "Hồ sơ \"{{name}}\" đã bị xóa", + "variablesTitle": "Biến môi trường", + "addVariable": "Thêm biến", + "keyLabel": "Chìa khóa", + "keyTooLong": "Khóa phải có 256 ký tự hoặc ít hơn", + "keyDuplicate": "Khóa này đã tồn tại trong hồ sơ", + "valueLabel": "Giá trị", + "secretLabel": "Bí mật", + "secrets": { + "markSecret": "Đánh dấu là bí mật", + "masked": "Giá trị ẩn", + "reveal": "Tiết lộ giá trị", + "newValuePlaceholder": "Nhập giá trị bí mật mới", + "valuePlaceholder": "Giá trị bí mật (đã mã hóa)" + }, + "variablesEmpty": "Chưa có biến nào", + "variablesSaveFailed": "Không thể lưu biến", + "commandsTitle": "Lệnh thiết lập", + "addCommand": "Thêm lệnh", + "commandLabel": "Yêu cầu", + "commandPlaceholder": "e.g. pnpm install", + "commandsEmpty": "Chưa có lệnh thiết lập nào", + "commandsSaveFailed": "Không thể lưu lệnh thiết lập", + "moveUp": "Di chuyển lên", + "moveDown": "Di chuyển xuống", + "skillsTitle": "Kỹ năng", + "addSkill": "Thêm kỹ năng", + "skillNameLabel": "Tên kỹ năng", + "skillMarkdownLabel": "Nội dung kỹ năng", + "skillsEmpty": "Chưa có kỹ năng", + "skillNameRequired": "Nhập tên kỹ năng", + "skillNameInvalid": "Tên kỹ năng phải bắt đầu bằng một chữ cái hoặc chữ số và chỉ sử dụng chữ cái viết thường, chữ số và dấu gạch ngang", + "skillContentRequired": "Nội dung kỹ năng là bắt buộc", + "skillSaveFailed": "Không thể lưu kỹ năng", + "skillsHint": "Dán SKILL.md và điền tên của nó vào; thêm kỹ năng từ điện thoại của bạn mà không cần kéo và thả.", + "skillEditTitle": "Chỉnh sửa kỹ năng", + "mcp": { + "title": "máy chủ MCP", + "add": "Thêm máy chủ MCP", + "empty": "Chưa có máy chủ MCP", + "name": "Tên máy chủ", + "type": "Loại máy chủ", + "localType": "Địa phương", + "remoteType": "Xa", + "command": "Dòng lệnh", + "url": "URL", + "environment": "Môi trường (JSON)", + "headers": "Tiêu đề (JSON)", + "timeout": "Hết giờ", + "secretCount": "Số bí mật", + "namePlaceholder": "my-server", + "commandPlaceholder": "npx @example/mcp-server", + "jsonPlaceholder": "{\n \"API_KEY\": \"sk-...\"\n}", + "nameRequired": "Nhập tên máy chủ", + "nameInvalid": "Tên chỉ có thể sử dụng chữ cái, số, dấu gạch dưới và dấu gạch ngang", + "commandRequired": "Nhập lệnh", + "commandTooLong": "Dùng tối đa 50 đối số, mỗi đối số 500 ký tự", + "urlRequired": "Nhập một URL", + "urlInvalid": "Nhập một URL hợp lệ", + "urlTooLong": "URL phải có 2048 ký tự hoặc ít hơn", + "jsonInvalid": "Nhập JSON hợp lệ - một đối tượng có giá trị chuỗi", + "jsonTooLarge": "Dùng tối đa 50 mục, khóa tối đa 128 và giá trị tối đa 4096 ký tự", + "timeoutInvalid": "Thời gian chờ phải là số nguyên mili giây", + "edit": "Chỉnh sửa máy chủ MCP", + "saveFailed": "Không thể lưu máy chủ MCP" + }, + "agents": { + "title": "Hồ sơ đại lý", + "add": "Thêm đại lý", + "empty": "Chưa có đại lý", + "name": "Tên đại lý", + "slug": "sên đại lý", + "description": "Mô tả đại lý", + "model": "Ghi đè mô hình", + "modelPlaceholder": "Sử dụng mặc định phiên", + "prompt": "Lời nhắc hệ thống", + "steps": "Số bước tối đa", + "stepsPlaceholder": "ví dụ: 50", + "temperature": "Nhiệt độ", + "temperaturePlaceholder": "ví dụ: 0.2", + "topP": "top_p", + "topPPlaceholder": "ví dụ: 0.95", + "slugPlaceholder": "my-agent", + "namePlaceholder": "Đại lý của tôi", + "promptPlaceholder": "Bạn là một...", + "tools": "Công cụ", + "visibility": "Khả năng hiển thị", + "visibilityPrimary": "Chính — hiển thị trong bộ chọn", + "visibilitySubagent": "Subagent - chỉ có thể gọi được từ người khác", + "visibilityAll": "Tất cả - cả hai", + "saveFailed": "Không thể lưu đại lý", + "nameRequired": "Nhập tên đại lý", + "slugRequired": "Nhập sên đại lý", + "slugInvalid": "Slug phải bắt đầu bằng một chữ cái và chỉ sử dụng chữ cái viết thường, chữ số và dấu gạch ngang", + "slugConflict": "Con sên đó được bảo lưu bởi một tác nhân tích hợp", + "edit": "Chỉnh sửa đại lý" + }, + "slashCommands": { + "title": "Lệnh gạch chéo", + "add": "Thêm lệnh gạch chéo", + "empty": "Chưa có lệnh gạch chéo", + "name": "Tên lệnh", + "description": "Mô tả lệnh", + "template": "Bản mẫu", + "namePlaceholder": "explain", + "templatePlaceholder": "Giải thích $ARGUMENTS", + "agent": "Đại lý", + "subtask": "Chạy dưới dạng nhiệm vụ phụ", + "edit": "Chỉnh sửa lệnh gạch chéo", + "saveFailed": "Không thể lưu lệnh", + "nameRequired": "Nhập tên lệnh", + "nameInvalid": "Tên phải bắt đầu bằng chữ cái viết thường và chỉ sử dụng chữ cái viết thường, chữ số và dấu gạch ngang", + "templateRequired": "Nhập mẫu lệnh", + "nameConflict": "Tên đó được bảo lưu bởi một lệnh tích hợp" + }, + "repoBindings": { + "title": "Hồ sơ mặc định cho Repos", + "add": "Thêm mặc định", + "empty": "Không có cấu hình mặc định", + "emptyDescription": "Thêm mặc định để tự động áp dụng cấu hình khi bắt đầu phiên cho kho lưu trữ.", + "repo": "Chọn kho lưu trữ…", + "profile": "Chọn hồ sơ", + "remove": "Hủy liên kết", + "private": "Riêng tư", + "search": "Tìm kiếm kho lưu trữ…", + "addedToast": "Đã đặt cấu hình mặc định cho \"{{repo}}\"", + "addFailed": "Không thể đặt hồ sơ mặc định", + "removedToast": "Đã xóa hồ sơ mặc định cho \"{{repo}}\"", + "removeFailed": "Không thể xóa hồ sơ mặc định", + "pinnedTitle": "Đã ghim vào kho lưu trữ", + "pinRepo": "Ghim một kho lưu trữ", + "pinnedEmpty": "Không được ghim vào bất kỳ kho lưu trữ nào.", + "pinnedDescription": "Tự động tải bất cứ khi nào phiên nhắm mục tiêu vào một trong các kho lưu trữ này.", + "pinnedToast": "Đã ghim vào \"{{repo}}\"", + "pinFailed": "Không thể ghim kho lưu trữ", + "unboundToast": "Repo không bị ràng buộc", + "unbindFailed": "Không thể hủy liên kết repo" + } } } diff --git a/apps/mobile/src/i18n/locales/yo.json b/apps/mobile/src/i18n/locales/yo.json index 779243f4236..2a2a544d2c4 100644 --- a/apps/mobile/src/i18n/locales/yo.json +++ b/apps/mobile/src/i18n/locales/yo.json @@ -2191,7 +2191,33 @@ "continueFrom": "A ń tẹ̀síwájú pẹ̀lú {{title}}.", "importingSession": "Ń gbé ìgbà iṣẹ́ wọlé", "cliCannotContinue": "CLI yìí kò lè tẹ̀síwájú pẹ̀lú ìgbà iṣẹ́ kan. Yan Cloud Agent tàbí CLI mìíràn.", - "importFailed": "A kò lè gbé ìgbà iṣẹ́ yìí wọlé." + "importFailed": "A kò lè gbé ìgbà iṣẹ́ yìí wọlé.", + "pickProfile": "Yan profaili kan", + "noProfile": "Ko si profaili", + "manageProfiles": "Ṣakoso awọn profaili", + "noProfiles": "Ko si awọn profaili sibẹsibẹ. Ṣẹda ọkan lati ṣafikun awọn oniyipada ayika, awọn olupin MCP, ati awọn ọgbọn.", + "advancedConfig": "To ti ni ilọsiwaju iṣeto ni", + "advancedConfigDescription": "Yan profaili kan lati kojọpọ awọn atunto ti o fipamọ, ati pẹlu ọwọ tunto awọn oniyipada agbegbe afikun ati awọn pipaṣẹ iṣeto", + "saveAsProfile": "Fipamọ bi Profaili", + "saveProfileDescription": "Ṣafipamọ awọn oniyipada ayika lọwọlọwọ ati awọn pipaṣẹ iṣeto bi profaili atunlo.", + "profileSummary": "{{vars}} awọn oniyipada ayika · {{commands}} Awọn aṣẹ iṣeto", + "profileSaved": "Profaili \"{{name}}\" ti fipamọ", + "organizationProfiles": "Awọn profaili Ajo", + "personalProfiles": "Awọn profaili ti ara ẹni", + "yourProfiles": "Awọn profaili rẹ", + "failedToLoadProfiles": "Kuna lati kojọpọ awọn profaili", + "nameTooLong": "Orukọ profaili ti gun ju", + "descriptionTooLong": "Apejuwe ti gun ju", + "secretsEncrypted": "Awọn asiri ti wa ni ti paroko ṣaaju ibi ipamọ.", + "profileActive": "Profaili nṣiṣẹ", + "profilesActive": "Awọn profaili ti nṣiṣe lọwọ", + "customConfigActive": "Aṣa atunto ti nṣiṣe lọwọ", + "configNeedsAttention": "Config nilo akiyesi", + "repoProfile": "Profaili Repo", + "selectedProfile": "Profaili ti o yan", + "manualOverrides": "Afowoyi danu", + "openSettingsToReview": "Ṣii Eto lati ṣe ayẹwo", + "profileSelection": "Aṣayan profaili" }, "partRenderer": { "updatedFileCount_one": "A ti ṣe ìmúdójúìwọ̀n fáìlì {{displayCount}}.", @@ -3224,5 +3250,180 @@ "multiplierError": "Tẹ ìlọ́po 1 tàbí jù bẹ́ẹ̀ lọ", "permissionDenied": "O kò ní ìyọ̀ǹda láti ṣàkóso àwọn ìkìlọ̀ ìnáwó.", "pushOffByCategory": "Ti pa nínú Àwọn ìfitónilétí" + }, + "profiles": { + "title": "Awọn profaili", + "entrySubtitle": "Tun lo awọn eto ayika kọja awọn akoko", + "newProfile": "Profaili tuntun", + "emptyTitle": "Ko si awọn profaili sibẹsibẹ", + "emptyDescription": "Ṣẹda profaili kan lati tun lo awọn oniyipada ayika, awọn pipaṣẹ iṣeto, ati awọn ọgbọn kọja awọn akoko.", + "loadFailed": "Ko le kojọpọ awọn profaili", + "counts": { + "vars": "{{count}} oniyipada", + "mcp": "{{count}} MCP", + "skills": "{{count}} ogbon", + "commands": "{{count}} aṣẹ", + "varsShort": "{{count}}oni", + "skillsShort": "{{count}}ogb", + "commandsShort": "{{count}}a" + }, + "nameLabel": "Orukọ profaili", + "namePlaceholder": "f.eks. N ṣatunṣe aṣiṣe afẹyinti", + "descriptionLabel": "Apejuwe profaili", + "descriptionPlaceholder": "Iyan apejuwe", + "ownerLabel": "Ololufe profaili", + "nameRequired": "Tẹ orukọ profaili sii", + "createAction": "Ṣẹda profaili", + "createFailed": "Ko le ṣẹda profaili", + "createdToast": "Profaili \"{{name}}\" ti ṣẹda", + "settingsSectionTitle": "Iṣeto ni", + "defaultSectionTitle": "profaili aiyipada", + "personalDefaultDescription": "Ti kojọpọ laifọwọyi nigbati ko si ibi ipamọ ti o ni profaili pinni.", + "organizationDefaultDescription": "Ti kojọpọ aifọwọyi fun awọn ọmọ ẹgbẹ ti ko ni aiyipada ti ara ẹni.", + "setAsDefault": "Ṣeto bi aiyipada", + "removeDefault": "Yọ kuro bi aiyipada", + "saveFailed": "Ko le fi profaili pamọ", + "deleteTitle": "Pa profaili rẹ", + "deleteMessage": "Eyi yoo yọ profaili ati awọn eto rẹ kuro patapata. Eyi ko le ṣe atunṣe.", + "deleteFailed": "Ko le pa profaili rẹ", + "deleteBlocked": "Profaili yii jẹ lilo nipasẹ fifa wẹẹbu kan. Yọ kuro lati awọn okunfa wọnyẹn ni akọkọ.", + "deletedToast": "Profaili \"{{name}}\" ti paarẹ", + "variablesTitle": "Awọn oniyipada ayika", + "addVariable": "Fi oniyipada kun", + "keyLabel": "Bọtini", + "keyTooLong": "Bọtini gbọdọ̀ jẹ́ 256 àmì tàbí kéré sí i", + "keyDuplicate": "Bọtini yii ti wà tẹ́lẹ̀ nínú profaili", + "valueLabel": "Iye", + "secretLabel": "Asiri", + "secrets": { + "markSecret": "Samisi bi asiri", + "masked": "Iye farasin", + "reveal": "Iye ifihan", + "newValuePlaceholder": "Tẹ iye asiri tuntun sii", + "valuePlaceholder": "Iye asiri (ti a fi pamọ́)" + }, + "variablesEmpty": "Ko si awọn oniyipada sibẹsibẹ", + "variablesSaveFailed": "Ko le fi oniyipada pamọ", + "commandsTitle": "Awọn aṣẹ iṣeto", + "addCommand": "Fi aṣẹ kun", + "commandLabel": "Òfin", + "commandPlaceholder": "e.g. pnpm install", + "commandsEmpty": "Ko si awọn pipaṣẹ iṣeto sibẹsibẹ", + "commandsSaveFailed": "Ko le fi awọn pipaṣẹ iṣeto pamọ", + "moveUp": "Gbe soke", + "moveDown": "Yi lọ si isalẹ", + "skillsTitle": "Awọn ogbon", + "addSkill": "Fi ogbon kun", + "skillNameLabel": "Orukọ ogbon", + "skillMarkdownLabel": "Akoonu ogbon", + "skillsEmpty": "Ko si ogbon sibẹsibẹ", + "skillNameRequired": "Tẹ a olorijori orukọ", + "skillNameInvalid": "Orukọ ogbon gbọdọ bẹrẹ pẹlu lẹta tabi nọmba ati lo awọn lẹta kekere nikan, awọn nọmba, ati dashes", + "skillContentRequired": "Akoonu ogbon ni a nilo", + "skillSaveFailed": "Ko le fi ogbon pamọ", + "skillsHint": "Lẹẹmọ SKILL.md ati orukọ rẹ kun; ṣafikun ọgbọn lati inu foonu rẹ laisi fa ati ju silẹ.", + "skillEditTitle": "Olorijori Ṣatunkọ", + "mcp": { + "title": "Awọn olupin MCP", + "add": "Fi olupin MCP kun", + "empty": "Ko si olupin MCP sibẹsibẹ", + "name": "Orukọ olupin", + "type": "Iru olupin", + "localType": "Agbegbe", + "remoteType": "Latọna jijin", + "command": "Laini aṣẹ", + "url": "URL", + "environment": "Ayika (JSON)", + "headers": "Awọn akọle (JSON)", + "timeout": "Duro na", + "secretCount": "Iye asiri", + "namePlaceholder": "my-server", + "commandPlaceholder": "npx @example/mcp-server", + "jsonPlaceholder": "{\n \"API_KEY\": \"sk-...\"\n}", + "nameRequired": "Tẹ orukọ olupin sii", + "nameInvalid": "Orukọ le lo awọn lẹta nikan, awọn nọmba, awọn abẹlẹ, ati dashes", + "commandRequired": "Tẹ aṣẹ sii", + "commandTooLong": "Lo kò ju 50 àríyànjiyàn lọ, ọ̀kọ̀ọ̀kan 500 àmì", + "urlRequired": "Tẹ URL kan sii", + "urlInvalid": "Tẹ URL to wulo", + "urlTooLong": "URL gbọdọ̀ jẹ́ 2048 àmì tàbí kéré sí i", + "jsonInvalid": "Tẹ JSON ti o tọ sii - ohun kan ti awọn iye okun", + "jsonTooLarge": "Lo kò ju 50 àkọsílẹ̀ lọ, bọtini tó 128 àti iye tó 4096 àmì", + "timeoutInvalid": "Akoko ipari gbọdọ jẹ gbogbo nọmba ti milliseconds", + "edit": "Ṣatunkọ olupin MCP", + "saveFailed": "Ko le fi olupin MCP pamọ" + }, + "agents": { + "title": "Awọn aṣoju profaili", + "add": "Fi oluranlowo kun", + "empty": "Ko si awọn aṣoju sibẹsibẹ", + "name": "Orukọ aṣoju", + "slug": "Aṣoju slug", + "description": "Apejuwe oluranlowo", + "model": "Awoṣe yi danu", + "modelPlaceholder": "Lo aiyipada igba", + "prompt": "Ilana eto", + "steps": "Ìgbésẹ̀ tó pọ̀ jù", + "stepsPlaceholder": "fun apẹẹrẹ 50", + "temperature": "Iwọn otutu", + "temperaturePlaceholder": "fun apẹẹrẹ 0.2", + "topP": "top_p", + "topPPlaceholder": "fun apẹẹrẹ 0.95", + "slugPlaceholder": "my-agent", + "namePlaceholder": "Aṣoju mi", + "promptPlaceholder": "Iwọ jẹ ...", + "tools": "Awọn irinṣẹ", + "visibility": "Hihan", + "visibilityPrimary": "Alakoko - ti o han ni oluka", + "visibilitySubagent": "Subagent — nikan callable lati elomiran", + "visibilityAll": "Gbogbo - mejeeji", + "saveFailed": "Ko le fipamọ aṣoju", + "nameRequired": "Tẹ orukọ aṣoju sii", + "slugRequired": "Tẹ slug oluranlowo", + "slugInvalid": "Slug gbọdọ bẹrẹ pẹlu lẹta kan ko si lo awọn lẹta kekere nikan, awọn nọmba, ati dashes", + "slugConflict": "Slug yẹn wa ni ipamọ nipasẹ aṣoju ti a ṣe sinu", + "edit": "Aṣoju Ṣatunkọ" + }, + "slashCommands": { + "title": "Awọn pipaṣẹ idinku", + "add": "Ṣafikun pipaṣẹ idinku", + "empty": "Ko si awọn pipaṣẹ idinku sibẹsibẹ", + "name": "Orukọ aṣẹ", + "description": "Apejuwe aṣẹ", + "template": "Àdàkọ", + "namePlaceholder": "explain", + "templatePlaceholder": "Ṣe alaye $ARGUMENTS", + "agent": "Aṣoju", + "subtask": "Ṣiṣe bi iṣẹ-ṣiṣe", + "edit": "Ṣatunkọ pipaṣẹ idinku", + "saveFailed": "Ko le fi aṣẹ pamọ", + "nameRequired": "Tẹ orukọ aṣẹ sii", + "nameInvalid": "Orukọ gbọdọ bẹrẹ pẹlu lẹta kekere ati lo awọn lẹta kekere nikan, awọn nọmba, ati dashes", + "templateRequired": "Tẹ awoṣe aṣẹ sii", + "nameConflict": "Orukọ yẹn wa ni ipamọ nipasẹ aṣẹ ti a ṣe sinu" + }, + "repoBindings": { + "title": "Awọn profaili Aiyipada fun Repos", + "add": "Fi aiyipada kun", + "empty": "Ko si awọn atunto aiyipada", + "emptyDescription": "Ṣafikun aiyipada lati kan profaili laifọwọyi nigbati o bẹrẹ awọn akoko fun atunto kan.", + "repo": "Yan ibi ipamọ…", + "profile": "Yan profaili", + "remove": "Yọọ", + "private": "Ikọkọ", + "search": "Ṣewadii awọn ibi ipamọ…", + "addedToast": "Eto profaili aiyipada fun \"{{repo}}\"", + "addFailed": "Ko le ṣeto profaili aiyipada", + "removedToast": "Profaili aiyipada kuro fun \"{{repo}}\"", + "removeFailed": "Ko le yọ profaili aiyipada kuro", + "pinnedTitle": "Pined si awọn ibi ipamọ", + "pinRepo": "So repo mọ́", + "pinnedEmpty": "Ko pin si eyikeyi awọn ibi ipamọ.", + "pinnedDescription": "Ti kojọpọ ni aifọwọyi nigbakugba ti igba kan fojusi ọkan ninu awọn ibi ipamọ wọnyi.", + "pinnedToast": "Ti so mọ \"{{repo}}\"", + "pinFailed": "Ko le pin repo", + "unboundToast": "A ti tú repo sílẹ̀", + "unbindFailed": "Ko le yọkuro repo" + } } } diff --git a/apps/mobile/src/i18n/locales/zh-Hans.json b/apps/mobile/src/i18n/locales/zh-Hans.json index a5d9f889a73..905fdd1b800 100644 --- a/apps/mobile/src/i18n/locales/zh-Hans.json +++ b/apps/mobile/src/i18n/locales/zh-Hans.json @@ -1687,7 +1687,33 @@ "continueFrom": "正在继续会话“{{title}}”。", "importingSession": "正在导入会话", "cliCannotContinue": "此 CLI 无法继续会话。请选择 Cloud Agent 或其他 CLI。", - "importFailed": "无法导入此会话。" + "importFailed": "无法导入此会话。", + "pickProfile": "选择个人资料", + "noProfile": "没有个人资料", + "manageProfiles": "管理个人资料", + "noProfiles": "还没有个人资料。创建一个以添加环境变量、MCP 服务器和技能。", + "advancedConfig": "高级配置", + "advancedConfigDescription": "选择一个配置文件以加载保存的配置,并手动配置其他环境变量和设置命令", + "saveAsProfile": "另存为配置文件", + "saveProfileDescription": "将当前环境变量和设置命令保存为可重复使用的配置文件。", + "profileSummary": "{{vars}} 环境变量 · {{commands}} 设置命令", + "profileSaved": "配置文件“{{name}}”已保存", + "organizationProfiles": "组织简介", + "personalProfiles": "个人简介", + "yourProfiles": "您的个人资料", + "failedToLoadProfiles": "无法加载配置文件", + "nameTooLong": "个人资料名称太长", + "descriptionTooLong": "描述太长", + "secretsEncrypted": "秘密在存储之前被加密。", + "profileActive": "个人资料处于活动状态", + "profilesActive": "配置文件处于活动状态", + "customConfigActive": "自定义配置激活", + "configNeedsAttention": "配置需要注意", + "repoProfile": "回购简介", + "selectedProfile": "选定的个人资料", + "manualOverrides": "手动超控", + "openSettingsToReview": "打开设置进行查看", + "profileSelection": "型材选择" }, "partRenderer": { "updatedFileCount_one": "已更新 {{displayCount}} 个文件。", @@ -3224,5 +3250,180 @@ "multiplierError": "请输入 1 或更大的倍数", "permissionDenied": "你没有管理支出提醒的权限。", "pushOffByCategory": "已在通知中关闭" + }, + "profiles": { + "title": "配置文件", + "entrySubtitle": "跨会话重用环境设置", + "newProfile": "新建配置文件", + "emptyTitle": "还没有配置文件", + "emptyDescription": "创建配置文件以跨会话重复使用环境变量、设置命令和技能。", + "loadFailed": "无法加载配置文件", + "counts": { + "vars": "{{count}} 个变量", + "mcp": "{{count}} MCP", + "skills": "{{count}} 个技能", + "commands": "{{count}} 个命令", + "varsShort": "{{count}}变", + "skillsShort": "{{count}}技", + "commandsShort": "{{count}}命" + }, + "nameLabel": "配置文件名称", + "namePlaceholder": "例如后端调试", + "descriptionLabel": "配置文件说明", + "descriptionPlaceholder": "可选描述", + "ownerLabel": "配置文件所有者", + "nameRequired": "输入配置文件名称", + "createAction": "创建配置文件", + "createFailed": "无法创建配置文件", + "createdToast": "已创建配置文件“{{name}}”", + "settingsSectionTitle": "配置", + "defaultSectionTitle": "默认配置文件", + "personalDefaultDescription": "当没有存储库具有固定配置文件时自动加载。", + "organizationDefaultDescription": "为没有个人默认值的会员自动加载。", + "setAsDefault": "设为默认", + "removeDefault": "取消默认", + "saveFailed": "无法保存配置文件", + "deleteTitle": "删除配置文件", + "deleteMessage": "这将永久删除配置文件及其设置。此操作无法撤消。", + "deleteFailed": "无法删除配置文件", + "deleteBlocked": "此配置文件由 Webhook 触发器使用。首先将其从那些触发器中删除。", + "deletedToast": "配置文件“{{name}}”已删除", + "variablesTitle": "环境变量", + "addVariable": "添加变量", + "keyLabel": "键", + "keyTooLong": "键不能超过 256 个字符", + "keyDuplicate": "此键已存在于配置文件中", + "valueLabel": "值", + "secretLabel": "秘密", + "secrets": { + "markSecret": "标记为秘密", + "masked": "隐藏值", + "reveal": "显示值", + "newValuePlaceholder": "输入新的秘密值", + "valuePlaceholder": "秘密值(已加密)" + }, + "variablesEmpty": "还没有变量", + "variablesSaveFailed": "无法保存变量", + "commandsTitle": "设置命令", + "addCommand": "添加命令", + "commandLabel": "命令", + "commandPlaceholder": "e.g. pnpm install", + "commandsEmpty": "还没有设置命令", + "commandsSaveFailed": "无法保存设置命令", + "moveUp": "向上移动", + "moveDown": "下移", + "skillsTitle": "技能", + "addSkill": "添加技能", + "skillNameLabel": "技能名称", + "skillMarkdownLabel": "技能内容", + "skillsEmpty": "还没有技能", + "skillNameRequired": "输入技能名称", + "skillNameInvalid": "技能名称必须以字母或数字开头,并且只能使用小写字母、数字和破折号", + "skillContentRequired": "技能内容为必填项", + "skillSaveFailed": "无法保存技能", + "skillsHint": "粘贴一个SKILL.md并填写其名称;从手机添加技能,无需拖放。", + "skillEditTitle": "编辑技能", + "mcp": { + "title": "MCP服务器", + "add": "添加MCP服务器", + "empty": "还没有 MCP 服务器", + "name": "服务器名称", + "type": "服务器类型", + "localType": "本地", + "remoteType": "远程", + "command": "命令行", + "url": "网址", + "environment": "环境(JSON)", + "headers": "标头 (JSON)", + "timeout": "超时", + "secretCount": "密钥数量", + "namePlaceholder": "my-server", + "commandPlaceholder": "npx @example/mcp-server", + "jsonPlaceholder": "{\n \"API_KEY\": \"sk-...\"\n}", + "nameRequired": "输入服务器名称", + "nameInvalid": "名称只能使用字母、数字、下划线和破折号", + "commandRequired": "输入命令", + "commandTooLong": "最多使用 50 个参数,每个参数 500 个字符", + "urlRequired": "输入网址", + "urlInvalid": "输入有效的网址", + "urlTooLong": "网址不能超过 2048 个字符", + "jsonInvalid": "输入有效的 JSON — 字符串值的对象", + "jsonTooLarge": "最多使用 50 个条目,键最多 128 个字符,值最多 4096 个字符", + "timeoutInvalid": "超时必须是整数毫秒", + "edit": "编辑 MCP 服务器", + "saveFailed": "无法保存 MCP 服务器" + }, + "agents": { + "title": "配置文件代理", + "add": "添加代理", + "empty": "还没有代理", + "name": "代理名称", + "slug": "代理标识符", + "description": "代理说明", + "model": "模型覆盖", + "modelPlaceholder": "使用会话默认值", + "prompt": "系统提示", + "steps": "最大步数", + "stepsPlaceholder": "例如 50", + "temperature": "温度", + "temperaturePlaceholder": "例如 0.2", + "topP": "top_p", + "topPPlaceholder": "例如 0.95", + "slugPlaceholder": "my-agent", + "namePlaceholder": "我的代理", + "promptPlaceholder": "你是一个...", + "tools": "工具", + "visibility": "可见性", + "visibilityPrimary": "主要 — 显示在选择器中", + "visibilitySubagent": "子代理——只能从其他人调用", + "visibilityAll": "全部 — 两者", + "saveFailed": "无法保存代理", + "nameRequired": "输入代理名称", + "slugRequired": "输入代理 slug", + "slugInvalid": "Slug 必须以字母开头,并且只能使用小写字母、数字和破折号", + "slugConflict": "该 slug 由内置代理保留", + "edit": "编辑代理" + }, + "slashCommands": { + "title": "斜杠命令", + "add": "添加斜杠命令", + "empty": "还没有斜线命令", + "name": "命令名称", + "description": "命令说明", + "template": "模板", + "namePlaceholder": "explain", + "templatePlaceholder": "解释$ARGUMENTS", + "agent": "代理人", + "subtask": "作为子任务运行", + "edit": "编辑斜杠命令", + "saveFailed": "无法保存命令", + "nameRequired": "输入命令名称", + "nameInvalid": "名称必须以小写字母开头,并且只能使用小写字母、数字和破折号", + "templateRequired": "输入命令模板", + "nameConflict": "该名称由内置命令保留" + }, + "repoBindings": { + "title": "仓库的默认配置文件", + "add": "添加默认值", + "empty": "没有配置默认值", + "emptyDescription": "添加默认值以在启动存储库会话时自动应用配置文件。", + "repo": "选择存储库...", + "profile": "选择个人资料", + "remove": "解绑", + "private": "私有", + "search": "搜索存储库...", + "addedToast": "为“{{repo}}”设置的默认配置文件", + "addFailed": "无法设置默认配置文件", + "removedToast": "删除了“{{repo}}”的默认配置文件", + "removeFailed": "无法删除默认配置文件", + "pinnedTitle": "固定到存储库", + "pinRepo": "固定存储库", + "pinnedEmpty": "未固定到任何存储库。", + "pinnedDescription": "每当会话针对这些存储库之一时就会自动加载。", + "pinnedToast": "固定到“{{repo}}”", + "pinFailed": "无法固定存储库", + "unboundToast": "仓库已解除绑定", + "unbindFailed": "无法解除仓库绑定" + } } } diff --git a/apps/mobile/src/i18n/locales/zh-Hant.json b/apps/mobile/src/i18n/locales/zh-Hant.json index ca6de4ebf74..86452107398 100644 --- a/apps/mobile/src/i18n/locales/zh-Hant.json +++ b/apps/mobile/src/i18n/locales/zh-Hant.json @@ -1687,7 +1687,33 @@ "continueFrom": "正在接續 {{title}}。", "importingSession": "匯入工作階段中", "cliCannotContinue": "此 CLI 無法繼續工作階段。請選擇 Cloud Agent 或其他 CLI。", - "importFailed": "無法匯入此工作階段。" + "importFailed": "無法匯入此工作階段。", + "pickProfile": "選擇個人資料", + "noProfile": "沒有個人資料", + "manageProfiles": "管理個人資料", + "noProfiles": "還沒有個人資料。建立一個以新增環境變數、MCP 伺服器和技能。", + "advancedConfig": "進階配置", + "advancedConfigDescription": "選擇一個設定檔以載入已儲存的配置,並手動配置其他環境變數和設定命令", + "saveAsProfile": "另存為設定檔", + "saveProfileDescription": "將目前環境變數和設定命令儲存為可重複使用的設定檔。", + "profileSummary": "{{vars}} 環境變數 · {{commands}} 設定指令", + "profileSaved": "設定檔“{{name}}”已儲存", + "organizationProfiles": "組織簡介", + "personalProfiles": "個人簡介", + "yourProfiles": "您的個人資料", + "failedToLoadProfiles": "無法載入設定檔", + "nameTooLong": "個人資料名稱太長", + "descriptionTooLong": "描述太長", + "secretsEncrypted": "秘密在儲存之前被加密。", + "profileActive": "個人資料處於活動狀態", + "profilesActive": "設定檔處於活動狀態", + "customConfigActive": "自訂配置激活", + "configNeedsAttention": "配置需要注意", + "repoProfile": "回購簡介", + "selectedProfile": "選定的個人資料", + "manualOverrides": "手動超控", + "openSettingsToReview": "打開設定進行查看", + "profileSelection": "型材選擇" }, "partRenderer": { "updatedFileCount_one": "已更新 {{displayCount}} 個檔案。", @@ -3224,5 +3250,180 @@ "multiplierError": "請輸入 1 或更大的倍數", "permissionDenied": "你沒有管理支出提醒的權限。", "pushOffByCategory": "已在通知中關閉" + }, + "profiles": { + "title": "設定檔", + "entrySubtitle": "跨會話重複使用環境設置", + "newProfile": "新增設定檔", + "emptyTitle": "還沒有設定檔", + "emptyDescription": "建立設定檔以跨會話重複使用環境變數、設定命令和技能。", + "loadFailed": "無法載入設定檔", + "counts": { + "vars": "{{count}} 個變數", + "mcp": "{{count}} MCP", + "skills": "{{count}} 個技能", + "commands": "{{count}} 個命令", + "varsShort": "{{count}}變", + "skillsShort": "{{count}}技", + "commandsShort": "{{count}}命" + }, + "nameLabel": "設定檔名稱", + "namePlaceholder": "例如後端偵錯", + "descriptionLabel": "設定檔說明", + "descriptionPlaceholder": "選用描述", + "ownerLabel": "設定檔擁有者", + "nameRequired": "輸入設定檔名稱", + "createAction": "建立設定檔", + "createFailed": "無法建立設定檔", + "createdToast": "已建立設定檔“{{name}}”", + "settingsSectionTitle": "配置", + "defaultSectionTitle": "預設設定檔", + "personalDefaultDescription": "當沒有儲存庫具有固定設定檔時自動載入。", + "organizationDefaultDescription": "為沒有個人預設值的會員自動載入。", + "setAsDefault": "設為預設", + "removeDefault": "取消預設", + "saveFailed": "無法儲存設定檔", + "deleteTitle": "刪除設定檔", + "deleteMessage": "這將永久刪除設定檔及其設定。此操作無法撤銷。", + "deleteFailed": "無法刪除設定檔", + "deleteBlocked": "此設定檔由 Webhook 觸發器使用。首先將其從那些觸發器中刪除。", + "deletedToast": "設定檔“{{name}}”已刪除", + "variablesTitle": "環境變數", + "addVariable": "新增變數", + "keyLabel": "鍵", + "keyTooLong": "鍵不能超過 256 個字元", + "keyDuplicate": "此鍵已存在於設定檔中", + "valueLabel": "值", + "secretLabel": "秘密", + "secrets": { + "markSecret": "標記為秘密", + "masked": "隱藏值", + "reveal": "顯示值", + "newValuePlaceholder": "輸入新的秘密值", + "valuePlaceholder": "秘密值(已加密)" + }, + "variablesEmpty": "還沒有變數", + "variablesSaveFailed": "無法保存變數", + "commandsTitle": "設定命令", + "addCommand": "新增命令", + "commandLabel": "命令", + "commandPlaceholder": "e.g. pnpm install", + "commandsEmpty": "還沒有設定命令", + "commandsSaveFailed": "無法儲存設定命令", + "moveUp": "向上移動", + "moveDown": "下移", + "skillsTitle": "技能", + "addSkill": "添加技能", + "skillNameLabel": "技能名稱", + "skillMarkdownLabel": "技能內容", + "skillsEmpty": "還沒有技能", + "skillNameRequired": "輸入技能名稱", + "skillNameInvalid": "技能名稱必須以字母或數字開頭,且只能使用小寫字母、數字和破折號", + "skillContentRequired": "技能內容為必填項", + "skillSaveFailed": "無法保存技能", + "skillsHint": "貼上一個SKILL.md並填寫其名稱;從手機添加技能,無需拖放。", + "skillEditTitle": "編輯技能", + "mcp": { + "title": "MCP伺服器", + "add": "新增MCP伺服器", + "empty": "還沒有 MCP 伺服器", + "name": "伺服器名稱", + "type": "伺服器類型", + "localType": "本機", + "remoteType": "遠端", + "command": "命令列", + "url": "網址", + "environment": "環境(JSON)", + "headers": "標頭 (JSON)", + "timeout": "逾時", + "secretCount": "密鑰數量", + "namePlaceholder": "my-server", + "commandPlaceholder": "npx @example/mcp-server", + "jsonPlaceholder": "{\n \"API_KEY\": \"sk-...\"\n}", + "nameRequired": "輸入伺服器名稱", + "nameInvalid": "名稱只能使用字母、數字、底線和破折號", + "commandRequired": "輸入命令", + "commandTooLong": "最多使用 50 個引數,每個引數 500 個字元", + "urlRequired": "輸入網址", + "urlInvalid": "輸入有效的網址", + "urlTooLong": "網址不能超過 2048 個字元", + "jsonInvalid": "輸入有效的 JSON — 字串值的對象", + "jsonTooLarge": "最多使用 50 個項目,鍵最多 128 個字元,值最多 4096 個字元", + "timeoutInvalid": "超時必須是整數毫秒", + "edit": "編輯 MCP 伺服器", + "saveFailed": "無法保存 MCP 伺服器" + }, + "agents": { + "title": "設定檔代理", + "add": "新增代理", + "empty": "還沒有代理", + "name": "代理名稱", + "slug": "代理識別碼", + "description": "代理說明", + "model": "模型覆蓋", + "modelPlaceholder": "使用會話預設值", + "prompt": "系統提示", + "steps": "最大步數", + "stepsPlaceholder": "例如 50", + "temperature": "溫度", + "temperaturePlaceholder": "例如 0.2", + "topP": "top_p", + "topPPlaceholder": "例如 0.95", + "slugPlaceholder": "my-agent", + "namePlaceholder": "我的代理", + "promptPlaceholder": "你是一個...", + "tools": "工具", + "visibility": "可見性", + "visibilityPrimary": "主要 — 顯示在選擇器中", + "visibilitySubagent": "子代理程式——只能從其他人調用", + "visibilityAll": "全部 — 兩者", + "saveFailed": "無法保存代理", + "nameRequired": "輸入代理名稱", + "slugRequired": "輸入代理 slug", + "slugInvalid": "Slug 必須以字母開頭,且只能使用小寫字母、數字和破折號", + "slugConflict": "該 slug 由內建代理保留", + "edit": "編輯代理" + }, + "slashCommands": { + "title": "斜槓命令", + "add": "添加斜杠命令", + "empty": "還沒有斜線指令", + "name": "命令名稱", + "description": "命令說明", + "template": "範本", + "namePlaceholder": "explain", + "templatePlaceholder": "解釋$ARGUMENTS", + "agent": "代理人", + "subtask": "作為子任務運行", + "edit": "編輯斜槓命令", + "saveFailed": "無法保存命令", + "nameRequired": "輸入命令名稱", + "nameInvalid": "名稱必須以小寫字母開頭,且只能使用小寫字母、數字和破折號", + "templateRequired": "輸入命令模板", + "nameConflict": "該名稱由內建指令保留" + }, + "repoBindings": { + "title": "倉庫的預設設定檔", + "add": "新增預設值", + "empty": "沒有配置預設值", + "emptyDescription": "新增預設值以在啟動儲存庫會話時自動套用設定檔。", + "repo": "選擇存儲庫...", + "profile": "選擇個人資料", + "remove": "解綁", + "private": "私有", + "search": "搜尋儲存庫...", + "addedToast": "為“{{repo}}”設定的預設設定文件", + "addFailed": "無法設定預設設定檔", + "removedToast": "刪除了“{{repo}}”的預設設定文件", + "removeFailed": "無法刪除預設配置文件", + "pinnedTitle": "固定到儲存庫", + "pinRepo": "固定儲存庫", + "pinnedEmpty": "未固定到任何儲存庫。", + "pinnedDescription": "每當會話針對這些儲存庫之一時就會自動載入。", + "pinnedToast": "固定到“{{repo}}”", + "pinFailed": "無法固定儲存庫", + "unboundToast": "倉庫已解除綁定", + "unbindFailed": "無法解除倉庫綁定" + } } } diff --git a/apps/mobile/src/i18n/locales/zu.json b/apps/mobile/src/i18n/locales/zu.json index 838662ae669..109d8a7a8b9 100644 --- a/apps/mobile/src/i18n/locales/zu.json +++ b/apps/mobile/src/i18n/locales/zu.json @@ -2191,7 +2191,33 @@ "continueFrom": "Kuqhutshekwa neseshini ethi {{title}}.", "importingSession": "Iseshini iyangeniswa", "cliCannotContinue": "Le CLI ayikwazi ukuqhubeka neseshini. Khetha i-Cloud Agent noma enye i-CLI.", - "importFailed": "Asikwazanga ukungenisa le seshini." + "importFailed": "Asikwazanga ukungenisa le seshini.", + "pickProfile": "Khetha iphrofayela", + "noProfile": "Ayikho iphrofayela", + "manageProfiles": "Phatha amaphrofayili", + "noProfiles": "Awekho amaphrofayela okwamanje. Dala eyodwa ukuze wengeze okuguquguqukayo kwendawo, amaseva e-MCP, namakhono.", + "advancedConfig": "Ukucushwa Okuthuthukile", + "advancedConfigDescription": "Khetha iphrofayili ukuze ulayishe ukucupha okulondoloziwe, futhi ulungiselele ngokwakho okuguquguqukayo kwemvelo okwengeziwe kanye nemiyalo yokusetha", + "saveAsProfile": "Londoloza Njengephrofayela", + "saveProfileDescription": "Londoloza okuguquguqukayo kwendawo yamanje kanye nemiyalelo yokusetha njengephrofayili esebenziseka kabusha.", + "profileSummary": "{{vars}} okuguquguqukayo kwemvelo · {{commands}} imiyalo yokusetha", + "profileSaved": "Iphrofayela \"{{name}}\" ilondoloziwe", + "organizationProfiles": "Amaphrofayili enhlangano", + "personalProfiles": "Amaphrofayili Omuntu Siqu", + "yourProfiles": "Amaphrofayili akho", + "failedToLoadProfiles": "Yehlulekile ukulayisha amaphrofayili", + "nameTooLong": "Igama lephrofayela lide kakhulu", + "descriptionTooLong": "Incazelo yinde kakhulu", + "secretsEncrypted": "Izimfihlo zibethelwa ngaphambi kokugcinwa.", + "profileActive": "Iphrofayela iyasebenza", + "profilesActive": "Amaphrofayili ayasebenza", + "customConfigActive": "Ukulungiselela ngokwezifiso kuyasebenza", + "configNeedsAttention": "Ukulungiselela kudinga ukunakwa", + "repoProfile": "Iphrofayili ye-Repo", + "selectedProfile": "Iphrofayili ekhethiwe", + "manualOverrides": "Ukukhipha mathupha", + "openSettingsToReview": "Vula Izilungiselelo ukuze ubuyekeze", + "profileSelection": "Ukukhetha iphrofayela" }, "partRenderer": { "updatedFileCount_one": "Kubuyekezwe ifayela eli-{{displayCount}}.", @@ -3224,5 +3250,180 @@ "multiplierError": "Faka isiphindaphindi esingu-1 noma ngaphezulu", "permissionDenied": "Awunayo imvume yokuphatha izexwayiso zokusetshenziswa kwemali.", "pushOffByCategory": "Kucishiwe kuzaziso" + }, + "profiles": { + "title": "Amaphrofayili", + "entrySubtitle": "Sebenzisa kabusha izilungiselelo zendawo kuzo zonke izikhathi", + "newProfile": "Iphrofayela entsha", + "emptyTitle": "Awekho amaphrofayela okwamanje", + "emptyDescription": "Dala iphrofayela ukuze usebenzise kabusha okuguquguqukayo kwendawo, imiyalo yokusetha, namakhono kuzo zonke izikhathi.", + "loadFailed": "Ayikwazanga ukulayisha amaphrofayela", + "counts": { + "vars": "{{count}} okuguquguqukayo", + "mcp": "{{count}} MCP", + "skills": "{{count}} amakhono", + "commands": "{{count}} imiyalo", + "varsShort": "{{count}}o", + "skillsShort": "{{count}}a", + "commandsShort": "{{count}}i" + }, + "nameLabel": "Igama lephrofayela", + "namePlaceholder": "isib. Ukulungisa iphutha emuva", + "descriptionLabel": "Incazelo yephrofayela", + "descriptionPlaceholder": "Incazelo yokuzikhethela", + "ownerLabel": "Umnikazi wephrofayela", + "nameRequired": "Faka igama lephrofayela", + "createAction": "Dala iphrofayela", + "createFailed": "Ayikwazanga ukudala iphrofayela", + "createdToast": "Iphrofayela \"{{name}}\" idaliwe", + "settingsSectionTitle": "Ukucushwa", + "defaultSectionTitle": "Iphrofayela ezenzakalelayo", + "personalDefaultDescription": "Ilayisha ngokuzenzakalelayo lapho kungekho nqolobane enephrofayela ephiniwe.", + "organizationDefaultDescription": "Kulayishwa ngokuzenzakalelayo kumalungu angenakho okumisiwe komuntu siqu.", + "setAsDefault": "Setha njengokuzenzakalelayo", + "removeDefault": "Susa njengokuzenzakalelayo", + "saveFailed": "Ayikwazanga ukulondoloza iphrofayela", + "deleteTitle": "Susa iphrofayela", + "deleteMessage": "Lokhu kususa iphrofayela nezilungiselelo zayo unomphela. Lokhu akukwazi ukuhlehliswa.", + "deleteFailed": "Ayikwazanga ukususa iphrofayela", + "deleteBlocked": "Le phrofayela isetshenziswa i-webhook trigger. Isuse kulezo zibangeli kuqala.", + "deletedToast": "Iphrofayela \"{{name}}\" isusiwe", + "variablesTitle": "Okuguquguqukayo kwemvelo", + "addVariable": "Engeza okuguquguqukayo", + "keyLabel": "Ukhiye", + "keyTooLong": "Ukhiye kufanele ube yizinhlamvu ezingu-256 noma ngaphansi", + "keyDuplicate": "Lolu khi selukhona kuphrofayela", + "valueLabel": "Inani", + "secretLabel": "Imfihlo", + "secrets": { + "markSecret": "Maka njengokuyimfihlo", + "masked": "Inani elifihliwe", + "reveal": "Veza inani", + "newValuePlaceholder": "Faka inani eliyimfihlo elisha", + "valuePlaceholder": "Inani eliyimfihlo (kubethelwe)" + }, + "variablesEmpty": "Akukho okuguquguqukayo okwamanje", + "variablesSaveFailed": "Ayikwazanga ukulondoloza okuhlukile", + "commandsTitle": "Setha imiyalo", + "addCommand": "Engeza umyalo", + "commandLabel": "Umyalo", + "commandPlaceholder": "e.g. pnpm install", + "commandsEmpty": "Ayikho imiyalo yokusetha okwamanje", + "commandsSaveFailed": "Ayikwazanga ukulondoloza imiyalo yokusetha", + "moveUp": "Hamba uyephezulu", + "moveDown": "Hamba phansi", + "skillsTitle": "Amakhono", + "addSkill": "Engeza ikhono", + "skillNameLabel": "Igama lekhono", + "skillMarkdownLabel": "Okuqukethwe kwekhono", + "skillsEmpty": "Awekho amakhono okwamanje", + "skillNameRequired": "Faka igama lekhono", + "skillNameInvalid": "Igama lekhono kufanele liqale ngohlamvu noma idijithi futhi lisebenzise kuphela izinhlamvu ezincane, amadijithi, namadeshi.", + "skillContentRequired": "Okuqukethwe kwekhono kuyadingeka", + "skillSaveFailed": "Ayikwazanga ukulondoloza ikhono", + "skillsHint": "Namathisela i-SKILL.md futhi igama layo ligcwalise; engeza ikhono kusuka kufoni yakho ngaphandle kokuhudula nokuwisa.", + "skillEditTitle": "Hlela ikhono", + "mcp": { + "title": "Amaseva e-MCP", + "add": "Engeza iseva ye-MCP", + "empty": "Awekho amaseva e-MCP okwamanje", + "name": "Igama leseva", + "type": "Uhlobo lweseva", + "localType": "Indawo", + "remoteType": "Isilawuli kude", + "command": "Umugqa womyalo", + "url": "I-URL", + "environment": "Imvelo (JSON)", + "headers": "Izihloko (JSON)", + "timeout": "Isikhathi siphelile", + "secretCount": "Ukubala okuyimfihlo", + "namePlaceholder": "my-server", + "commandPlaceholder": "npx @example/mcp-server", + "jsonPlaceholder": "{\n \"API_KEY\": \"sk-...\"\n}", + "nameRequired": "Faka igama leseva", + "nameInvalid": "Igama lingasebenzisa kuphela izinhlamvu, izinombolo, ama-underscore, namadeshi", + "commandRequired": "Faka umyalo", + "commandTooLong": "Sebenzisa ama-agumenti angu-50 noma ngaphansi, ngalinye izinhlamvu ezingu-500", + "urlRequired": "Faka i-URL", + "urlInvalid": "Faka i-URL evumelekile", + "urlTooLong": "I-URL kufanele ibe yizinhlamvu ezingu-2048 noma ngaphansi", + "jsonInvalid": "Faka i-JSON evumelekile — into enamanani eyunithi yezinhlamvu", + "jsonTooLarge": "Sebenzisa okufakiwe okungu-50 noma ngaphansi, okhiye kufika ku-128 kanye namavelu kufika ezinhlamvu ezingu-4096", + "timeoutInvalid": "Isikhathi sokuvala kufanele sibe inani eliphelele lama-millisecond", + "edit": "Hlela iseva ye-MCP", + "saveFailed": "Ayikwazanga ukulondoloza iseva ye-MCP" + }, + "agents": { + "title": "Abenzeli bephrofayela", + "add": "Engeza umenzeli", + "empty": "Abekho ama-agent okwamanje", + "name": "Igama lomenzeli", + "slug": "I-agent slug", + "description": "Incazelo yomenzeli", + "model": "Ukukhipha imodeli", + "modelPlaceholder": "Sebenzisa okuzenzakalelayo kweseshini", + "prompt": "Ukwaziswa kwesistimu", + "steps": "Amanyathelo aphezulu", + "stepsPlaceholder": "isb. 50", + "temperature": "Izinga lokushisa", + "temperaturePlaceholder": "isb. 0.2", + "topP": "top_p", + "topPPlaceholder": "isb. 0.95", + "slugPlaceholder": "my-agent", + "namePlaceholder": "Umenzeli wami", + "promptPlaceholder": "Uyi...", + "tools": "Amathuluzi", + "visibility": "Ukubonakala", + "visibilityPrimary": "Okuyinhloko — kukhonjiswe kusikhi", + "visibilitySubagent": "I-Subagent - itholakala kuphela kwabanye", + "visibilityAll": "Konke - kokubili", + "saveFailed": "Ayikwazanga ukulondoloza umenzeli", + "nameRequired": "Faka igama lomenzeli", + "slugRequired": "Faka i-ejenti ye-slug", + "slugInvalid": "I-slug kufanele siqale ngohlamvu futhi sisebenzise kuphela izinhlamvu ezincane, amadijithi, namadeshi", + "slugConflict": "Leso slug sigodlwe i-ejenti eyakhelwe ngaphakathi", + "edit": "Hlela umenzeli" + }, + "slashCommands": { + "title": "Imiyalo ye-Slash", + "add": "Engeza umyalo we-slash", + "empty": "Ayikho imiyalo ye-slash okwamanje", + "name": "Igama lomyalo", + "description": "Incazelo yomyalo", + "template": "Isifanekiso", + "namePlaceholder": "explain", + "templatePlaceholder": "Chaza $ARGUMENTS", + "agent": "Umenzeli", + "subtask": "Qalisa njengomsebenzi ongaphansi", + "edit": "Hlela umyalo we-slash", + "saveFailed": "Ayikwazanga ukulondoloza umyalo", + "nameRequired": "Faka igama lomyalo", + "nameInvalid": "Igama kufanele liqale ngohlamvu oluncane futhi lisebenzise kuphela izinhlamvu ezincane, amadijithi, namadeshi", + "templateRequired": "Faka isifanekiso somyalo", + "nameConflict": "Lelo gama ligodliwe umyalo owakhelwe ngaphakathi" + }, + "repoBindings": { + "title": "Amaphrofayili Azenzakalelayo we-Repos", + "add": "Engeza okuzenzakalelayo", + "empty": "Akukho okumisiwe okumisiwe", + "emptyDescription": "Engeza okuzenzakalelayo ukuze usebenzise ngokuzenzakalelayo iphrofayela lapho uqala izikhathi ze-repo.", + "repo": "Khetha inqolobane...", + "profile": "Khetha iphrofayela", + "remove": "Khipha", + "private": "Okuyimfihlo", + "search": "Sesha amaqoqo...", + "addedToast": "Iphrofayela ezenzakalelayo isethelwe \"{{repo}}\"", + "addFailed": "Ayikwazanga ukusetha iphrofayela ezenzakalelayo", + "removedToast": "Iphrofayela ezenzakalelayo ikhishiwe \"{{repo}}\"", + "removeFailed": "Ayikwazanga ukususa iphrofayela ezenzakalelayo", + "pinnedTitle": "Iphiniwe kumakhosombe", + "pinRepo": "Phina i-repo", + "pinnedEmpty": "Akuphiniwe kunoma yimaphi amaqoqo.", + "pinnedDescription": "Ilayisha ngokuzenzakalelayo noma nini lapho iseshini iqondise eyodwa yalawa makhosombe.", + "pinnedToast": "Kuphinwe kokuthi \"{{repo}}\"", + "pinFailed": "Ayikwazanga ukuphina i-repo", + "unboundToast": "I-Repo ikhululiwe", + "unbindFailed": "Ayikwazanga ukukhulula i-repo" + } } } diff --git a/apps/mobile/src/i18n/profiles-copy.test.ts b/apps/mobile/src/i18n/profiles-copy.test.ts new file mode 100644 index 00000000000..004bc7525e5 --- /dev/null +++ b/apps/mobile/src/i18n/profiles-copy.test.ts @@ -0,0 +1,199 @@ +import { describe, expect, it } from 'vitest'; + +import en from './locales/en.json'; + +/** + * The profile-manager screens are written against these key names, so this + * literal is the interface: adding or renaming a key here means the screens + * that cite it must change too. + * + * It covers every nested section the surfaces use: the s3 overview copy, the + * s6 variables and s7 skills editors, and the s11 MCP/agents/slash-command + * editors plus the s12 repo bindings and s13 secrets lock/unlock copy. + */ +const EXPECTED_PROFILES = { + title: 'Profiles', + entrySubtitle: 'Reuse environment settings across sessions', + newProfile: 'New profile', + emptyTitle: 'No profiles yet', + emptyDescription: + 'Create a profile to reuse environment variables, setup commands, and skills across sessions.', + loadFailed: "Couldn't load profiles", + counts: { + vars: '{{count}} vars', + mcp: '{{count}} MCP', + skills: '{{count}} skills', + commands: '{{count}} cmds', + varsShort: '{{count}}v', + skillsShort: '{{count}}s', + commandsShort: '{{count}}c', + }, + nameLabel: 'Profile name', + namePlaceholder: 'e.g. Backend debugging', + descriptionLabel: 'Profile description', + descriptionPlaceholder: 'Optional description', + ownerLabel: 'Profile owner', + nameRequired: 'Enter a profile name', + createAction: 'Create profile', + createFailed: "Couldn't create profile", + createdToast: 'Profile "{{name}}" created', + settingsSectionTitle: 'Configuration', + defaultSectionTitle: 'Default profile', + personalDefaultDescription: 'Auto-loaded when no repository has a pinned profile.', + organizationDefaultDescription: 'Auto-loaded for members who have no personal default.', + setAsDefault: 'Set as default', + removeDefault: 'Remove as default', + saveFailed: "Couldn't save profile", + deleteTitle: 'Delete profile', + deleteMessage: 'This permanently removes the profile and its settings. This cannot be undone.', + deleteFailed: "Couldn't delete profile", + deleteBlocked: 'This profile is used by a webhook trigger. Remove it from those triggers first.', + deletedToast: 'Profile "{{name}}" deleted', + variablesTitle: 'Environment variables', + addVariable: 'Add variable', + keyLabel: 'Key', + keyTooLong: 'Key must be 256 characters or fewer', + keyDuplicate: 'This key already exists in the profile', + valueLabel: 'Value', + secretLabel: 'Secret', + secrets: { + markSecret: 'Mark as secret', + masked: 'Hidden value', + reveal: 'Reveal value', + newValuePlaceholder: 'Enter new secret value', + valuePlaceholder: 'Secret value (encrypted)', + }, + variablesEmpty: 'No variables yet', + variablesSaveFailed: "Couldn't save variable", + commandsTitle: 'Setup commands', + addCommand: 'Add command', + commandLabel: 'Command', + commandPlaceholder: 'e.g. pnpm install', + commandsEmpty: 'No setup commands yet', + commandsSaveFailed: "Couldn't save setup commands", + moveUp: 'Move up', + moveDown: 'Move down', + skillsTitle: 'Skills', + addSkill: 'Add skill', + skillNameLabel: 'Skill name', + skillMarkdownLabel: 'Skill content', + skillsEmpty: 'No skills yet', + skillNameRequired: 'Enter a skill name', + skillNameInvalid: + 'Skill name must start with a letter or digit and use only lowercase letters, digits, and dashes', + skillContentRequired: 'Skill content is required', + skillSaveFailed: "Couldn't save skill", + skillsHint: + 'Paste a SKILL.md and its name fills in; add a skill from your phone without drag and drop.', + skillEditTitle: 'Edit skill', + mcp: { + title: 'MCP servers', + add: 'Add MCP server', + empty: 'No MCP servers yet', + name: 'Server name', + type: 'Server type', + localType: 'Local', + remoteType: 'Remote', + command: 'Command line', + url: 'URL', + environment: 'Environment (JSON)', + headers: 'Headers (JSON)', + timeout: 'Timeout', + secretCount: 'Secret count', + namePlaceholder: 'my-server', + commandPlaceholder: 'npx @example/mcp-server', + jsonPlaceholder: '{\n "API_KEY": "sk-..."\n}', + nameRequired: 'Enter a server name', + nameInvalid: 'Name can use only letters, numbers, underscores, and dashes', + commandRequired: 'Enter a command', + commandTooLong: 'Use at most 50 arguments of 500 characters each', + urlRequired: 'Enter a URL', + urlInvalid: 'Enter a valid URL', + urlTooLong: 'URL must be 2048 characters or fewer', + jsonInvalid: 'Enter valid JSON — an object of string values', + jsonTooLarge: 'Use at most 50 entries, keys up to 128 and values up to 4096 characters', + timeoutInvalid: 'Timeout must be a whole number of milliseconds', + edit: 'Edit MCP server', + saveFailed: "Couldn't save MCP server", + }, + agents: { + title: 'Profile agents', + add: 'Add agent', + empty: 'No agents yet', + name: 'Agent name', + slug: 'Agent slug', + description: 'Agent description', + model: 'Model override', + modelPlaceholder: 'Use session default', + prompt: 'System prompt', + slugPlaceholder: 'my-agent', + namePlaceholder: 'My Agent', + promptPlaceholder: 'You are a ...', + steps: 'Max steps', + stepsPlaceholder: 'e.g. 50', + temperature: 'Temperature', + temperaturePlaceholder: 'e.g. 0.2', + topP: 'top_p', + topPPlaceholder: 'e.g. 0.95', + tools: 'Tools', + visibility: 'Visibility', + visibilityPrimary: 'Primary — shown in the picker', + visibilitySubagent: 'Subagent — only callable from others', + visibilityAll: 'All — both', + saveFailed: "Couldn't save agent", + nameRequired: 'Enter an agent name', + slugRequired: 'Enter an agent slug', + slugInvalid: 'Slug must start with a letter and use only lowercase letters, digits, and dashes', + slugConflict: 'That slug is reserved by a built-in agent', + edit: 'Edit agent', + }, + slashCommands: { + title: 'Slash commands', + add: 'Add slash command', + empty: 'No slash commands yet', + name: 'Command name', + description: 'Command description', + template: 'Template', + namePlaceholder: 'explain', + templatePlaceholder: 'Explain $ARGUMENTS', + agent: 'Agent', + subtask: 'Run as subtask', + edit: 'Edit slash command', + saveFailed: "Couldn't save command", + nameRequired: 'Enter a command name', + nameInvalid: + 'Name must start with a lowercase letter and use only lowercase letters, digits, and dashes', + templateRequired: 'Enter a command template', + nameConflict: 'That name is reserved by a built-in command', + }, + repoBindings: { + title: 'Default Profiles for Repos', + add: 'Add default', + empty: 'No defaults configured', + emptyDescription: + 'Add a default to automatically apply a profile when starting sessions for a repo.', + repo: 'Select repository…', + profile: 'Select profile', + remove: 'Unbind', + private: 'Private', + search: 'Search repositories…', + addedToast: 'Default profile set for "{{repo}}"', + addFailed: "Couldn't set default profile", + removedToast: 'Default profile removed for "{{repo}}"', + removeFailed: "Couldn't remove default profile", + pinnedTitle: 'Pinned to repositories', + pinRepo: 'Pin a repo', + pinnedEmpty: 'Not pinned to any repositories.', + pinnedDescription: 'Auto-loaded whenever a session targets one of these repositories.', + pinnedToast: 'Pinned to "{{repo}}"', + pinFailed: "Couldn't pin repo", + unboundToast: 'Repo unbound', + unbindFailed: "Couldn't unbind repo", + }, +} as const; + +describe('profiles copy', () => { + it('pins the profile-manager key set', () => { + expect(en.profiles).toEqual(EXPECTED_PROFILES); + }); +}); diff --git a/apps/mobile/src/lib/agent-profile-forms.test.ts b/apps/mobile/src/lib/agent-profile-forms.test.ts new file mode 100644 index 00000000000..cc7d14c3be2 --- /dev/null +++ b/apps/mobile/src/lib/agent-profile-forms.test.ts @@ -0,0 +1,163 @@ +import { describe, expect, it } from 'vitest'; + +import { + addCommand, + cleanVariableKey, + isValidVariableKey, + MAX_SETUP_COMMANDS, + moveCommand, + parseSkillFrontmatter, + removeCommand, + replaceCommand, + validateProfileName, + validateSkillInput, +} from '@/lib/agent-profile-forms'; + +describe('validateProfileName', () => { + it('accepts a trimmed name within the 1..100 bound', () => { + expect(validateProfileName('Backend debugging')).toBeNull(); + expect(validateProfileName(' padded ')).toBeNull(); + }); + + it('reports an empty name after trimming', () => { + expect(validateProfileName('')).toBe('empty'); + expect(validateProfileName(' ')).toBe('empty'); + }); + + it('reports a name longer than 100 characters', () => { + expect(validateProfileName('a'.repeat(100))).toBeNull(); + expect(validateProfileName('a'.repeat(101))).toBe('too-long'); + }); +}); + +describe('parseSkillFrontmatter', () => { + it('parses unquoted name and description', () => { + const markdown = ['---', 'name: code-review', 'description: Reviews a diff', '---', ''].join( + '\n' + ); + expect(parseSkillFrontmatter(markdown)).toEqual({ + name: 'code-review', + description: 'Reviews a diff', + }); + }); + + it('strips matching double and single quotes', () => { + expect( + parseSkillFrontmatter( + ['---', 'name: "code-review"', "description: 'Reviews a diff'", '---', ''].join('\n') + ) + ).toEqual({ name: 'code-review', description: 'Reviews a diff' }); + }); + + it('handles CRLF line endings', () => { + const markdown = '---\r\nname: crlf-skill\r\ndescription: Windows file\r\n---\r\nbody'; + expect(parseSkillFrontmatter(markdown)).toEqual({ + name: 'crlf-skill', + description: 'Windows file', + }); + }); + + it('returns nothing without a leading frontmatter block', () => { + expect(parseSkillFrontmatter('# Just markdown')).toEqual({}); + expect(parseSkillFrontmatter('')).toEqual({}); + }); + + it('ignores unrelated frontmatter fields and missing fields', () => { + expect(parseSkillFrontmatter(['---', 'name: only-name', '---', ''].join('\n'))).toEqual({ + name: 'only-name', + description: undefined, + }); + expect(parseSkillFrontmatter(['---', 'license: MIT', '---', ''].join('\n'))).toEqual({ + name: undefined, + description: undefined, + }); + }); +}); + +describe('validateSkillInput', () => { + const content = ['---', 'name: my-skill', 'description: Does a thing', '---', 'Body'].join('\n'); + + it('accepts a valid name and content', () => { + expect(validateSkillInput({ name: 'my-skill', content })).toEqual({ + error: null, + description: 'Does a thing', + }); + }); + + it('accepts valid input without frontmatter and returns no description', () => { + expect(validateSkillInput({ name: 'my-skill', content: 'Body only' })).toEqual({ error: null }); + }); + + it('reports an empty name even with content present', () => { + expect(validateSkillInput({ name: ' ', content })).toEqual({ error: 'empty' }); + }); + + it('reports a name the server pattern rejects', () => { + expect(validateSkillInput({ name: 'My Skill', content })).toEqual({ error: 'bad-name' }); + expect(validateSkillInput({ name: '-leading-dash', content })).toEqual({ error: 'bad-name' }); + expect(validateSkillInput({ name: 'Upper', content })).toEqual({ error: 'bad-name' }); + }); + + it('reports empty content', () => { + expect(validateSkillInput({ name: 'my-skill', content: ' ' })).toEqual({ + error: 'no-content', + }); + }); +}); + +describe('variable keys', () => { + it('cleans a typed key into the stored shape', () => { + expect(cleanVariableKey('api key')).toBe('API_KEY'); + expect(cleanVariableKey('api-key.v2')).toBe('API_KEY_V2'); + expect(cleanVariableKey('ALREADY_OK')).toBe('ALREADY_OK'); + }); + + it('validates the 1..256 non-empty bound', () => { + expect(isValidVariableKey('API_KEY')).toBe(true); + expect(isValidVariableKey('a'.repeat(256))).toBe(true); + expect(isValidVariableKey('a'.repeat(257))).toBe(false); + expect(isValidVariableKey(' ')).toBe(false); + }); +}); + +describe('setup command list operations', () => { + const commands = ['pnpm install', 'pnpm build', 'pnpm test']; + + it('appends a blank command', () => { + expect(addCommand(commands)).toEqual(['pnpm install', 'pnpm build', 'pnpm test', '']); + expect(addCommand([])).toEqual(['']); + }); + + it('refuses a command beyond the server list cap', () => { + const atCap = Array.from({ length: MAX_SETUP_COMMANDS }, (_, index) => `cmd ${index}`); + expect(addCommand(atCap)).toEqual(atCap); + }); + + it('replaces the command at an index', () => { + expect(replaceCommand(commands, 1, 'pnpm lint')).toEqual([ + 'pnpm install', + 'pnpm lint', + 'pnpm test', + ]); + expect(replaceCommand(commands, 7, 'ignored')).toEqual(commands); + }); + + it('removes the command at an index without touching the source list', () => { + expect(removeCommand(commands, 0)).toEqual(['pnpm build', 'pnpm test']); + expect(removeCommand(commands, 7)).toEqual(commands); + expect(commands).toEqual(['pnpm install', 'pnpm build', 'pnpm test']); + }); + + it('moves a command up and down', () => { + expect(moveCommand(commands, 2, -1)).toEqual(['pnpm install', 'pnpm test', 'pnpm build']); + expect(moveCommand(commands, 0, 1)).toEqual(['pnpm build', 'pnpm install', 'pnpm test']); + }); + + it('clamps a move at both edges and returns new arrays', () => { + expect(moveCommand(commands, 0, -1)).toEqual(commands); + expect(moveCommand(commands, 2, 1)).toEqual(commands); + expect(moveCommand(commands, 5, -1)).toEqual(commands); + expect(moveCommand(commands, 0, -1)).not.toBe(commands); + expect(commands).toEqual(['pnpm install', 'pnpm build', 'pnpm test']); + }); +}); diff --git a/apps/mobile/src/lib/agent-profile-forms.ts b/apps/mobile/src/lib/agent-profile-forms.ts new file mode 100644 index 00000000000..94f59d434a6 --- /dev/null +++ b/apps/mobile/src/lib/agent-profile-forms.ts @@ -0,0 +1,170 @@ +/** + * Pure form helpers for the mobile profile manager. + * + * No React and no React Native imports: every function here is unit-tested + * directly in `agent-profile-forms.test.ts` and mirrors the server-side + * validation the `agentProfiles.*` procedures apply, so the mobile form can + * reject invalid input before it reaches the API. + */ + +/** Server bound: `ProfileNameSchema`'s `z.string().min(1).max(100)`. */ +const PROFILE_NAME_MAX_LENGTH = 100; + +/** Server bound: `VarSchema`'s `z.string().min(1).max(256)`. */ +export const VARIABLE_KEY_MAX_LENGTH = 256; + +/** + * Server bound: the inline session layer's env-var values. The prepare-session + * input caps each value at 256 characters (`cloud-agent-next-schemas.ts`'s + * `envVars` record), which is stricter than a profile's own `VarSchema` + * (10000), so the manual editor under Advanced Configuration applies this + * bound where the profile editor does not. + */ +export const MAX_SESSION_ENV_VAR_VALUE_LENGTH = 256; + +/** Server pattern: `profile-skills-service.ts`'s `SKILL_NAME_PATTERN`. */ +const SKILL_NAME_PATTERN = /^[a-z0-9][a-z0-9-]*$/; + +export type ProfileNameError = 'empty' | 'too-long'; + +/** + * Validate a profile name. Names are trimmed, matching the server's + * `min(1)` after the form trims. Returns an error code, or `null` when valid. + */ +export function validateProfileName(value: string): ProfileNameError | null { + const trimmed = value.trim(); + if (trimmed.length === 0) { + return 'empty'; + } + if (trimmed.length > PROFILE_NAME_MAX_LENGTH) { + return 'too-long'; + } + return null; +} + +/** + * Parse frontmatter `name` and `description` out of a SKILL.md body. + * Ported from the web editor's minimal parser + * (`apps/web/src/components/cloud-agent/profile-editor/SkillsTab.tsx`): only a + * leading `---` block, only these two fields, quotes stripped. + */ +export type SkillFrontmatter = { name?: string; description?: string }; + +function stripQuotes(value: string): string { + const trimmed = value.trim(); + if ( + (trimmed.startsWith('"') && trimmed.endsWith('"')) || + (trimmed.startsWith("'") && trimmed.endsWith("'")) + ) { + return trimmed.slice(1, -1); + } + return trimmed; +} + +export function parseSkillFrontmatter(markdown: string): SkillFrontmatter { + const match = /^---\s*\r?\n([\s\S]*?)\r?\n---\s*\r?\n/.exec(markdown); + if (!match) { + return {}; + } + const frontmatter = match[1] ?? ''; + const nameMatch = /^name\s*:\s*(.+)$/m.exec(frontmatter); + const descriptionMatch = /^description\s*:\s*(.+)$/m.exec(frontmatter); + return { + name: nameMatch?.[1] === undefined ? undefined : stripQuotes(nameMatch[1]), + description: descriptionMatch?.[1] === undefined ? undefined : stripQuotes(descriptionMatch[1]), + }; +} + +export type SkillInputError = 'empty' | 'bad-name' | 'no-content'; + +export type SkillInputValidation = { error: SkillInputError | null; description?: string }; + +/** + * Validate a pasted custom skill. The name is checked against the server's + * pattern; when the content carries frontmatter, its `description` is returned + * so the caller can pass it through. + */ +export function validateSkillInput(input: { name: string; content: string }): SkillInputValidation { + const name = input.name.trim(); + if (name.length === 0) { + return { error: 'empty' }; + } + if (!SKILL_NAME_PATTERN.test(name)) { + return { error: 'bad-name' }; + } + if (input.content.trim().length === 0) { + return { error: 'no-content' }; + } + const { description } = parseSkillFrontmatter(input.content); + return description === undefined ? { error: null } : { error: null, description }; +} + +/** + * Normalize a typed environment variable key to the shape the server stores: + * upper case, with anything outside `A-Z0-9_` replaced by `_`. Matches the web + * editor's `cleanKey`. + */ +export function cleanVariableKey(value: string): string { + return value.toUpperCase().replaceAll(/[^A-Z0-9_]/g, '_'); +} + +/** A key is valid when its trimmed length is within the server's 1..256 bound. */ +export function isValidVariableKey(value: string): boolean { + const trimmed = value.trim(); + return trimmed.length > 0 && trimmed.length <= VARIABLE_KEY_MAX_LENGTH; +} + +/** Server bound: each setup command is at most 500 characters. */ +export const MAX_SETUP_COMMAND_LENGTH = 500; + +/** Server bound: a profile or session accepts at most 20 setup commands. */ +export const MAX_SETUP_COMMANDS = 20; + +/** + * Append a blank setup command for the user to type into. The server caps the + * list at `MAX_SETUP_COMMANDS` (`CommandsSchema`'s + * `z.array(z.string().max(500)).max(20)`), so at the cap the list is returned + * unchanged rather than producing a payload the server would reject. + */ +export function addCommand(commands: readonly string[]): string[] { + if (commands.length >= MAX_SETUP_COMMANDS) { + return [...commands]; + } + return [...commands, '']; +} + +/** Replace the command at `index`. Out-of-range indices leave the list intact. */ +export function replaceCommand( + commands: readonly string[], + index: number, + value: string +): string[] { + return commands.map((command, current) => (current === index ? value : command)); +} + +/** Remove the command at `index`. Out-of-range indices leave the list intact. */ +export function removeCommand(commands: readonly string[], index: number): string[] { + return commands.filter((_, current) => current !== index); +} + +/** + * Move the command at `index` by `delta` positions (`-1` up, `+1` down), + * clamped to the list's edges, returning a new array. The screen sends the + * whole ordered list to `setCommands`, so order is the only state. + */ +export function moveCommand(commands: readonly string[], index: number, delta: number): string[] { + if (index < 0 || index >= commands.length) { + return [...commands]; + } + const target = Math.max(0, Math.min(commands.length - 1, index + delta)); + if (target === index) { + return [...commands]; + } + const next = [...commands]; + const [moved] = next.splice(index, 1); + if (moved === undefined) { + return [...commands]; + } + next.splice(target, 0, moved); + return next; +} diff --git a/apps/mobile/src/lib/app-actions/prepare-agent-session.ts b/apps/mobile/src/lib/app-actions/prepare-agent-session.ts index c86a9fc796f..e20562d02f0 100644 --- a/apps/mobile/src/lib/app-actions/prepare-agent-session.ts +++ b/apps/mobile/src/lib/app-actions/prepare-agent-session.ts @@ -42,6 +42,10 @@ export type PrepareAgentSessionInput = model: string; variant?: string; profileId?: string | null; + /** Manual env vars layered over the profile; omitted from the body when empty. */ + envVars?: Record; + /** Manual setup commands layered over the profile; omitted from the body when empty. */ + setupCommands?: string[]; /** Commit and push the agent's changes (true) or leave them uncommitted. */ autoCommit?: boolean; attachments?: AgentAttachmentWire; @@ -245,6 +249,8 @@ type PrepareSessionSharedFields = PrepareSessionRepositoryFields & { operationKey: string; upstreamBranch?: string; profileId?: string; + envVars?: Record; + setupCommands?: string[]; attachments?: AgentAttachmentWire; }; @@ -296,6 +302,17 @@ function newIntentFingerprint( input: Extract, repo: ReturnType | string ): string { + // The inline overrides are part of the intent only when present, so a session + // with no manual config keeps the exact fingerprint the deployed app stored + // (a relaunch reuses its safe-retry key instead of minting a duplicate). + const inlineOverrides = + input.envVars !== undefined && Object.keys(input.envVars).length > 0 + ? { envVars: input.envVars } + : {}; + const setupCommands = + input.setupCommands !== undefined && input.setupCommands.length > 0 + ? { setupCommands: input.setupCommands } + : {}; return JSON.stringify({ prompt: input.prompt, mode: input.mode, @@ -306,6 +323,8 @@ function newIntentFingerprint( organizationId: input.organizationId ?? null, profileId: input.profileId ?? null, attachments: input.attachments ?? null, + ...inlineOverrides, + ...setupCommands, }); } @@ -357,6 +376,15 @@ function prepareSessionBody( if (input.profileId) { body.profileId = input.profileId; } + // Inline overrides the server layers over the resolved profile. Omitted when + // empty so the body is byte-identical to what the deployed app sent before + // the advanced config carried manual values. + if (input.envVars !== undefined && Object.keys(input.envVars).length > 0) { + body.envVars = input.envVars; + } + if (input.setupCommands !== undefined && input.setupCommands.length > 0) { + body.setupCommands = input.setupCommands; + } if (input.attachments) { body.attachments = input.attachments; } diff --git a/apps/mobile/src/lib/case-guard.test.ts b/apps/mobile/src/lib/case-guard.test.ts index 321e7838194..458893d4063 100644 --- a/apps/mobile/src/lib/case-guard.test.ts +++ b/apps/mobile/src/lib/case-guard.test.ts @@ -40,6 +40,7 @@ const ALLOWED_NON_DISPLAY: Readonly> = { 'lib/use-new-session-repos.ts': 'repo key normalization', 'lib/organization-invoice-download.ts': 'filename comparison', 'lib/agent-attachments/validate.ts': 'file-extension normalization', + 'lib/agent-profile-forms.ts': 'environment-variable key normalization', 'lib/auth/use-native-auth.ts': 'email normalization', 'lib/telemetry/install-error-reporting.ts': 'hostname comparison', 'lib/pr-review/diff/highlight.ts': 'file-extension normalization', @@ -64,6 +65,9 @@ const ALLOWED_NON_DISPLAY: Readonly> = { 'components/agents/session-terminal-error.ts': 'service-message classification', 'components/agents/tool-card-image-cache.ts': 'mime-subtype normalization', 'components/agents/tool-list-model.ts': 'status-note comparison', + 'components/profiles/agent-form-sheet.tsx': 'agent-slug normalization', + 'components/profiles/kilo-command-form-sheet.tsx': 'slash-command name normalization', + 'components/profiles/repo-bindings-model.ts': 'repository full-name search and comparison', 'app/(app)/kiloclaw/[instance-id]/settings/model-list.tsx': 'model search folding', }; diff --git a/apps/mobile/src/lib/hooks/agent-profile-mutation-helpers.ts b/apps/mobile/src/lib/hooks/agent-profile-mutation-helpers.ts new file mode 100644 index 00000000000..6ecc065f344 --- /dev/null +++ b/apps/mobile/src/lib/hooks/agent-profile-mutation-helpers.ts @@ -0,0 +1,32 @@ +import { type MutationOptions } from '@tanstack/react-query'; + +/** + * Add the hook's context organization to a mutation's input, so every screen + * under the same context does not repeat `organizationId` at each call site. + * `organizationId` is optional in every `agentProfiles.*` input, so the merged + * object is still the procedure's input type. + * + * Shared by the profile-management mutation hooks so their inputs carry the same + * organization context. + */ +export function withOrganization< + TData, + TError, + TVariables extends { organizationId?: string }, + TContext, +>( + options: MutationOptions, + organizationId: string | undefined +): MutationOptions { + const mutationFn = options.mutationFn; + if (!mutationFn || organizationId === undefined) { + return options; + } + return { + ...options, + // The wrapper must return the underlying promise unchanged; `async` without + // an await trips `require-await` (same conflict noted in use-code-reviewer). + // eslint-disable-next-line typescript-eslint/promise-function-async -- wraps a promise-returning mutationFn + mutationFn: (variables, context) => mutationFn({ ...variables, organizationId }, context), + }; +} diff --git a/apps/mobile/src/lib/hooks/agent-profile-types.ts b/apps/mobile/src/lib/hooks/agent-profile-types.ts new file mode 100644 index 00000000000..b9d4d0c7dda --- /dev/null +++ b/apps/mobile/src/lib/hooks/agent-profile-types.ts @@ -0,0 +1,19 @@ +import { type inferRouterOutputs, type MobileRouter } from '@kilocode/trpc/mobile'; + +/** + * The tRPC output shapes the profile screens and hooks share. Kept in their own + * module so the query hooks and the mutation hook can both depend on them + * without importing each other. + */ +type RouterOutputs = inferRouterOutputs; + +/** A profile summary as the list surfaces render it. Org summaries add `ownerType`. */ +export type AgentProfileListItem = RouterOutputs['agentProfiles']['list'][number] & { + ownerType?: 'organization' | 'user'; +}; + +export type AgentProfileListCombined = RouterOutputs['agentProfiles']['listCombined']; +export type AgentProfileDetail = RouterOutputs['agentProfiles']['get']; + +/** One repo-to-profile binding as `listRepoBindings` returns it. */ +export type AgentRepoBinding = RouterOutputs['agentProfiles']['listRepoBindings'][number]; diff --git a/apps/mobile/src/lib/hooks/use-agent-profile-mutations.ts b/apps/mobile/src/lib/hooks/use-agent-profile-mutations.ts new file mode 100644 index 00000000000..44f9e33fabf --- /dev/null +++ b/apps/mobile/src/lib/hooks/use-agent-profile-mutations.ts @@ -0,0 +1,258 @@ +import { type QueryKey, useMutation, useQueryClient } from '@tanstack/react-query'; +import { toast } from 'sonner-native'; + +import { withOrganization } from '@/lib/hooks/agent-profile-mutation-helpers'; +import { + type AgentProfileDetail, + type AgentProfileListCombined, + type AgentProfileListItem, +} from '@/lib/hooks/agent-profile-types'; +import { + isLatestMutationGeneration, + nextMutationGeneration, +} from '@/lib/hooks/mutation-generations'; +import { useTRPC } from '@/lib/trpc'; + +// Optimistic writes on the same cache region are generation-stamped so an older +// mutation's rollback cannot stomp a newer mutation's optimistic value. +const DEFAULT_MUTATION_KEY = 'agentProfiles:default'; +const SKILL_MUTATION_KEY = 'agentProfiles:skill'; + +function withDefaultFlag( + profiles: readonly T[], + profileId: string, + setDefault: boolean +): T[] { + return profiles.map(profile => withDefaultDetail(profile, profileId, setDefault)); +} + +function withDefaultDetail( + profile: T, + profileId: string, + setDefault: boolean +): T { + const isDefault = setDefault + ? profile.id === profileId + : profile.id !== profileId && profile.isDefault; + return profile.isDefault === isDefault ? profile : { ...profile, isDefault }; +} + +function withSkillEnabled( + profile: AgentProfileDetail, + skillId: string, + enabled: boolean +): AgentProfileDetail { + return { + ...profile, + skills: profile.skills.map(skill => (skill.id === skillId ? { ...skill, enabled } : skill)), + }; +} + +/** + * Every mutation the profile-management screens call for the profile itself, its + * variables, setup commands, and skills. Each mutation carries the hook's + * organization context, toasts a failed server call exactly once, and + * invalidates the `agentProfiles` namespace so the list and detail queries + * reconcile with server truth. + * + * The section editors (MCP servers, agents, kilo commands) have their own hook + * in `use-agent-profile-section-mutations`. + * + * `setAsDefault`, `clearDefault` and `setSkillEnabled` are optimistic: they + * snapshot the affected cache in `onMutate`, roll back in `onError` (latest + * generation only), and invalidate again in `onSettled` to reconcile. + */ +export function useAgentProfileMutations(organizationId?: string) { + const trpc = useTRPC(); + const queryClient = useQueryClient(); + + const profilesFilter = trpc.agentProfiles.pathFilter(); + const combinedFilter = trpc.agentProfiles.listCombined.pathFilter(); + const detailFilter = trpc.agentProfiles.get.pathFilter(); + const defaultListKey = trpc.agentProfiles.list.queryKey({ organizationId }); + const defaultDetailKey = trpc.agentProfiles.get.queryKey({ organizationId }); + const defaultCombinedKey = organizationId + ? trpc.agentProfiles.listCombined.queryKey({ organizationId }) + : combinedFilter.queryKey; + + const invalidateProfiles = () => { + void queryClient.invalidateQueries(profilesFilter); + }; + const toastError = (error: { message: string }) => { + toast.error(error.message); + }; + const callbacks = { + onSuccess: invalidateProfiles, + onError: (error: { message: string }) => { + toastError(error); + }, + }; + + const applyDefault = (profileId: string, setDefault: boolean) => { + queryClient.setQueriesData({ queryKey: defaultListKey }, old => + old ? withDefaultFlag(old, profileId, setDefault) : old + ); + queryClient.setQueriesData({ queryKey: defaultCombinedKey }, old => { + if (!old) { + return old; + } + const orgProfiles = organizationId + ? withDefaultFlag(old.orgProfiles, profileId, setDefault) + : old.orgProfiles; + const personalProfiles = organizationId + ? old.personalProfiles + : withDefaultFlag(old.personalProfiles, profileId, setDefault); + return { + ...old, + orgProfiles, + personalProfiles, + effectiveDefaultId: + ( + personalProfiles.find(profile => profile.isDefault) ?? + orgProfiles.find(profile => profile.isDefault) + )?.id ?? null, + }; + }); + queryClient.setQueriesData({ queryKey: defaultDetailKey }, old => + old ? withDefaultDetail(old, profileId, setDefault) : old + ); + }; + + const snapshotProfiles = async () => { + await queryClient.cancelQueries({ queryKey: defaultListKey }); + await queryClient.cancelQueries({ queryKey: defaultCombinedKey }); + await queryClient.cancelQueries({ queryKey: defaultDetailKey }); + const previous: [QueryKey, unknown][] = [ + ...queryClient.getQueriesData({ queryKey: defaultListKey }), + ...queryClient.getQueriesData({ + queryKey: defaultCombinedKey, + }), + ...queryClient.getQueriesData({ queryKey: defaultDetailKey }), + ]; + return { generation: nextMutationGeneration(DEFAULT_MUTATION_KEY), previous }; + }; + + const rollbackProfiles = ( + context: { generation: number; previous: [QueryKey, unknown][] } | undefined + ) => { + if (!context || !isLatestMutationGeneration(DEFAULT_MUTATION_KEY, context.generation)) { + return; + } + for (const [key, data] of context.previous) { + queryClient.setQueryData(key, data); + } + }; + + const create = useMutation( + withOrganization(trpc.agentProfiles.create.mutationOptions(callbacks), organizationId) + ); + const update = useMutation( + withOrganization(trpc.agentProfiles.update.mutationOptions(callbacks), organizationId) + ); + const deleteProfile = useMutation( + withOrganization(trpc.agentProfiles.delete.mutationOptions(callbacks), organizationId) + ); + const setVar = useMutation( + withOrganization(trpc.agentProfiles.setVar.mutationOptions(callbacks), organizationId) + ); + const deleteVar = useMutation( + withOrganization(trpc.agentProfiles.deleteVar.mutationOptions(callbacks), organizationId) + ); + const setCommands = useMutation( + withOrganization(trpc.agentProfiles.setCommands.mutationOptions(callbacks), organizationId) + ); + const createCustomSkill = useMutation( + withOrganization( + trpc.agentProfiles.createCustomSkill.mutationOptions(callbacks), + organizationId + ) + ); + const updateSkill = useMutation( + withOrganization(trpc.agentProfiles.updateSkill.mutationOptions(callbacks), organizationId) + ); + const deleteSkill = useMutation( + withOrganization(trpc.agentProfiles.deleteSkill.mutationOptions(callbacks), organizationId) + ); + + const setAsDefault = useMutation( + withOrganization( + trpc.agentProfiles.setAsDefault.mutationOptions({ + ...callbacks, + onMutate: async variables => { + const context = await snapshotProfiles(); + applyDefault(variables.profileId, true); + return context; + }, + onError: (error, _variables, context) => { + rollbackProfiles(context); + toastError(error); + }, + onSettled: invalidateProfiles, + }), + organizationId + ) + ); + + const clearDefault = useMutation( + withOrganization( + trpc.agentProfiles.clearDefault.mutationOptions({ + ...callbacks, + onMutate: async variables => { + const context = await snapshotProfiles(); + applyDefault(variables.profileId, false); + return context; + }, + onError: (error, _variables, context) => { + rollbackProfiles(context); + toastError(error); + }, + onSettled: invalidateProfiles, + }), + organizationId + ) + ); + + const setSkillEnabled = useMutation( + withOrganization( + trpc.agentProfiles.setSkillEnabled.mutationOptions({ + ...callbacks, + onMutate: async variables => { + await queryClient.cancelQueries({ queryKey: detailFilter.queryKey }); + const generation = nextMutationGeneration(SKILL_MUTATION_KEY); + const previous = queryClient.getQueriesData({ + queryKey: detailFilter.queryKey, + }); + queryClient.setQueriesData({ queryKey: detailFilter.queryKey }, old => + old ? withSkillEnabled(old, variables.skillId, variables.enabled) : old + ); + return { generation, previous }; + }, + onError: (error, _variables, context) => { + if (context && isLatestMutationGeneration(SKILL_MUTATION_KEY, context.generation)) { + for (const [key, data] of context.previous) { + queryClient.setQueryData(key, data); + } + } + toastError(error); + }, + onSettled: invalidateProfiles, + }), + organizationId + ) + ); + + return { + create, + update, + deleteProfile, + setAsDefault, + clearDefault, + setVar, + deleteVar, + setCommands, + createCustomSkill, + updateSkill, + deleteSkill, + setSkillEnabled, + }; +} diff --git a/apps/mobile/src/lib/hooks/use-agent-profile-section-mutations.ts b/apps/mobile/src/lib/hooks/use-agent-profile-section-mutations.ts new file mode 100644 index 00000000000..e57803b8a5e --- /dev/null +++ b/apps/mobile/src/lib/hooks/use-agent-profile-section-mutations.ts @@ -0,0 +1,238 @@ +import { useMutation, useQueryClient } from '@tanstack/react-query'; +import { toast } from 'sonner-native'; + +import { withOrganization } from '@/lib/hooks/agent-profile-mutation-helpers'; +import { type AgentProfileDetail } from '@/lib/hooks/agent-profile-types'; +import { + isLatestMutationGeneration, + nextMutationGeneration, +} from '@/lib/hooks/mutation-generations'; +import { useTRPC } from '@/lib/trpc'; + +// Optimistic writes on the same cache region are generation-stamped so an older +// mutation's rollback cannot stomp a newer mutation's optimistic value. +const MCP_MUTATION_KEY = 'agentProfiles:mcp'; +const KILO_COMMAND_MUTATION_KEY = 'agentProfiles:kiloCommand'; + +function withMcpEnabled( + profile: AgentProfileDetail, + mcpServerId: string, + enabled: boolean +): AgentProfileDetail { + return { + ...profile, + mcpServers: profile.mcpServers.map(server => + server.id === mcpServerId ? { ...server, enabled } : server + ), + }; +} + +function withKiloCommandEnabled( + profile: AgentProfileDetail, + commandId: string, + enabled: boolean +): AgentProfileDetail { + return { + ...profile, + kiloCommands: profile.kiloCommands.map(command => + command.id === commandId ? { ...command, enabled } : command + ), + }; +} + +/** + * Reorder the local kilo commands to match `orderedIds`. Ids the server no + * longer has keep their relative position at the end, so a stale client list + * can never drop a command from the cache. + */ +function withKiloCommandOrder( + profile: AgentProfileDetail, + orderedIds: readonly string[] +): AgentProfileDetail { + const byId = new Map(profile.kiloCommands.map(command => [command.id, command])); + const next = orderedIds.flatMap(id => { + const command = byId.get(id); + if (command === undefined) { + return []; + } + byId.delete(id); + return [command]; + }); + return { ...profile, kiloCommands: [...next, ...byId.values()] }; +} + +/** + * The MCP server, agent, and kilo (slash) command mutations. Each carries the + * hook's organization context, toasts a failed server call exactly once, and + * invalidates the `agentProfiles` namespace so the list and detail queries + * reconcile with server truth. + * + * The enabled toggles and the kilo-command reorder are optimistic: they snapshot + * the detail cache in `onMutate`, roll back in `onError` (latest generation + * only), and invalidate again in `onSettled` to reconcile. + */ +export function useAgentProfileSectionMutations(organizationId?: string) { + const trpc = useTRPC(); + const queryClient = useQueryClient(); + + const profilesFilter = trpc.agentProfiles.pathFilter(); + const detailFilter = trpc.agentProfiles.get.pathFilter(); + + const invalidateProfiles = () => { + void queryClient.invalidateQueries(profilesFilter); + }; + const toastError = (error: { message: string }) => { + toast.error(error.message); + }; + const callbacks = { + onSuccess: invalidateProfiles, + onError: (error: { message: string }) => { + toastError(error); + }, + }; + + const createMcp = useMutation( + withOrganization(trpc.agentProfiles.createMcp.mutationOptions(callbacks), organizationId) + ); + const updateMcp = useMutation( + withOrganization(trpc.agentProfiles.updateMcp.mutationOptions(callbacks), organizationId) + ); + const deleteMcp = useMutation( + withOrganization(trpc.agentProfiles.deleteMcp.mutationOptions(callbacks), organizationId) + ); + const createAgent = useMutation( + withOrganization(trpc.agentProfiles.createAgent.mutationOptions(callbacks), organizationId) + ); + const updateAgent = useMutation( + withOrganization(trpc.agentProfiles.updateAgent.mutationOptions(callbacks), organizationId) + ); + const deleteAgent = useMutation( + withOrganization(trpc.agentProfiles.deleteAgent.mutationOptions(callbacks), organizationId) + ); + const createKiloCommand = useMutation( + withOrganization( + trpc.agentProfiles.createKiloCommand.mutationOptions(callbacks), + organizationId + ) + ); + const updateKiloCommand = useMutation( + withOrganization( + trpc.agentProfiles.updateKiloCommand.mutationOptions(callbacks), + organizationId + ) + ); + const deleteKiloCommand = useMutation( + withOrganization( + trpc.agentProfiles.deleteKiloCommand.mutationOptions(callbacks), + organizationId + ) + ); + + const setMcpEnabled = useMutation( + withOrganization( + trpc.agentProfiles.setMcpEnabled.mutationOptions({ + ...callbacks, + onMutate: async variables => { + await queryClient.cancelQueries({ queryKey: detailFilter.queryKey }); + const generation = nextMutationGeneration(MCP_MUTATION_KEY); + const previous = queryClient.getQueriesData({ + queryKey: detailFilter.queryKey, + }); + queryClient.setQueriesData({ queryKey: detailFilter.queryKey }, old => + old ? withMcpEnabled(old, variables.mcpServerId, variables.enabled) : old + ); + return { generation, previous }; + }, + onError: (error, _variables, context) => { + if (context && isLatestMutationGeneration(MCP_MUTATION_KEY, context.generation)) { + for (const [key, data] of context.previous) { + queryClient.setQueryData(key, data); + } + } + toastError(error); + }, + onSettled: invalidateProfiles, + }), + organizationId + ) + ); + + const setKiloCommandEnabled = useMutation( + withOrganization( + trpc.agentProfiles.setKiloCommandEnabled.mutationOptions({ + ...callbacks, + onMutate: async variables => { + await queryClient.cancelQueries({ queryKey: detailFilter.queryKey }); + const generation = nextMutationGeneration(KILO_COMMAND_MUTATION_KEY); + const previous = queryClient.getQueriesData({ + queryKey: detailFilter.queryKey, + }); + queryClient.setQueriesData({ queryKey: detailFilter.queryKey }, old => + old ? withKiloCommandEnabled(old, variables.commandId, variables.enabled) : old + ); + return { generation, previous }; + }, + onError: (error, _variables, context) => { + if ( + context && + isLatestMutationGeneration(KILO_COMMAND_MUTATION_KEY, context.generation) + ) { + for (const [key, data] of context.previous) { + queryClient.setQueryData(key, data); + } + } + toastError(error); + }, + onSettled: invalidateProfiles, + }), + organizationId + ) + ); + + const reorderKiloCommands = useMutation( + withOrganization( + trpc.agentProfiles.reorderKiloCommands.mutationOptions({ + ...callbacks, + onMutate: async variables => { + await queryClient.cancelQueries({ queryKey: detailFilter.queryKey }); + const generation = nextMutationGeneration(KILO_COMMAND_MUTATION_KEY); + const previous = queryClient.getQueriesData({ + queryKey: detailFilter.queryKey, + }); + queryClient.setQueriesData({ queryKey: detailFilter.queryKey }, old => + old ? withKiloCommandOrder(old, variables.orderedIds) : old + ); + return { generation, previous }; + }, + onError: (error, _variables, context) => { + if ( + context && + isLatestMutationGeneration(KILO_COMMAND_MUTATION_KEY, context.generation) + ) { + for (const [key, data] of context.previous) { + queryClient.setQueryData(key, data); + } + } + toastError(error); + }, + onSettled: invalidateProfiles, + }), + organizationId + ) + ); + + return { + createMcp, + updateMcp, + deleteMcp, + setMcpEnabled, + createAgent, + updateAgent, + deleteAgent, + createKiloCommand, + updateKiloCommand, + deleteKiloCommand, + setKiloCommandEnabled, + reorderKiloCommands, + }; +} diff --git a/apps/mobile/src/lib/hooks/use-agent-profiles.mounted.test.tsx b/apps/mobile/src/lib/hooks/use-agent-profiles.mounted.test.tsx new file mode 100644 index 00000000000..8c3d1821bcb --- /dev/null +++ b/apps/mobile/src/lib/hooks/use-agent-profiles.mounted.test.tsx @@ -0,0 +1,569 @@ +/* eslint-disable max-lines -- mounted hook integration; the read, mutation, and optimistic contracts exceed the default line limit */ +import { createElement } from 'react'; +import { QueryClientProvider } from '@tanstack/react-query'; +import { act } from '@/test/renderer'; +import { beforeEach, describe, expect, it, vi } from 'vitest'; + +import { createTestQueryClient, renderWithProviders, waitFor } from '@/test/render-with-providers'; +import { + type AgentProfileListItem, + useAgentProfile, + useAgentProfileList, + useAgentProfileMutations, +} from '@/lib/hooks/use-agent-profiles'; + +// ── Hoisted mocks ────────────────────────────────────────────────────────── + +const mocks = vi.hoisted(() => ({ + queries: { + list: vi.fn(), + listCombined: vi.fn(), + get: vi.fn(), + }, + queryInputs: { + list: undefined as unknown, + listCombined: undefined as unknown, + get: undefined as unknown, + }, + mutations: { + create: vi.fn(), + update: vi.fn(), + delete: vi.fn(), + setAsDefault: vi.fn(), + clearDefault: vi.fn(), + setVar: vi.fn(), + deleteVar: vi.fn(), + setCommands: vi.fn(), + createCustomSkill: vi.fn(), + updateSkill: vi.fn(), + deleteSkill: vi.fn(), + setSkillEnabled: vi.fn(), + }, + toastError: vi.fn(), +})); + +vi.mock('@/lib/trpc', () => { + function queryProcedure(name: keyof typeof mocks.queries, path: string) { + return { + pathFilter: () => ({ queryKey: ['agentProfiles', path] }), + queryKey: (input: unknown) => ['agentProfiles', path, input], + queryOptions: (input: unknown) => { + mocks.queryInputs[name] = input; + return { + queryKey: ['agentProfiles', path, input], + queryFn: () => mocks.queries[name](), + }; + }, + }; + } + function mutationProcedure(name: keyof typeof mocks.mutations, path: string) { + return { + pathFilter: () => ({ queryKey: ['agentProfiles', path] }), + mutationOptions: (options: Record) => ({ + ...options, + mutationKey: ['agentProfiles', path], + mutationFn: (input: unknown) => mocks.mutations[name](input), + }), + }; + } + const trpc = { + agentProfiles: { + pathFilter: () => ({ queryKey: ['agentProfiles'] }), + list: queryProcedure('list', 'list'), + listCombined: queryProcedure('listCombined', 'listCombined'), + get: queryProcedure('get', 'get'), + create: mutationProcedure('create', 'create'), + update: mutationProcedure('update', 'update'), + delete: mutationProcedure('delete', 'delete'), + setAsDefault: mutationProcedure('setAsDefault', 'setAsDefault'), + clearDefault: mutationProcedure('clearDefault', 'clearDefault'), + setVar: mutationProcedure('setVar', 'setVar'), + deleteVar: mutationProcedure('deleteVar', 'deleteVar'), + setCommands: mutationProcedure('setCommands', 'setCommands'), + createCustomSkill: mutationProcedure('createCustomSkill', 'createCustomSkill'), + updateSkill: mutationProcedure('updateSkill', 'updateSkill'), + deleteSkill: mutationProcedure('deleteSkill', 'deleteSkill'), + setSkillEnabled: mutationProcedure('setSkillEnabled', 'setSkillEnabled'), + }, + }; + return { useTRPC: () => trpc }; +}); + +vi.mock('sonner-native', () => ({ toast: { error: mocks.toastError } })); + +// ── Helpers ──────────────────────────────────────────────────────────────── + +function summary(overrides: Record = {}): Record { + return { + id: 'profile-1', + name: 'Profile', + description: null, + isDefault: false, + createdAt: '2026-01-01T00:00:00.000Z', + updatedAt: '2026-01-01T00:00:00.000Z', + varCount: 0, + commandCount: 0, + mcpServerCount: 0, + skillCount: 0, + agentCount: 0, + kiloCommandCount: 0, + ...overrides, + }; +} + +type ListResult = ReturnType; +type MutationsResult = ReturnType; + +function ListProbe({ + holder, + organizationId, +}: { + holder: { current: ListResult | null }; + organizationId?: string; +}) { + holder.current = useAgentProfileList(organizationId); + return null; +} + +function MutationsProbe({ + holder, + organizationId, +}: { + holder: { current: MutationsResult | null }; + organizationId?: string; +}) { + holder.current = useAgentProfileMutations(organizationId); + return null; +} + +function ProfileProbe({ + holder, + profileId, + organizationId, +}: { + holder: { current: ReturnType | null }; + profileId: string; + organizationId?: string; +}) { + holder.current = useAgentProfile(profileId, organizationId); + return null; +} + +function current(holder: { current: T | null }): T { + const result = holder.current; + if (!result) { + throw new Error('probe did not render'); + } + return result; +} + +// ── Tests ────────────────────────────────────────────────────────────────── + +describe('useAgentProfileList', () => { + beforeEach(() => { + mocks.queries.list.mockReset().mockResolvedValue([]); + mocks.queries.listCombined.mockReset().mockResolvedValue({ + orgProfiles: [], + personalProfiles: [], + effectiveDefaultId: null, + }); + mocks.queries.get.mockReset().mockResolvedValue({}); + mocks.queryInputs.list = undefined; + mocks.queryInputs.listCombined = undefined; + mocks.queryInputs.get = undefined; + }); + + it('personal context reads list and exposes personalProfiles with the personal default', async () => { + mocks.queries.list.mockResolvedValue([ + summary({ id: 'personal-1', isDefault: false }), + summary({ id: 'personal-2', isDefault: true }), + ]); + + const holder: { current: ListResult | null } = { current: null }; + const { unmount } = await renderWithProviders(createElement(ListProbe, { holder })); + + await waitFor(() => current(holder).personalProfiles.length === 2); + + expect(current(holder).personalProfiles.map(profile => profile.id)).toEqual([ + 'personal-1', + 'personal-2', + ]); + expect(current(holder).orgProfiles).toEqual([]); + expect(current(holder).effectiveDefaultId).toBe('personal-2'); + expect(current(holder).isError).toBe(false); + expect(mocks.queries.listCombined).not.toHaveBeenCalled(); + unmount(); + }); + + it('org context reads listCombined and surfaces effectiveDefaultId', async () => { + mocks.queries.listCombined.mockResolvedValue({ + orgProfiles: [summary({ id: 'org-1', ownerType: 'organization' })], + personalProfiles: [summary({ id: 'personal-1', ownerType: 'user', isDefault: true })], + effectiveDefaultId: 'personal-1', + }); + + const holder: { current: ListResult | null } = { current: null }; + const { unmount } = await renderWithProviders( + createElement(ListProbe, { holder, organizationId: 'org-1' }) + ); + + await waitFor(() => current(holder).effectiveDefaultId === 'personal-1'); + + expect(current(holder).orgProfiles.map(profile => profile.id)).toEqual(['org-1']); + expect(current(holder).personalProfiles.map(profile => profile.id)).toEqual(['personal-1']); + expect(mocks.queries.list).not.toHaveBeenCalled(); + unmount(); + }); + + it('starts empty on an organization switch instead of keeping the prior rows', async () => { + type Combined = { + orgProfiles: Record[]; + personalProfiles: Record[]; + effectiveDefaultId: string | null; + }; + const first = Promise.withResolvers(); + const second = Promise.withResolvers(); + mocks.queries.listCombined + .mockReturnValueOnce(first.promise) + .mockReturnValueOnce(second.promise); + + const holder: { current: ListResult | null } = { current: null }; + const { renderer, queryClient, unmount } = await renderWithProviders( + createElement(ListProbe, { holder, organizationId: 'org-1' }) + ); + + first.resolve({ + orgProfiles: [summary({ id: 'org-1', ownerType: 'organization' })], + personalProfiles: [], + effectiveDefaultId: null, + }); + await waitFor(() => current(holder).orgProfiles.length === 1); + + // Switch context while the new organization's read is still pending. The + // previous organization's rows must not render under the new scope. + await act(async () => { + renderer.update( + createElement( + QueryClientProvider, + { client: queryClient }, + createElement(ListProbe, { holder, organizationId: 'org-2' }) + ) + ); + await Promise.resolve(); + }); + + expect(current(holder).orgProfiles).toEqual([]); + expect(current(holder).isLoading).toBe(true); + + second.resolve({ + orgProfiles: [summary({ id: 'org-2', ownerType: 'organization' })], + personalProfiles: [], + effectiveDefaultId: null, + }); + await waitFor( + () => + current(holder) + .orgProfiles.map(profile => profile.id) + .join(',') === 'org-2' + ); + unmount(); + }); +}); + +describe('useAgentProfile', () => { + it('reads get with the profile id and the organization context', async () => { + mocks.queries.get.mockResolvedValue({ id: 'profile-1', name: 'Org profile' }); + + const holder: { current: ReturnType | null } = { current: null }; + const { unmount } = await renderWithProviders( + createElement(ProfileProbe, { holder, profileId: 'profile-1', organizationId: 'org-1' }) + ); + + await waitFor(() => current(holder).data !== undefined); + expect(mocks.queryInputs.get).toEqual({ profileId: 'profile-1', organizationId: 'org-1' }); + unmount(); + }); +}); + +describe('useAgentProfileMutations', () => { + beforeEach(() => { + for (const mutation of Object.values(mocks.mutations)) { + mutation.mockReset().mockResolvedValue({ success: true }); + } + mocks.toastError.mockReset(); + }); + + it('passes organizationId to the procedure and invalidates the agentProfiles key', async () => { + const holder: { current: MutationsResult | null } = { current: null }; + const { queryClient, unmount } = await renderWithProviders( + createElement(MutationsProbe, { holder, organizationId: 'org-1' }) + ); + const invalidateSpy = vi.spyOn(queryClient, 'invalidateQueries'); + + await act(async () => { + await current(holder).setVar.mutateAsync({ + profileId: 'profile-1', + key: 'API_KEY', + value: 'secret-value', + isSecret: false, + }); + }); + + expect(mocks.mutations.setVar).toHaveBeenCalledTimes(1); + expect(mocks.mutations.setVar).toHaveBeenCalledWith({ + profileId: 'profile-1', + organizationId: 'org-1', + key: 'API_KEY', + value: 'secret-value', + isSecret: false, + }); + await waitFor(() => invalidateSpy.mock.calls.length > 0); + expect(invalidateSpy).toHaveBeenCalledWith({ queryKey: ['agentProfiles'] }); + unmount(); + }); + + it('leaves organizationId off personal-context input', async () => { + const holder: { current: MutationsResult | null } = { current: null }; + const { unmount } = await renderWithProviders(createElement(MutationsProbe, { holder })); + + await act(async () => { + await current(holder).create.mutateAsync({ name: 'Backend debugging' }); + }); + + expect(mocks.mutations.create).toHaveBeenCalledWith({ name: 'Backend debugging' }); + unmount(); + }); + + it('toasts a rejected mutation once with the server message', async () => { + mocks.mutations.create.mockRejectedValue(new Error('Profiles are limited to 50')); + + const holder: { current: MutationsResult | null } = { current: null }; + const { unmount } = await renderWithProviders(createElement(MutationsProbe, { holder })); + + await act(async () => { + await expect(current(holder).create.mutateAsync({ name: 'Too many' })).rejects.toThrow( + 'Profiles are limited to 50' + ); + }); + + await waitFor(() => mocks.toastError.mock.calls.length > 0); + expect(mocks.toastError).toHaveBeenCalledTimes(1); + expect(mocks.toastError).toHaveBeenCalledWith('Profiles are limited to 50'); + unmount(); + }); + + it('setAsDefault applies optimistically and rolls back when the server rejects', async () => { + let rejectMutation: ((error: Error) => void) | undefined = undefined; + mocks.mutations.setAsDefault.mockReturnValue( + new Promise((_resolve, reject: (error: Error) => void) => { + rejectMutation = reject; + }) + ); + + const queryClient = createTestQueryClient(); + const listKey = ['agentProfiles', 'list', {}]; + queryClient.setQueryData(listKey, [ + summary({ id: 'personal-1', isDefault: true }), + summary({ id: 'personal-2', isDefault: false }), + ] as AgentProfileListItem[]); + + const holder: { current: MutationsResult | null } = { current: null }; + const { unmount } = await renderWithProviders(createElement(MutationsProbe, { holder }), { + queryClient, + }); + + let pending: Promise | undefined = undefined; + act(() => { + pending = current(holder).setAsDefault.mutateAsync({ profileId: 'personal-2' }); + }); + + // Optimistic write is visible while the mutation is still in flight. + await waitFor(() => { + const data = queryClient.getQueryData(listKey); + return data?.map(profile => profile.isDefault).join(',') === 'false,true'; + }); + const optimistic = queryClient.getQueryData(listKey); + expect(optimistic?.map(profile => profile.isDefault)).toEqual([false, true]); + + await act(async () => { + rejectMutation?.(new Error('Could not set default')); + await pending?.catch(() => undefined); + }); + + const rolledBack = queryClient.getQueryData(listKey); + expect(rolledBack?.map(profile => profile.isDefault)).toEqual([true, false]); + expect(mocks.toastError).toHaveBeenCalledTimes(1); + unmount(); + }); + + it.each([ + { + organizationId: 'org-1', + clear: false, + effective: 'personal', + personalDefault: true, + orgDefault: true, + }, + { + organizationId: 'org-1', + clear: true, + effective: 'personal', + personalDefault: true, + orgDefault: false, + }, + { + organizationId: undefined, + clear: true, + effective: 'org', + personalDefault: false, + orgDefault: true, + }, + { + organizationId: undefined, + clear: false, + effective: 'personal', + personalDefault: true, + orgDefault: true, + }, + ])( + 'scopes optimistic defaults to their owner: $organizationId clear=$clear', + async ({ organizationId, clear, effective, personalDefault, orgDefault }) => { + const holder: { current: MutationsResult | null } = { current: null }; + const { queryClient, unmount } = await renderWithProviders( + createElement(MutationsProbe, { holder, organizationId }) + ); + const personal = summary({ id: 'personal', isDefault: true, ownerType: 'user' }); + const org = summary({ id: 'org', isDefault: true, ownerType: 'organization' }); + const combinedKey = ['agentProfiles', 'listCombined', { organizationId: 'org-1' }]; + const otherKey = ['agentProfiles', 'listCombined', { organizationId: 'org-2' }]; + const combined = { + personalProfiles: [personal], + orgProfiles: [org], + effectiveDefaultId: 'personal', + }; + queryClient.setQueryData(combinedKey, combined); + queryClient.setQueryData(otherKey, { + ...combined, + orgProfiles: [summary({ id: 'other', isDefault: true })], + }); + const listKey = ['agentProfiles', 'list', { organizationId }]; + const untouchedListKey = ['agentProfiles', 'list', { organizationId: 'org-2' }]; + const detailKey = ['agentProfiles', 'get', { profileId: 'other', organizationId: 'org-2' }]; + queryClient.setQueryData(listKey, [organizationId ? org : personal]); + queryClient.setQueryData(untouchedListKey, [summary({ id: 'other', isDefault: true })]); + queryClient.setQueryData(detailKey, summary({ id: 'other', isDefault: true })); + await act(async () => { + await (clear + ? current(holder).clearDefault.mutateAsync({ + profileId: organizationId ? 'org' : 'personal', + }) + : current(holder).setAsDefault.mutateAsync({ + profileId: organizationId ? 'org' : 'personal', + })); + }); + expect(queryClient.getQueryData(combinedKey)).toMatchObject({ + effectiveDefaultId: effective, + personalProfiles: [{ isDefault: personalDefault }], + orgProfiles: [{ isDefault: orgDefault }], + }); + expect(queryClient.getQueryData(untouchedListKey)).toMatchObject([{ isDefault: true }]); + expect(queryClient.getQueryData(detailKey)).toMatchObject({ isDefault: true }); + expect(queryClient.getQueryData(otherKey)).toMatchObject({ + orgProfiles: [{ isDefault: true }], + personalProfiles: [{ isDefault: organizationId ? true : personalDefault }], + effectiveDefaultId: !organizationId && clear ? 'other' : 'personal', + }); + unmount(); + } + ); + + it('clearing a non-default leaves the same owner current default intact', async () => { + const holder: { current: MutationsResult | null } = { current: null }; + const { queryClient, unmount } = await renderWithProviders( + createElement(MutationsProbe, { holder }) + ); + const listKey = ['agentProfiles', 'list', {}]; + const profiles = [summary({ id: 'default', isDefault: true }), summary({ id: 'other' })]; + queryClient.setQueryData(listKey, profiles); + await act(async () => { + await current(holder).clearDefault.mutateAsync({ profileId: 'other' }); + }); + expect(queryClient.getQueryData(listKey)).toEqual(profiles); + unmount(); + }); + + it('rolls back an organization default without touching another organization cache', async () => { + const pending = Promise.withResolvers<{ success: boolean }>(); + mocks.mutations.setAsDefault.mockReturnValueOnce(pending.promise); + const holder: { current: MutationsResult | null } = { current: null }; + const { queryClient, unmount } = await renderWithProviders( + createElement(MutationsProbe, { holder, organizationId: 'org-1' }) + ); + const key = ['agentProfiles', 'listCombined', { organizationId: 'org-1' }]; + const otherKey = ['agentProfiles', 'listCombined', { organizationId: 'org-2' }]; + const original = { + personalProfiles: [], + orgProfiles: [summary({ id: 'previous', isDefault: true }), summary({ id: 'next' })], + effectiveDefaultId: 'previous', + }; + queryClient.setQueryData(key, original); + queryClient.setQueryData(otherKey, original); + let request: Promise | undefined = undefined; + act(() => { + request = current(holder).setAsDefault.mutateAsync({ profileId: 'next' }); + }); + await waitFor(() => mocks.mutations.setAsDefault.mock.calls.length > 0); + expect(queryClient.getQueryData(key)).toMatchObject({ + orgProfiles: [{ isDefault: false }, { isDefault: true }], + effectiveDefaultId: 'next', + }); + const refreshed = { ...original, effectiveDefaultId: null, orgProfiles: [] }; + queryClient.setQueryData(otherKey, refreshed); + await act(async () => { + pending.reject(new Error('Try again')); + await request?.catch(() => undefined); + }); + expect(queryClient.getQueryData(key)).toEqual(original); + expect(queryClient.getQueryData(otherKey)).toEqual(refreshed); + expect(mocks.toastError).toHaveBeenCalledWith('Try again'); + unmount(); + }); + + it('setSkillEnabled applies optimistically to the profile detail', async () => { + mocks.mutations.setSkillEnabled.mockResolvedValue({ success: true }); + + const queryClient = createTestQueryClient(); + const detailKey = ['agentProfiles', 'get', { profileId: 'profile-1' }]; + queryClient.setQueryData(detailKey, { + id: 'profile-1', + name: 'Profile', + isDefault: false, + skills: [ + { id: 'skill-1', name: 'one', enabled: true }, + { id: 'skill-2', name: 'two', enabled: false }, + ], + }); + + const holder: { current: MutationsResult | null } = { current: null }; + const { unmount } = await renderWithProviders(createElement(MutationsProbe, { holder }), { + queryClient, + }); + + await act(async () => { + await current(holder).setSkillEnabled.mutateAsync({ + profileId: 'profile-1', + skillId: 'skill-2', + enabled: true, + }); + }); + + const detail = queryClient.getQueryData<{ + skills: { id: string; enabled: boolean }[]; + }>(detailKey); + expect(detail?.skills.map(skill => [skill.id, skill.enabled])).toEqual([ + ['skill-1', true], + ['skill-2', true], + ]); + unmount(); + }); +}); diff --git a/apps/mobile/src/lib/hooks/use-agent-profiles.ts b/apps/mobile/src/lib/hooks/use-agent-profiles.ts new file mode 100644 index 00000000000..554d9b84c8f --- /dev/null +++ b/apps/mobile/src/lib/hooks/use-agent-profiles.ts @@ -0,0 +1,105 @@ +import { useQuery } from '@tanstack/react-query'; +import { useMemo } from 'react'; + +import { splitProfilesByOwner } from '@/components/profiles/profile-owner-model'; +import { + type AgentProfileDetail, + type AgentProfileListItem, +} from '@/lib/hooks/agent-profile-types'; +import { useTRPC } from '@/lib/trpc'; + +export type { AgentProfileDetail, AgentProfileListItem }; +export { useAgentProfileMutations } from '@/lib/hooks/use-agent-profile-mutations'; +export { useAgentProfileSectionMutations } from '@/lib/hooks/use-agent-profile-section-mutations'; + +/** + * Keep the previous rows across a refetch of the SAME query key. React Query's + * `keepPreviousData` also keeps them across a query-key change, so switching + * organization would render the previous organization's rows while the + * actions already use the newly selected scope. Comparing the previous query's + * key to the current one drops the placeholder the moment the input changes and + * still keeps the rows across a same-input refetch. + * + * Exported so every context-scoped list hook shares the guard; `useRepoBindings` + * has the same organization switch to survive. + */ +export function keepPreviousDataForQueryKey( + queryKey: readonly unknown[] +): ( + previousData: TQueryData | undefined, + previousQuery: { queryKey: readonly unknown[] } | undefined +) => TQueryData | undefined { + const currentKey = JSON.stringify(queryKey); + return (previousData, previousQuery) => + previousQuery !== undefined && JSON.stringify(previousQuery.queryKey) === currentKey + ? previousData + : undefined; +} + +/** + * List the profiles for a context. Personal context reads `list`; org context + * reads `listCombined`, which groups org and personal profiles and already + * resolves `effectiveDefaultId` (personal default > org default). + * + * `placeholderData` keeps the previous rows across a refetch of the same key, + * so the list never blanks while a refetch is in flight; a different key (an + * organization switch) starts empty instead of showing another scope's rows. + * + * `isLoading` is `isPending`, not React Query's `isLoading`: in v5 `isLoading` + * is `isPending && isFetching`, which is false while a paused (offline) first + * fetch is unsettled. `isPending` stays true until the query settles, so the + * screen cannot mistake an unloaded list for an empty one. + */ +export function useAgentProfileList(organizationId?: string) { + const trpc = useTRPC(); + const isOrganization = organizationId !== undefined; + + const personalOptions = trpc.agentProfiles.list.queryOptions({}); + const combinedOptions = trpc.agentProfiles.listCombined.queryOptions({ + organizationId: organizationId ?? '', + }); + + const personal = useQuery({ + ...personalOptions, + enabled: !isOrganization, + placeholderData: keepPreviousDataForQueryKey(personalOptions.queryKey), + }); + const combined = useQuery({ + ...combinedOptions, + enabled: isOrganization, + placeholderData: keepPreviousDataForQueryKey(combinedOptions.queryKey), + }); + + const query = isOrganization ? combined : personal; + // The bucket split lives in the pure ownership model, so the org/personal + // rule has one source of truth (`ProfilesListDialog.tsx:95-100`). Memoized on + // the query data: consumers memoize on the bucket identity, so a render that + // did not change the data must not mint new arrays. + const { orgProfiles, personalProfiles } = useMemo( + () => + splitProfilesByOwner({ + isOrgContext: isOrganization, + combined: combined.data, + personal: personal.data, + }), + [isOrganization, combined.data, personal.data] + ); + + return { + orgProfiles, + personalProfiles, + effectiveDefaultId: isOrganization + ? (combined.data?.effectiveDefaultId ?? null) + : (personal.data?.find(profile => profile.isDefault)?.id ?? null), + isLoading: query.isPending, + isError: query.isError, + isRefetching: query.isRefetching, + refetch: query.refetch, + }; +} + +/** A single profile with its variables, commands, MCP servers, skills and agents. */ +export function useAgentProfile(profileId: string, organizationId?: string) { + const trpc = useTRPC(); + return useQuery(trpc.agentProfiles.get.queryOptions({ profileId, organizationId })); +} diff --git a/apps/mobile/src/lib/hooks/use-repo-bindings.mounted.test.tsx b/apps/mobile/src/lib/hooks/use-repo-bindings.mounted.test.tsx new file mode 100644 index 00000000000..cca2aff96ea --- /dev/null +++ b/apps/mobile/src/lib/hooks/use-repo-bindings.mounted.test.tsx @@ -0,0 +1,135 @@ +import { createElement } from 'react'; +import { QueryClientProvider } from '@tanstack/react-query'; +import { act } from '@/test/renderer'; +import { beforeEach, describe, expect, it, vi } from 'vitest'; + +import { renderWithProviders, waitFor } from '@/test/render-with-providers'; +import { useRepoBindings } from '@/lib/hooks/use-repo-bindings'; + +// ── Hoisted mocks ────────────────────────────────────────────────────────── + +const mocks = vi.hoisted(() => ({ + listRepoBindings: vi.fn(), + inputs: undefined as unknown, +})); + +vi.mock('@/lib/trpc', () => { + const trpc = { + agentProfiles: { + pathFilter: () => ({ queryKey: ['agentProfiles'] }), + listRepoBindings: { + pathFilter: () => ({ queryKey: ['agentProfiles', 'listRepoBindings'] }), + queryKey: (input: unknown) => ['agentProfiles', 'listRepoBindings', input], + queryOptions: (input: unknown) => { + mocks.inputs = input; + return { + queryKey: ['agentProfiles', 'listRepoBindings', input], + queryFn: () => mocks.listRepoBindings(), + }; + }, + }, + bindToRepo: { + pathFilter: () => ({ queryKey: ['agentProfiles', 'listRepoBindings'] }), + mutationOptions: (options: Record) => ({ + ...options, + mutationFn: vi.fn(), + }), + }, + unbindRepo: { + pathFilter: () => ({ queryKey: ['agentProfiles', 'listRepoBindings'] }), + mutationOptions: (options: Record) => ({ + ...options, + mutationFn: vi.fn(), + }), + }, + }, + }; + return { useTRPC: () => trpc }; +}); + +vi.mock('sonner-native', () => ({ toast: { error: vi.fn() } })); + +// ── Helpers ──────────────────────────────────────────────────────────────── + +type BindingsResult = ReturnType; + +function binding(repoFullName: string): BindingsResult['bindings'][number] { + return { repoFullName, platform: 'github', profileId: 'profile-1', profileName: 'Backend' }; +} + +function BindingsProbe({ + holder, + organizationId, +}: { + holder: { current: BindingsResult | null }; + organizationId?: string; +}) { + holder.current = useRepoBindings(organizationId); + return null; +} + +function current(holder: { current: BindingsResult | null }): BindingsResult { + const result = holder.current; + if (!result) { + throw new Error('probe did not render'); + } + return result; +} + +// ── Tests ────────────────────────────────────────────────────────────────── + +describe('useRepoBindings', () => { + beforeEach(() => { + mocks.listRepoBindings.mockReset().mockResolvedValue([]); + mocks.inputs = undefined; + }); + + it('starts empty on an organization switch instead of keeping the prior rows', async () => { + const first = Promise.withResolvers(); + const second = Promise.withResolvers(); + mocks.listRepoBindings.mockReturnValueOnce(first.promise).mockReturnValueOnce(second.promise); + + const holder: { current: BindingsResult | null } = { current: null }; + const { renderer, queryClient, unmount } = await renderWithProviders( + createElement(BindingsProbe, { holder, organizationId: 'org-1' }) + ); + + first.resolve([binding('org-1/api')]); + await waitFor(() => current(holder).bindings.length === 1); + + // Switch context while the new organization's read is still pending. The + // previous organization's bindings must not render under the new scope, + // because the unbind mutation now carries the newly selected organization. + await act(async () => { + renderer.update( + createElement( + QueryClientProvider, + { client: queryClient }, + createElement(BindingsProbe, { holder, organizationId: 'org-2' }) + ) + ); + await Promise.resolve(); + }); + + expect(current(holder).bindings).toEqual([]); + expect(current(holder).isLoading).toBe(true); + + second.resolve([binding('org-2/api')]); + await waitFor( + () => + current(holder) + .bindings.map(row => row.repoFullName) + .join(',') === 'org-2/api' + ); + unmount(); + }); + + it('reads the personal context without an organizationId', async () => { + const holder: { current: BindingsResult | null } = { current: null }; + const { unmount } = await renderWithProviders(createElement(BindingsProbe, { holder })); + + await waitFor(() => mocks.listRepoBindings.mock.calls.length > 0); + expect(mocks.inputs).toEqual({}); + unmount(); + }); +}); diff --git a/apps/mobile/src/lib/hooks/use-repo-bindings.ts b/apps/mobile/src/lib/hooks/use-repo-bindings.ts new file mode 100644 index 00000000000..c54a7c7e296 --- /dev/null +++ b/apps/mobile/src/lib/hooks/use-repo-bindings.ts @@ -0,0 +1,125 @@ +import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'; +import { useMemo } from 'react'; +import { toast } from 'sonner-native'; + +import { mergeRepositoryOptions, type RepoOption } from '@/components/profiles/repo-bindings-model'; +import { withOrganization } from '@/lib/hooks/agent-profile-mutation-helpers'; +import { type AgentRepoBinding } from '@/lib/hooks/agent-profile-types'; +import { keepPreviousDataForQueryKey } from '@/lib/hooks/use-agent-profiles'; +import { useTRPC } from '@/lib/trpc'; + +/** Stable empty fallbacks so an unsettled result is not a new array each render. */ +const NO_BINDINGS: AgentRepoBinding[] = []; +const NO_REPOSITORIES: RepoOption[] = []; + +/** + * The repo bindings for a context. Personal context reads + * `listRepoBindings({})`; org context carries `organizationId`, exactly like + * every other `agentProfiles.*` input. + * + * `placeholderData` keeps the rows across a refetch of the same key, so the + * list never blanks while an unbind reconciles; a different key (an + * organization switch) starts empty instead of showing another scope's rows + * while `useRepoBindingMutations` already targets the new scope. `isLoading` is + * `isPending`, not React Query v5's `isLoading`, so an offline first fetch still + * counts as unloaded rather than empty. + */ +export function useRepoBindings(organizationId?: string) { + const trpc = useTRPC(); + const options = trpc.agentProfiles.listRepoBindings.queryOptions( + organizationId === undefined ? {} : { organizationId } + ); + const query = useQuery({ + ...options, + placeholderData: keepPreviousDataForQueryKey(options.queryKey), + }); + + return { + bindings: query.data ?? NO_BINDINGS, + isLoading: query.isPending, + isError: query.isError, + isRefetching: query.isRefetching, + refetch: query.refetch, + }; +} + +/** + * The bind and unbind mutations. Each carries the hook's organization context, + * toasts a failed server call exactly once (the screen only fills in a fallback + * when the server sent no readable message), and invalidates the + * `listRepoBindings` namespace so both the bindings list and the Overview pins + * section reconcile with server truth. + */ +export function useRepoBindingMutations(organizationId?: string) { + const trpc = useTRPC(); + const queryClient = useQueryClient(); + const bindingsFilter = trpc.agentProfiles.listRepoBindings.pathFilter(); + + const callbacks = { + onSuccess: () => { + void queryClient.invalidateQueries(bindingsFilter); + }, + onError: (error: { message: string }) => { + toast.error(error.message); + }, + }; + + const bind = useMutation( + withOrganization(trpc.agentProfiles.bindToRepo.mutationOptions(callbacks), organizationId) + ); + const unbind = useMutation( + withOrganization(trpc.agentProfiles.unbindRepo.mutationOptions(callbacks), organizationId) + ); + + return { bind, unbind }; +} + +/** + * The merged GitHub + GitLab repository options the Add form's repo picker + * shows. The queries run only while `enabled` (the picker is open), matching the + * web dialog's lazy fetch. GitHub rows come first, then GitLab. + */ +export function useRepoOptions(organizationId: string | undefined, enabled: boolean) { + const trpc = useTRPC(); + + const github = useQuery({ + ...(organizationId === undefined + ? trpc.cloudAgentNext.listGitHubRepositories.queryOptions({ forceRefresh: false }) + : trpc.organizations.cloudAgentNext.listGitHubRepositories.queryOptions({ + organizationId, + forceRefresh: false, + })), + enabled, + }); + const gitlab = useQuery({ + ...(organizationId === undefined + ? trpc.cloudAgentNext.listGitLabRepositories.queryOptions({ forceRefresh: false }) + : trpc.organizations.cloudAgentNext.listGitLabRepositories.queryOptions({ + organizationId, + forceRefresh: false, + })), + enabled, + }); + + const repositories = useMemo( + () => + enabled + ? mergeRepositoryOptions( + github.data?.repositories ?? NO_REPOSITORIES, + gitlab.data?.repositories ?? NO_REPOSITORIES + ) + : NO_REPOSITORIES, + [enabled, github.data, gitlab.data] + ); + + return { + repositories, + isLoading: enabled && (github.isPending || gitlab.isPending), + isError: github.isError || gitlab.isError, + isRefetching: github.isRefetching || gitlab.isRefetching, + refetch: () => { + void github.refetch(); + void gitlab.refetch(); + }, + }; +} diff --git a/apps/mobile/src/lib/picker-bridge.ts b/apps/mobile/src/lib/picker-bridge.ts index dc30d6a79e9..7cfd2c1ab2a 100644 --- a/apps/mobile/src/lib/picker-bridge.ts +++ b/apps/mobile/src/lib/picker-bridge.ts @@ -106,6 +106,20 @@ export type InstancePickerBridge = { onSelect: (instance: InstancePickerInstance | null) => void; }; +/** + * Bridge for the new-session profile picker. `selectedOverrideProfileId` is the + * pick the form already holds (null for the effective default), and + * `repoBindingProfileId` is the base layer when the caller knows the target + * repo. The picker re-queries its scope so the list is fresh and a failure can + * offer Retry. + */ +export type ProfilePickerBridge = { + organizationId: string | undefined; + selectedOverrideProfileId: string | null; + repoBindingProfileId: string | null; + onSelect: (id: string | null) => void; +}; + /** * Bridge for the new-session folder picker. `currentPath` is `""` at launch * (the CLI's launch directory) and a relative path once the user has drilled diff --git a/apps/mobile/src/lib/profile-agent-navigation.test.ts b/apps/mobile/src/lib/profile-agent-navigation.test.ts index db1c1c28470..9c9fe85bb21 100644 --- a/apps/mobile/src/lib/profile-agent-navigation.test.ts +++ b/apps/mobile/src/lib/profile-agent-navigation.test.ts @@ -1,6 +1,17 @@ import { describe, expect, it } from 'vitest'; -import { getCodeReviewerProfilePath, getProfileAgentScope } from '@/lib/profile-agent-navigation'; +import { + getCodeReviewerProfilePath, + getProfileAgentScope, + getProfileAgentsPath, + getProfileCommandsPath, + getProfileMcpPath, + getProfileOverviewPath, + getProfileSkillsPath, + getProfileSlashCommandsPath, + getProfilesPath, + getProfileVariablesPath, +} from '@/lib/profile-agent-navigation'; import { getSecurityAgentPath } from '@/lib/security-agent'; const organizations = [{ organizationId: 'org-1' }, { organizationId: 'org-2' }]; @@ -41,3 +52,55 @@ describe('Profile agent paths', () => { expect(getSecurityAgentPath('org-1')).toBe('/(app)/(tabs)/(3_profile)/security-agent/org-1'); }); }); + +describe('Profile management paths', () => { + it('builds the Manage Profiles entry path', () => { + expect(getProfilesPath()).toBe('/(app)/(tabs)/(3_profile)/profiles'); + }); + + it('builds each personal profile tab path', () => { + expect(getProfileOverviewPath('profile-1')).toBe( + '/(app)/(tabs)/(3_profile)/profiles/profile-1' + ); + expect(getProfileVariablesPath('profile-1')).toBe( + '/(app)/(tabs)/(3_profile)/profiles/profile-1/variables' + ); + expect(getProfileCommandsPath('profile-1')).toBe( + '/(app)/(tabs)/(3_profile)/profiles/profile-1/commands' + ); + expect(getProfileSlashCommandsPath('profile-1')).toBe( + '/(app)/(tabs)/(3_profile)/profiles/profile-1/slash-commands' + ); + expect(getProfileMcpPath('profile-1')).toBe('/(app)/(tabs)/(3_profile)/profiles/profile-1/mcp'); + expect(getProfileAgentsPath('profile-1')).toBe( + '/(app)/(tabs)/(3_profile)/profiles/profile-1/agents' + ); + expect(getProfileSkillsPath('profile-1')).toBe( + '/(app)/(tabs)/(3_profile)/profiles/profile-1/skills' + ); + }); + + it('carries the organization id on each organization profile tab path', () => { + expect(getProfileOverviewPath('profile-1', 'org-1')).toBe( + '/(app)/(tabs)/(3_profile)/profiles/profile-1?organizationId=org-1' + ); + expect(getProfileVariablesPath('profile-1', 'org-1')).toBe( + '/(app)/(tabs)/(3_profile)/profiles/profile-1/variables?organizationId=org-1' + ); + expect(getProfileCommandsPath('profile-1', 'org-1')).toBe( + '/(app)/(tabs)/(3_profile)/profiles/profile-1/commands?organizationId=org-1' + ); + expect(getProfileSlashCommandsPath('profile-1', 'org-1')).toBe( + '/(app)/(tabs)/(3_profile)/profiles/profile-1/slash-commands?organizationId=org-1' + ); + expect(getProfileMcpPath('profile-1', 'org-1')).toBe( + '/(app)/(tabs)/(3_profile)/profiles/profile-1/mcp?organizationId=org-1' + ); + expect(getProfileAgentsPath('profile-1', 'org-1')).toBe( + '/(app)/(tabs)/(3_profile)/profiles/profile-1/agents?organizationId=org-1' + ); + expect(getProfileSkillsPath('profile-1', 'org-1')).toBe( + '/(app)/(tabs)/(3_profile)/profiles/profile-1/skills?organizationId=org-1' + ); + }); +}); diff --git a/apps/mobile/src/lib/profile-agent-navigation.ts b/apps/mobile/src/lib/profile-agent-navigation.ts index 90504e0ca5b..5239025db5f 100644 --- a/apps/mobile/src/lib/profile-agent-navigation.ts +++ b/apps/mobile/src/lib/profile-agent-navigation.ts @@ -26,3 +26,57 @@ export function getCodeReviewerProfilePath(scope: string): Href { export function getPrReviewEntryPath(): Href { return '/(app)/pr-review' as Href; } + +/** + * Profile context carried by a profile route. Org-owned profiles travel with + * their `organizationId`; a personal profile has none, exactly like the tRPC + * `agentProfiles.*` inputs. + */ +function profileContextQuery(organizationId?: string): string { + return organizationId ? `?organizationId=${encodeURIComponent(organizationId)}` : ''; +} + +/** The Manage Profiles entry on the Profile tab. */ +export function getProfilesPath(): Href { + return '/(app)/(tabs)/(3_profile)/profiles' as Href; +} + +/** The repo-to-profile default bindings screen. */ +export function getRepoBindingsPath(organizationId?: string): Href { + return `/(app)/(tabs)/(3_profile)/profiles/repo-bindings${profileContextQuery(organizationId)}` as Href; +} + +/** The profile editor's Overview tab (the profile's own route). */ +export function getProfileOverviewPath(profileId: string, organizationId?: string): Href { + return `/(app)/(tabs)/(3_profile)/profiles/${profileId}${profileContextQuery(organizationId)}` as Href; +} + +/** The profile editor's Variables tab. */ +export function getProfileVariablesPath(profileId: string, organizationId?: string): Href { + return `/(app)/(tabs)/(3_profile)/profiles/${profileId}/variables${profileContextQuery(organizationId)}` as Href; +} + +/** The profile editor's Setup Commands tab. */ +export function getProfileCommandsPath(profileId: string, organizationId?: string): Href { + return `/(app)/(tabs)/(3_profile)/profiles/${profileId}/commands${profileContextQuery(organizationId)}` as Href; +} + +/** The profile editor's Slash Commands tab. */ +export function getProfileSlashCommandsPath(profileId: string, organizationId?: string): Href { + return `/(app)/(tabs)/(3_profile)/profiles/${profileId}/slash-commands${profileContextQuery(organizationId)}` as Href; +} + +/** The profile editor's MCP Servers tab. */ +export function getProfileMcpPath(profileId: string, organizationId?: string): Href { + return `/(app)/(tabs)/(3_profile)/profiles/${profileId}/mcp${profileContextQuery(organizationId)}` as Href; +} + +/** The profile editor's Agents tab. */ +export function getProfileAgentsPath(profileId: string, organizationId?: string): Href { + return `/(app)/(tabs)/(3_profile)/profiles/${profileId}/agents${profileContextQuery(organizationId)}` as Href; +} + +/** The profile editor's Skills tab. */ +export function getProfileSkillsPath(profileId: string, organizationId?: string): Href { + return `/(app)/(tabs)/(3_profile)/profiles/${profileId}/skills${profileContextQuery(organizationId)}` as Href; +} diff --git a/apps/mobile/src/lib/profile-count-labels.test.ts b/apps/mobile/src/lib/profile-count-labels.test.ts new file mode 100644 index 00000000000..e08ad2abb47 --- /dev/null +++ b/apps/mobile/src/lib/profile-count-labels.test.ts @@ -0,0 +1,40 @@ +import { type TFunction } from 'i18next'; +import { describe, expect, it } from 'vitest'; + +import { i18n } from '@/i18n'; + +import { formatProfileCountItems, formatProfileCountItemsShort } from './profile-count-labels'; + +const t = i18n.t as TFunction; + +describe('formatProfileCountItems', () => { + it('localizes each count with its unit word, in order', () => { + expect( + formatProfileCountItems(t, [ + { kind: 'vars', count: 3 }, + { kind: 'mcp', count: 1 }, + ]) + ).toEqual(['3 vars', '1 MCP']); + expect(formatProfileCountItems(t, [{ kind: 'commands', count: 1 }])).toEqual(['1 cmds']); + }); + + it('resolves nothing for an empty list', () => { + expect(formatProfileCountItems(t, [])).toEqual([]); + }); +}); + +describe('formatProfileCountItemsShort', () => { + it('localizes each count with its compact suffix, in order', () => { + expect( + formatProfileCountItemsShort(t, [ + { kind: 'vars', count: 3 }, + { kind: 'commands', count: 2 }, + { kind: 'skills', count: 1 }, + ]) + ).toEqual(['3v', '2c', '1s']); + }); + + it('falls back to the full unit word where no compact form exists', () => { + expect(formatProfileCountItemsShort(t, [{ kind: 'mcp', count: 2 }])).toEqual(['2 MCP']); + }); +}); diff --git a/apps/mobile/src/lib/profile-count-labels.ts b/apps/mobile/src/lib/profile-count-labels.ts new file mode 100644 index 00000000000..5e11eaa0fc9 --- /dev/null +++ b/apps/mobile/src/lib/profile-count-labels.ts @@ -0,0 +1,50 @@ +import { type TFunction } from 'i18next'; + +/** + * The composition counts a profile row summarizes. The pure models resolve + * them; this module maps each kind to the catalog keys that carry the reader's + * own unit words, so no screen spells an English unit (`3 vars`, `2v`). + */ + +type ProfileCountKind = 'vars' | 'mcp' | 'skills' | 'commands'; + +export type ProfileCountItem = { + kind: ProfileCountKind; + count: number; +}; + +/** Full unit words, e.g. `3 vars`. Used by the picker and selector rows. */ +const COUNT_KEYS = { + vars: 'profiles.counts.vars', + mcp: 'profiles.counts.mcp', + skills: 'profiles.counts.skills', + commands: 'profiles.counts.commands', +} satisfies Record; + +/** Localize each count in order, e.g. `['3 vars', '1 MCP']`. */ +export function formatProfileCountItems( + t: TFunction, + items: readonly ProfileCountItem[] +): string[] { + return items.map(item => t(COUNT_KEYS[item.kind], { count: item.count })); +} + +/** + * Compact keys for the profile-list subtitle, e.g. `3v`. MCP is absent + * because its full unit word is already short, so it falls back to it. + */ +const SHORT_COUNT_KEYS = new Map([ + ['vars', 'profiles.counts.varsShort'], + ['skills', 'profiles.counts.skillsShort'], + ['commands', 'profiles.counts.commandsShort'], +]); + +/** Localize each count with its compact suffix in order, e.g. `['3v', '1c']`. */ +export function formatProfileCountItemsShort( + t: TFunction, + items: readonly ProfileCountItem[] +): string[] { + return items.map(item => + t(SHORT_COUNT_KEYS.get(item.kind) ?? COUNT_KEYS[item.kind], { count: item.count }) + ); +} diff --git a/apps/mobile/src/lib/route-registry.ts b/apps/mobile/src/lib/route-registry.ts index f77054d0a17..b7a596631a0 100644 --- a/apps/mobile/src/lib/route-registry.ts +++ b/apps/mobile/src/lib/route-registry.ts @@ -16,6 +16,7 @@ import { type InstancePickerBridge, type ModelPickerBridge, type ModePickerBridge, + type ProfilePickerBridge, type RepoPickerBridge, } from './picker-bridge'; import { type SecurityFindingFilterBridge } from './security-finding-filter-bridge'; @@ -51,6 +52,7 @@ type SlotValue = { branchPicker: BranchPickerBridge; instancePicker: InstancePickerBridge; folderPicker: FolderPickerBridge; + profilePicker: ProfilePickerBridge; prFileNav: Set; prDiffSelection: DiffSelection; securityFilter: SecurityFindingFilterBridge; @@ -67,6 +69,7 @@ const slots: RegistrySlots = { branchPicker: new Map(), instancePicker: new Map(), folderPicker: new Map(), + profilePicker: new Map(), prFileNav: new Map>(), prDiffSelection: new Map(), securityFilter: new Map(), @@ -79,6 +82,7 @@ const ALL_SLOT_KINDS: readonly SlotKind[] = [ 'branchPicker', 'instancePicker', 'folderPicker', + 'profilePicker', 'prFileNav', 'prDiffSelection', 'securityFilter', @@ -113,6 +117,7 @@ export const repoPickerSlot = createSlot('repoPicker'); export const branchPickerSlot = createSlot('branchPicker'); export const instancePickerSlot = createSlot('instancePicker'); export const folderPickerSlot = createSlot('folderPicker'); +export const profilePickerSlot = createSlot('profilePicker'); export const prFileNavSlot = createSlot('prFileNav'); export const prDiffSelectionSlot = createSlot('prDiffSelection'); export const securityFilterSlot = createSlot('securityFilter'); diff --git a/apps/web/package.json b/apps/web/package.json index 2ab2a9c5b10..8596cc5df4d 100644 --- a/apps/web/package.json +++ b/apps/web/package.json @@ -14,7 +14,7 @@ "format:check": "pnpm -w exec oxfmt --list-different .", "format:changed": "git diff --name-only $(git merge-base origin/main HEAD) --diff-filter=ACMR -- '**/*.js' '**/*.jsx' '**/*.ts' '**/*.tsx' '**/*.json' '**/*.css' '**/*.md' | xargs -r pnpm -w exec oxfmt --no-error-on-unmatched-pattern", "test": "NODE_ENV=test jest", - "typecheck": "pnpm --filter @kilocode/trpc run build && GOMEMLIMIT=2GiB tsgo --noEmit --checkers 1", + "typecheck": "pnpm --filter @kilocode/trpc run build && GOMEMLIMIT=2GiB tsgo --noEmit --checkers 1 --tsBuildInfoFile .tsbuildinfo/tsgo.tsbuildinfo", "dependency-cycle-check": "madge --circular --warning --extensions ts,tsx --ts-config tsconfig.json --basedir . src", "validate": "pnpm run typecheck && pnpm run lint && pnpm run format:changed && pnpm run test && pnpm run dependency-cycle-check", "node": "node", diff --git a/apps/web/src/app/(app)/components/PrefetchedOrganizations.test.ts b/apps/web/src/app/(app)/components/PrefetchedOrganizations.test.ts new file mode 100644 index 00000000000..467c7664183 --- /dev/null +++ b/apps/web/src/app/(app)/components/PrefetchedOrganizations.test.ts @@ -0,0 +1,62 @@ +import React from 'react'; +import { renderToStaticMarkup } from 'react-dom/server'; +import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; +import { createTRPCOptionsProxy } from '@trpc/tanstack-react-query'; +import { createTRPCContext } from '@/lib/trpc/init'; +import { PrefetchedOrganizations } from './PrefetchedOrganizations'; + +// The component relies on the automatic JSX runtime; jest's classic transform +// emits `React.createElement`, so expose React globally as the sibling +// `SidebarUserFooter.test.ts` does. +(globalThis as typeof globalThis & { React: typeof React }).React = React; + +jest.mock('@/lib/trpc/init', () => ({ createTRPCContext: jest.fn() })); +jest.mock('@/routers/root-router', () => ({ rootRouter: {} })); +jest.mock('@sentry/nextjs', () => ({ captureException: jest.fn() })); +jest.mock('@trpc/tanstack-react-query', () => ({ + createTRPCOptionsProxy: jest.fn(() => ({ + organizations: { + list: { queryOptions: () => ({ queryKey: ['organizations'], queryFn: () => [] }) }, + }, + })), +})); + +describe('PrefetchedOrganizations user seed', () => { + it.each(['signed-in', 'signed-out', 'context-failed', 'org-prefetch-failed'] as const)( + 'only seeds a resolved user: %s', + async state => { + const user = { id: 'user-1' }; + const context = jest.mocked(createTRPCContext); + if (state === 'context-failed') + context.mockRejectedValueOnce(new Error('Transient context failure')); + else + context.mockResolvedValueOnce({ user: state === 'signed-out' ? null : user } as Awaited< + ReturnType + >); + if (state === 'org-prefetch-failed') { + jest.mocked(createTRPCOptionsProxy).mockImplementationOnce(() => { + throw new Error('Organization prefetch failed'); + }); + } + const queryClient = new QueryClient({ defaultOptions: { queries: { staleTime: 60_000 } } }); + const tree = await PrefetchedOrganizations({ + children: React.createElement('span', null, 'app shell'), + }); + const html = renderToStaticMarkup( + React.createElement(QueryClientProvider, { client: queryClient }, tree) + ); + expect(html).toContain('app shell'); + expect(queryClient.getQueryData(['user'])).toEqual( + state === 'signed-in' || state === 'org-prefetch-failed' ? user : undefined + ); + if (state === 'context-failed') { + const fetchUser = jest.fn().mockResolvedValue(user); + await expect( + queryClient.fetchQuery({ queryKey: ['user'], queryFn: fetchUser }) + ).resolves.toEqual(user); + expect(fetchUser).toHaveBeenCalledTimes(1); + } + queryClient.clear(); + } + ); +}); diff --git a/apps/web/src/app/(app)/components/PrefetchedOrganizations.tsx b/apps/web/src/app/(app)/components/PrefetchedOrganizations.tsx index 1c7d9d6fa31..3762aed6b6b 100644 --- a/apps/web/src/app/(app)/components/PrefetchedOrganizations.tsx +++ b/apps/web/src/app/(app)/components/PrefetchedOrganizations.tsx @@ -2,8 +2,10 @@ import type { ReactNode } from 'react'; import * as Sentry from '@sentry/nextjs'; import { dehydrate, HydrationBoundary, QueryClient } from '@tanstack/react-query'; import { createTRPCOptionsProxy } from '@trpc/tanstack-react-query'; +import type { User } from '@kilocode/db/schema'; import { createTRPCContext } from '@/lib/trpc/init'; import { rootRouter } from '@/routers/root-router'; +import { SeedUserQuery } from './SeedUserQuery'; /** * Server component that prefetches the organizations list during SSR, @@ -11,12 +13,20 @@ import { rootRouter } from '@/routers/root-router'; * * Uses tRPC's createTRPCOptionsProxy to generate the correct query key, * matching what the client-side `trpc.organizations.list.queryOptions()` produces. + * + * It also hands the resolved user to `SeedUserQuery`, which seeds the `['user']` + * query the sidebar footer and the customer-source survey read, so both render + * their final markup during SSR instead of swapping a loading state for real + * content while React is still hydrating. */ export async function PrefetchedOrganizations({ children }: { children: ReactNode }) { const queryClient = new QueryClient(); + let user: User | null = null; try { const ctx = await createTRPCContext(); + // Same user `GET /api/user` returns; `useUser()` fetches that route on the client. + user = ctx.user; const trpc = createTRPCOptionsProxy({ router: rootRouter, ctx, queryClient }); await queryClient.prefetchQuery(trpc.organizations.list.queryOptions()); } catch (error) { @@ -25,5 +35,10 @@ export async function PrefetchedOrganizations({ children }: { children: ReactNod Sentry.captureException(error); } - return {children}; + return ( + + {user !== null ? : null} + {children} + + ); } diff --git a/apps/web/src/app/(app)/components/SeedUserQuery.test.ts b/apps/web/src/app/(app)/components/SeedUserQuery.test.ts new file mode 100644 index 00000000000..0120ee52506 --- /dev/null +++ b/apps/web/src/app/(app)/components/SeedUserQuery.test.ts @@ -0,0 +1,39 @@ +import React from 'react'; +import { renderToStaticMarkup } from 'react-dom/server'; +import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; +import { SeedUserQuery } from './SeedUserQuery'; + +/** + * The footer and the customer-source survey read `['user']` while React + * hydrates; seeding it during render is what keeps the server and the first + * client render in agreement. These assertions pin the seeding contract. + */ +function renderWithClient(user: unknown) { + const queryClient = new QueryClient(); + const html = renderToStaticMarkup( + React.createElement( + QueryClientProvider, + { client: queryClient }, + React.createElement(SeedUserQuery, { user }), + React.createElement('span', null, 'app shell') + ) + ); + return { queryClient, html }; +} + +describe('SeedUserQuery', () => { + it('seeds the user query and renders nothing of its own', () => { + const user = { id: 'user-1', google_user_email: 'seed@example.com' }; + const { queryClient, html } = renderWithClient(user); + + expect(html).toContain('app shell'); + expect(html).not.toContain('seed@example.com'); + expect(queryClient.getQueryData(['user'])).toEqual(user); + }); + + it('seeds null when nobody is signed in', () => { + const { queryClient } = renderWithClient(null); + + expect(queryClient.getQueryData(['user'])).toBeNull(); + }); +}); diff --git a/apps/web/src/app/(app)/components/SeedUserQuery.tsx b/apps/web/src/app/(app)/components/SeedUserQuery.tsx new file mode 100644 index 00000000000..96107ecf41e --- /dev/null +++ b/apps/web/src/app/(app)/components/SeedUserQuery.tsx @@ -0,0 +1,26 @@ +'use client'; + +import { useRef } from 'react'; +import { useQueryClient } from '@tanstack/react-query'; + +/** + * Seeds the query cache with the user resolved during SSR, before the app shell + * renders. + * + * The sidebar's `useUser()` footer renders a skeleton while `['user']` is + * pending, then swaps to the real footer as soon as the client query resolves. + * When that swap lands while React is still hydrating, React sees the server's + * skeleton HTML next to the client's footer, regenerates the whole tree and + * drops interactions that land in between (observed as an unclickable profile + * picker on /cloud). Seeding the same value the server rendered removes the + * swap entirely. + */ +export function SeedUserQuery({ user }: { user: unknown }) { + const queryClient = useQueryClient(); + const seeded = useRef(false); + if (!seeded.current) { + seeded.current = true; + queryClient.setQueryData(['user'], user); + } + return null; +} diff --git a/apps/web/src/app/(app)/layout.tsx b/apps/web/src/app/(app)/layout.tsx index aae2e8bdac7..b74422ade7b 100644 --- a/apps/web/src/app/(app)/layout.tsx +++ b/apps/web/src/app/(app)/layout.tsx @@ -16,8 +16,13 @@ export default function AppLayout({ children }: { children: React.ReactNode }) { - - + {/* + The SSR-resolved user is seeded here so every consumer that renders + during hydration (the sidebar footer and the customer-source survey) + agrees with the server markup. Both live inside this boundary. + */} + +

@@ -30,9 +35,9 @@ export default function AppLayout({ children }: { children: React.ReactNode }) {
- - - + + + diff --git a/apps/web/src/components/cloud-agent/ProfilePickerPopover.test.ts b/apps/web/src/components/cloud-agent/ProfilePickerPopover.test.ts new file mode 100644 index 00000000000..fad14245d23 --- /dev/null +++ b/apps/web/src/components/cloud-agent/ProfilePickerPopover.test.ts @@ -0,0 +1,91 @@ +/* eslint-disable @typescript-eslint/no-require-imports, @typescript-eslint/no-var-requires -- Jest node-environment mocks must be registered before loading the component. */ +// A failed background refetch keeps `data` in React Query while setting `error`. +// The picker must keep showing the cached profiles (and the active profile) in +// that state, and only fall back to the retry error when there is nothing to +// show. The profiles hooks are stubbed with exactly that state; the popover +// primitives render their children so the content is present in static markup. +import { jest } from '@jest/globals'; +import React, { createElement } from 'react'; +import type { ReactNode } from 'react'; +import { renderToStaticMarkup } from 'react-dom/server'; +import type * as ProfilePickerPopoverModule from './ProfilePickerPopover'; + +let mockCombined: unknown; +let mockPersonal: unknown; + +jest.mock('@/hooks/useCloudAgentProfiles', () => ({ + useCombinedProfiles: () => mockCombined, + useProfiles: () => mockPersonal, + useRepoBindings: () => ({ data: [] }), +})); + +jest.mock('@/components/ui/popover', () => { + const react = jest.requireActual('react'); + const block = (tag: string) => { + const Block = ({ children }: { children?: ReactNode }) => + react.createElement(tag, null, children); + Block.displayName = `Mock${tag}`; + return Block; + }; + return { Popover: block('div'), PopoverTrigger: block('button'), PopoverContent: block('div') }; +}); + +jest.mock('@/components/cloud-agent/ProfilesListDialog', () => ({ + ProfilesListDialog: () => null, +})); + +const { ProfilePickerPopover } = + require('./ProfilePickerPopover') as typeof ProfilePickerPopoverModule; + +Object.assign(globalThis, { React }); + +const PROFILE = { + id: 'profile-1', + name: 'Production', + ownerType: 'organization', + varCount: 2, + mcpServerCount: 1, + skillCount: 0, + kiloCommandCount: 0, +}; + +function renderPicker(): string { + return renderToStaticMarkup( + createElement(ProfilePickerPopover, { + organizationId: 'org-1', + selectedOverrideProfileId: null, + onOverrideProfileSelect: jest.fn(), + }) + ); +} + +describe('ProfilePickerPopover profiles error state', () => { + it('keeps the picker when a refetch fails but cached profiles remain', () => { + mockCombined = { + data: { allProfiles: [PROFILE], effectiveDefaultId: null }, + isLoading: false, + error: new Error('refetch failed'), + refetch: jest.fn(), + }; + mockPersonal = { data: [], isLoading: false, error: undefined, refetch: jest.fn() }; + + const markup = renderPicker(); + expect(markup).toContain('Production'); + expect(markup).toContain('Pick a profile'); + expect(markup).not.toContain('Could not load profiles.'); + }); + + it('shows the retry state when the list failed with nothing cached', () => { + mockCombined = { + data: undefined, + isLoading: false, + error: new Error('refetch failed'), + refetch: jest.fn(), + }; + mockPersonal = { data: [], isLoading: false, error: undefined, refetch: jest.fn() }; + + const markup = renderPicker(); + expect(markup).toContain('Could not load profiles.'); + expect(markup).not.toContain('Pick a profile'); + }); +}); diff --git a/apps/web/src/components/cloud-agent/ProfilePickerPopover.tsx b/apps/web/src/components/cloud-agent/ProfilePickerPopover.tsx index 92d9189bd84..48a0fcea42e 100644 --- a/apps/web/src/components/cloud-agent/ProfilePickerPopover.tsx +++ b/apps/web/src/components/cloud-agent/ProfilePickerPopover.tsx @@ -1,8 +1,20 @@ 'use client'; import { useState, useMemo, useEffect, useId } from 'react'; -import { Layers, ChevronDown, Check, Plus, FolderCog, X, ArrowLeft, Zap } from 'lucide-react'; +import { + Layers, + ChevronDown, + Check, + Plus, + FolderCog, + X, + ArrowLeft, + Zap, + Loader2, + AlertCircle, +} from 'lucide-react'; import { Popover, PopoverContent, PopoverTrigger } from '@/components/ui/popover'; +import { Button } from '@/components/ui/button'; import { Switch } from '@/components/ui/switch'; import { cn } from '@/lib/utils'; import { @@ -57,14 +69,32 @@ export function ProfilePickerPopover({ setShowManageProfiles(true); }; - const { data: combinedData } = useCombinedProfiles({ + const { + data: combinedData, + isLoading: combinedProfilesLoading, + error: combinedProfilesError, + refetch: refetchCombinedProfiles, + } = useCombinedProfiles({ organizationId: organizationId ?? '', enabled: !!organizationId, }); - const { data: personalProfilesData } = useProfiles({ + const { + data: personalProfilesData, + isLoading: personalProfilesLoading, + error: personalProfilesError, + refetch: refetchPersonalProfiles, + } = useProfiles({ organizationId: undefined, enabled: !organizationId, }); + const profilesLoading = organizationId ? combinedProfilesLoading : personalProfilesLoading; + const profilesError = organizationId ? combinedProfilesError : personalProfilesError; + // React Query keeps `error` set while retaining `data` when a background + // refetch fails (a window-focus refetch, or the invalidation refetch a profile + // mutation triggers). Only surface the retry state when there is nothing + // cached to show, so a transient failure never blanks a working picker. + const profilesData = organizationId ? combinedData : personalProfilesData; + const refetchProfiles = organizationId ? refetchCombinedProfiles : refetchPersonalProfiles; const allProfiles: ProfileSummaryWithOwner[] = useMemo( () => @@ -196,6 +226,12 @@ export function ProfilePickerPopover({ topProfile={topProfile} topSource={topSource} allProfiles={allProfiles} + selectedOverrideProfileId={selectedOverrideProfileId} + isLoading={profilesLoading} + isError={!!profilesError && !profilesData} + onRetry={() => { + void refetchProfiles(); + }} overrideCandidatesCount={overrideCandidates.length} formatCounts={formatCounts} onEditProfile={openEditDialog} @@ -327,12 +363,18 @@ type ActiveProfileViewProps = { topProfile: ProfileSummaryWithOwner | null; topSource: 'repo-binding' | 'default' | 'explicit' | null; allProfiles: ProfileSummaryWithOwner[]; + selectedOverrideProfileId: string | null; + /** The profiles query is still resolving; show a loading row, never the empty state. */ + isLoading: boolean; + /** The profiles query failed; offer a retry instead of the empty state. */ + isError: boolean; + onRetry: () => void; overrideCandidatesCount: number; formatCounts: (p: ProfileSummaryWithOwner) => string; onEditProfile: (id: string) => void; onStartPickOverride: () => void; onRemoveOverride: () => void; - onSelectProfile: (id: string) => void; + onSelectProfile: (id: string | null) => void; }; function ActiveProfileView({ @@ -340,6 +382,10 @@ function ActiveProfileView({ topProfile, topSource, allProfiles, + selectedOverrideProfileId, + isLoading, + isError, + onRetry, overrideCandidatesCount, formatCounts, onEditProfile, @@ -347,6 +393,31 @@ function ActiveProfileView({ onRemoveOverride, onSelectProfile, }: ActiveProfileViewProps) { + // The list is still resolving — never claim the user has no profiles. + if (isLoading) { + return ( +
+ + Loading profiles… +
+ ); + } + + // A failed list request is retryable — never present it as "no profiles". + if (isError) { + return ( +
+

+ + Could not load profiles. +

+ +
+ ); + } + // Nothing selected at all and no profiles exist. if (!baseProfile && !topProfile && allProfiles.length === 0) { return ( @@ -357,7 +428,8 @@ function ActiveProfileView({ } // No base and no top, but profiles exist — show the picker list directly so - // the user can select one with a single click. + // the user can select one with a single click. "No profile" stays the first + // row so the current (unset) choice is visible and can be kept. if (!baseProfile && !topProfile) { return ( <> @@ -365,12 +437,17 @@ function ActiveProfileView({ Pick a profile

+ onSelectProfile(null)} + /> {allProfiles.map(profile => ( onSelectProfile(profile.id)} /> ))} diff --git a/apps/web/src/components/webhook-triggers/TriggerForm.test.ts b/apps/web/src/components/webhook-triggers/TriggerForm.test.ts index 5036924e36c..b24e5471b69 100644 --- a/apps/web/src/components/webhook-triggers/TriggerForm.test.ts +++ b/apps/web/src/components/webhook-triggers/TriggerForm.test.ts @@ -113,6 +113,9 @@ jest.mock('@/components/ui/dialog', () => ({ DialogTitle: ({ children }: { children: React.ReactNode }) => createElement('h2', {}, children), })); jest.mock('@/components/cloud-agent/ProfileSelector', () => ({ ProfileSelector: () => null })); +jest.mock('@/components/cloud-agent/RepoProfileBindingsDialog', () => ({ + RepoProfileBindingsDialog: () => null, +})); jest.mock('@/components/shared/RepositoryCombobox', () => ({ RepositoryCombobox: () => null })); jest.mock('@/components/shared/ModeCombobox', () => ({ ModeCombobox: () => null })); jest.mock('@/components/shared/ModelCombobox', () => ({ diff --git a/apps/web/src/components/webhook-triggers/TriggerForm.tsx b/apps/web/src/components/webhook-triggers/TriggerForm.tsx index 725619cf1ab..33a62efe757 100644 --- a/apps/web/src/components/webhook-triggers/TriggerForm.tsx +++ b/apps/web/src/components/webhook-triggers/TriggerForm.tsx @@ -16,6 +16,7 @@ import { SelectValue, } from '@/components/ui/select'; import { ProfileSelector } from '@/components/cloud-agent/ProfileSelector'; +import { RepoProfileBindingsDialog } from '@/components/cloud-agent/RepoProfileBindingsDialog'; import { RepositoryCombobox, type RepositoryOption } from '@/components/shared/RepositoryCombobox'; import { ModeCombobox } from '@/components/shared/ModeCombobox'; import { ModelCombobox, type ModelOption } from '@/components/shared/ModelCombobox'; @@ -159,6 +160,7 @@ export function TriggerForm({ hasSavedSandboxAllocation || sandboxAllocation === 'isolated-standard'; const modelVariants = models.find(option => option.id === model)?.variants ?? []; + const [showRepoDefaults, setShowRepoDefaults] = useState(false); const handleModelChange = (nextModel: string) => { if (nextModel === model) return; @@ -707,6 +709,12 @@ export function TriggerForm({ onProfileSelect={setProfileId} disabled={isLoading} orgProfilesOnly={!!organizationId} + onRepoDefaultsClick={() => setShowRepoDefaults(true)} + /> + {!profileId && (

diff --git a/apps/web/src/routers/agent-profiles-mobile.test.ts b/apps/web/src/routers/agent-profiles-mobile.test.ts new file mode 100644 index 00000000000..938cb6d5a9f --- /dev/null +++ b/apps/web/src/routers/agent-profiles-mobile.test.ts @@ -0,0 +1,35 @@ +import { describe, test, expect } from '@jest/globals'; +import { agentProfilesMobileRouter } from '../../../../packages/trpc/src/agent-profiles-mobile'; + +const procedures = ( + agentProfilesMobileRouter as unknown as { + _def: { procedures: Record }; + } +)._def.procedures; + +describe('agentProfilesMobileRouter', () => { + test.each([ + // read procedures + 'list', + 'listCombined', + 'get', + // profile mutations the mobile profiles screens call + 'create', + 'update', + 'delete', + 'setAsDefault', + 'clearDefault', + // profile var mutations + 'setVar', + 'deleteVar', + // command mutations + 'setCommands', + // skill mutations + 'createCustomSkill', + 'updateSkill', + 'deleteSkill', + 'setSkillEnabled', + ])('mounts the %s procedure', name => { + expect(procedures[name]).toBeDefined(); + }); +}); diff --git a/apps/web/src/routers/cli-sessions-v2-router.ts b/apps/web/src/routers/cli-sessions-v2-router.ts index 387d462d05e..fa08a5a3664 100644 --- a/apps/web/src/routers/cli-sessions-v2-router.ts +++ b/apps/web/src/routers/cli-sessions-v2-router.ts @@ -1553,6 +1553,7 @@ export const cliSessionsV2Router = createTRPCRouter({ title: z.string().nullable(), cloud_agent_session_id: z.string().nullable(), cloud_agent_worktree_id: cloudAgentWorktreeIdSchema.nullable(), + profile_id: z.string().nullable(), organization_id: z.string().nullable(), git_url: z.string().nullable(), git_branch: z.string().nullable(), @@ -1678,6 +1679,7 @@ export const cliSessionsV2Router = createTRPCRouter({ ? cloudAgentWorktreeIdSchema.parse(session.cloud_agent_worktree_id) : null, organization_id: session.organization_id ?? null, + profile_id: session.profile_id ?? null, git_url: session.git_url ?? null, git_branch: session.git_branch ?? null, created_on_platform: session.created_on_platform, diff --git a/apps/web/src/routers/cli-sessions-v2-worktree.test.ts b/apps/web/src/routers/cli-sessions-v2-worktree.test.ts index 6457ca2ff25..4843b5bc5d9 100644 --- a/apps/web/src/routers/cli-sessions-v2-worktree.test.ts +++ b/apps/web/src/routers/cli-sessions-v2-worktree.test.ts @@ -136,6 +136,7 @@ const ownershipRow = { parent_session_id: null, organization_id: null, cloud_agent_session_id: WORKSPACE_ID, + profile_id: null, cloud_agent_session_scope_id: null, cloud_agent_worktree_id: WORKTREE_ID, created_on_platform: 'cloud-agent-web', diff --git a/apps/web/src/tests/cloud-agent-profile/profile-session-config.test.ts b/apps/web/src/tests/cloud-agent-profile/profile-session-config.test.ts index 8c465c731dc..90aba8b7f82 100644 --- a/apps/web/src/tests/cloud-agent-profile/profile-session-config.test.ts +++ b/apps/web/src/tests/cloud-agent-profile/profile-session-config.test.ts @@ -180,6 +180,7 @@ describe('mergeProfileConfiguration', () => { setupCommands: undefined, encryptedSecrets: undefined, }); + expect(result.resolvedProfileId).toBeUndefined(); }); test('passes through manual envVars only', async () => { @@ -265,6 +266,7 @@ describe('mergeProfileConfiguration', () => { expect(result.envVars).toEqual({ REPO_VAR: 'bound' }); expect(result.setupCommands).toEqual(['repo-setup']); + expect(result.resolvedProfileId).toBe(profileId); }); test('merges repo binding (base) with explicit override and manual args', async () => { @@ -302,6 +304,7 @@ describe('mergeProfileConfiguration', () => { }); // Commands: base, override, manual (concatenated) expect(result.setupCommands).toEqual(['base-cmd', 'override-cmd', 'manual-cmd']); + expect(result.resolvedProfileId).toBe(overrideProfileId); }); test('repo binding (base) and effective default (top) are co-applied; default wins on collision', async () => { @@ -329,6 +332,7 @@ describe('mergeProfileConfiguration', () => { DEFAULT_ONLY: 'default-val', FROM: 'default', }); + expect(result.resolvedProfileId).toBe(defaultProfileId); }); test('explicit pick suppresses the default (default is only a fallback)', async () => { @@ -344,6 +348,7 @@ describe('mergeProfileConfiguration', () => { const result = await mergeProfileConfiguration(db, { owner, profileId: pickedProfileId }); expect(result.envVars).toEqual({ FROM: 'picked' }); + expect(result.resolvedProfileId).toBe(pickedProfileId); }); test('deduplicates when explicit override equals the repo binding', async () => { @@ -364,6 +369,7 @@ describe('mergeProfileConfiguration', () => { // Should not duplicate vars/commands expect(result.envVars).toEqual({ KEY: 'val' }); expect(result.setupCommands).toEqual(['cmd']); + expect(result.resolvedProfileId).toBe(profileId); }); test('handles secret vars as encryptedSecrets', async () => { diff --git a/packages/cloud-agent-profile/src/profile-session-config.ts b/packages/cloud-agent-profile/src/profile-session-config.ts index 64dc28692b5..0af7626e7f6 100644 --- a/packages/cloud-agent-profile/src/profile-session-config.ts +++ b/packages/cloud-agent-profile/src/profile-session-config.ts @@ -101,6 +101,14 @@ export type MergeProfileConfigurationResult = { skills?: MergedSkillForSession[]; agents?: MergedAgentForSession[]; kiloCommands?: KiloCommandForSession[]; + /** + * The profile that claimed the active layer: the explicit pick or effective + * default when one applied, else the repository-binding base. Absent when no + * profile applied to the session. Mirrors the picker's `selectedProfileId` + * (`topProfile?.id ?? baseProfile?.id`) so a recorded id names the profile + * the user saw selected. + */ + resolvedProfileId?: string; }; /** Ensure a profileId belongs to the given owner (or, for org context, to the user personally). */ @@ -347,7 +355,15 @@ export async function mergeProfileConfiguration( const inline = inlineToLayer(args); if (inline) layers.push(inline); - return mergeLayers(layers); + // The active layer's id: the top layer when it applied, else the repo-binding + // base. `top` is dropped when it duplicates `base`, so a dropped top leaves + // the (identical) base id as the active profile. + const resolvedProfileId = top?.profileId ?? base?.profileId ?? null; + + return { + ...mergeLayers(layers), + ...(resolvedProfileId ? { resolvedProfileId } : {}), + }; } /** diff --git a/packages/cloud-agent-sdk/src/session-manager.ts b/packages/cloud-agent-sdk/src/session-manager.ts index f8598813655..d3e5f3ddda5 100644 --- a/packages/cloud-agent-sdk/src/session-manager.ts +++ b/packages/cloud-agent-sdk/src/session-manager.ts @@ -284,6 +284,12 @@ type FetchedSessionData = { totalCostMicrodollars?: number | null; /** Origin platform (`created_on_platform`). Populated by the mobile adapter only. */ createdOnPlatform?: string | null; + /** + * The profile the session was prepared with, as recorded on the session row. + * Null for a session created before profile recording, or one whose create + * origin resolved no profile. Populated by the mobile and extension adapters. + */ + profileId?: string | null; }; type PrepareInput = { diff --git a/packages/db/src/migrations/0260_free_amazoness.sql b/packages/db/src/migrations/0260_free_amazoness.sql new file mode 100644 index 00000000000..856a7aa1a51 --- /dev/null +++ b/packages/db/src/migrations/0260_free_amazoness.sql @@ -0,0 +1,5 @@ +ALTER TABLE "cli_sessions_v2" ADD COLUMN "profile_id" uuid;--> statement-breakpoint +ALTER TABLE "cli_sessions_v2" ADD CONSTRAINT "cli_sessions_v2_profile_id_agent_environment_profiles_id_fk" FOREIGN KEY ("profile_id") REFERENCES "public"."agent_environment_profiles"("id") ON DELETE set null ON UPDATE no action;--> statement-breakpoint +COMMIT;--> statement-breakpoint +CREATE INDEX CONCURRENTLY "IDX_cli_sessions_v2_profile_id" ON "cli_sessions_v2" USING btree ("profile_id");--> statement-breakpoint +BEGIN; diff --git a/packages/db/src/migrations/meta/0260_snapshot.json b/packages/db/src/migrations/meta/0260_snapshot.json new file mode 100644 index 00000000000..d8adfebce2b --- /dev/null +++ b/packages/db/src/migrations/meta/0260_snapshot.json @@ -0,0 +1,42898 @@ +{ + "id": "f64a63e2-9886-46e7-94f7-eb3cfa2b4348", + "prevId": "e57d892c-ce1d-420e-b533-46f38968a755", + "version": "7", + "dialect": "postgresql", + "tables": { + "public.agent_configs": { + "name": "agent_configs", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "pg_catalog.gen_random_uuid()" + }, + "owned_by_organization_id": { + "name": "owned_by_organization_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "owned_by_user_id": { + "name": "owned_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "agent_type": { + "name": "agent_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "platform": { + "name": "platform", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "config": { + "name": "config", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "is_enabled": { + "name": "is_enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "runtime_state": { + "name": "runtime_state", + "type": "jsonb", + "primaryKey": false, + "notNull": false, + "default": "'{}'::jsonb" + }, + "created_by": { + "name": "created_by", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "config_revision": { + "name": "config_revision", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 1 + } + }, + "indexes": { + "IDX_agent_configs_org_id": { + "name": "IDX_agent_configs_org_id", + "columns": [ + { + "expression": "owned_by_organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_agent_configs_owned_by_user_id": { + "name": "IDX_agent_configs_owned_by_user_id", + "columns": [ + { + "expression": "owned_by_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_agent_configs_agent_type": { + "name": "IDX_agent_configs_agent_type", + "columns": [ + { + "expression": "agent_type", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_agent_configs_platform": { + "name": "IDX_agent_configs_platform", + "columns": [ + { + "expression": "platform", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "agent_configs_owned_by_organization_id_organizations_id_fk": { + "name": "agent_configs_owned_by_organization_id_organizations_id_fk", + "tableFrom": "agent_configs", + "tableTo": "organizations", + "columnsFrom": [ + "owned_by_organization_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "agent_configs_owned_by_user_id_kilocode_users_id_fk": { + "name": "agent_configs_owned_by_user_id_kilocode_users_id_fk", + "tableFrom": "agent_configs", + "tableTo": "kilocode_users", + "columnsFrom": [ + "owned_by_user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "UQ_agent_configs_org_agent_platform": { + "name": "UQ_agent_configs_org_agent_platform", + "nullsNotDistinct": false, + "columns": [ + "owned_by_organization_id", + "agent_type", + "platform" + ] + }, + "UQ_agent_configs_user_agent_platform": { + "name": "UQ_agent_configs_user_agent_platform", + "nullsNotDistinct": false, + "columns": [ + "owned_by_user_id", + "agent_type", + "platform" + ] + } + }, + "policies": {}, + "checkConstraints": { + "agent_configs_owner_check": { + "name": "agent_configs_owner_check", + "value": "(\n (\"agent_configs\".\"owned_by_user_id\" IS NOT NULL AND \"agent_configs\".\"owned_by_organization_id\" IS NULL) OR\n (\"agent_configs\".\"owned_by_user_id\" IS NULL AND \"agent_configs\".\"owned_by_organization_id\" IS NOT NULL)\n )" + }, + "agent_configs_agent_type_check": { + "name": "agent_configs_agent_type_check", + "value": "\"agent_configs\".\"agent_type\" IN ('code_review', 'auto_triage', 'auto_fix', 'security_scan')" + }, + "agent_configs_config_revision_check": { + "name": "agent_configs_config_revision_check", + "value": "\"agent_configs\".\"config_revision\" >= 1" + } + }, + "isRLSEnabled": false + }, + "public.agent_environment_profile_agents": { + "name": "agent_environment_profile_agents", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "pg_catalog.gen_random_uuid()" + }, + "profile_id": { + "name": "profile_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "slug": { + "name": "slug", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "config": { + "name": "config", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "IDX_agent_env_profile_agents_profile_id": { + "name": "IDX_agent_env_profile_agents_profile_id", + "columns": [ + { + "expression": "profile_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "agent_environment_profile_agents_profile_id_agent_environment_profiles_id_fk": { + "name": "agent_environment_profile_agents_profile_id_agent_environment_profiles_id_fk", + "tableFrom": "agent_environment_profile_agents", + "tableTo": "agent_environment_profiles", + "columnsFrom": [ + "profile_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "UQ_agent_env_profile_agents_profile_slug": { + "name": "UQ_agent_env_profile_agents_profile_slug", + "nullsNotDistinct": false, + "columns": [ + "profile_id", + "slug" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.agent_environment_profile_commands": { + "name": "agent_environment_profile_commands", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "pg_catalog.gen_random_uuid()" + }, + "profile_id": { + "name": "profile_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "sequence": { + "name": "sequence", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "command": { + "name": "command", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "IDX_agent_env_profile_commands_profile_id": { + "name": "IDX_agent_env_profile_commands_profile_id", + "columns": [ + { + "expression": "profile_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "agent_environment_profile_commands_profile_id_agent_environment_profiles_id_fk": { + "name": "agent_environment_profile_commands_profile_id_agent_environment_profiles_id_fk", + "tableFrom": "agent_environment_profile_commands", + "tableTo": "agent_environment_profiles", + "columnsFrom": [ + "profile_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "UQ_agent_env_profile_commands_profile_sequence": { + "name": "UQ_agent_env_profile_commands_profile_sequence", + "nullsNotDistinct": false, + "columns": [ + "profile_id", + "sequence" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.agent_environment_profile_kilo_commands": { + "name": "agent_environment_profile_kilo_commands", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "pg_catalog.gen_random_uuid()" + }, + "profile_id": { + "name": "profile_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "template": { + "name": "template", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "agent": { + "name": "agent", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "model": { + "name": "model", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "subtask": { + "name": "subtask", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "enabled": { + "name": "enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "sort_order": { + "name": "sort_order", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "IDX_agent_env_profile_kilo_cmds_profile_id": { + "name": "IDX_agent_env_profile_kilo_cmds_profile_id", + "columns": [ + { + "expression": "profile_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "agent_environment_profile_kilo_commands_profile_id_agent_environment_profiles_id_fk": { + "name": "agent_environment_profile_kilo_commands_profile_id_agent_environment_profiles_id_fk", + "tableFrom": "agent_environment_profile_kilo_commands", + "tableTo": "agent_environment_profiles", + "columnsFrom": [ + "profile_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "UQ_agent_env_profile_kilo_cmds_profile_name": { + "name": "UQ_agent_env_profile_kilo_cmds_profile_name", + "nullsNotDistinct": false, + "columns": [ + "profile_id", + "name" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.agent_environment_profile_mcp_servers": { + "name": "agent_environment_profile_mcp_servers", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "pg_catalog.gen_random_uuid()" + }, + "profile_id": { + "name": "profile_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "enabled": { + "name": "enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "timeout": { + "name": "timeout", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "config": { + "name": "config", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "IDX_agent_env_profile_mcp_servers_profile_id": { + "name": "IDX_agent_env_profile_mcp_servers_profile_id", + "columns": [ + { + "expression": "profile_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "agent_environment_profile_mcp_servers_profile_id_agent_environment_profiles_id_fk": { + "name": "agent_environment_profile_mcp_servers_profile_id_agent_environment_profiles_id_fk", + "tableFrom": "agent_environment_profile_mcp_servers", + "tableTo": "agent_environment_profiles", + "columnsFrom": [ + "profile_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "UQ_agent_env_profile_mcp_servers_profile_name": { + "name": "UQ_agent_env_profile_mcp_servers_profile_name", + "nullsNotDistinct": false, + "columns": [ + "profile_id", + "name" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.agent_environment_profile_repo_bindings": { + "name": "agent_environment_profile_repo_bindings", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "pg_catalog.gen_random_uuid()" + }, + "repo_full_name": { + "name": "repo_full_name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "platform": { + "name": "platform", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'github'" + }, + "profile_id": { + "name": "profile_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "owned_by_organization_id": { + "name": "owned_by_organization_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "owned_by_user_id": { + "name": "owned_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "UQ_agent_env_profile_repo_bindings_user": { + "name": "UQ_agent_env_profile_repo_bindings_user", + "columns": [ + { + "expression": "repo_full_name", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "platform", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "owned_by_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"agent_environment_profile_repo_bindings\".\"owned_by_user_id\" is not null", + "concurrently": false, + "method": "btree", + "with": {} + }, + "UQ_agent_env_profile_repo_bindings_org": { + "name": "UQ_agent_env_profile_repo_bindings_org", + "columns": [ + { + "expression": "repo_full_name", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "platform", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "owned_by_organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"agent_environment_profile_repo_bindings\".\"owned_by_organization_id\" is not null", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "agent_environment_profile_repo_bindings_profile_id_agent_environment_profiles_id_fk": { + "name": "agent_environment_profile_repo_bindings_profile_id_agent_environment_profiles_id_fk", + "tableFrom": "agent_environment_profile_repo_bindings", + "tableTo": "agent_environment_profiles", + "columnsFrom": [ + "profile_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "agent_environment_profile_repo_bindings_owned_by_organization_id_organizations_id_fk": { + "name": "agent_environment_profile_repo_bindings_owned_by_organization_id_organizations_id_fk", + "tableFrom": "agent_environment_profile_repo_bindings", + "tableTo": "organizations", + "columnsFrom": [ + "owned_by_organization_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "agent_environment_profile_repo_bindings_owned_by_user_id_kilocode_users_id_fk": { + "name": "agent_environment_profile_repo_bindings_owned_by_user_id_kilocode_users_id_fk", + "tableFrom": "agent_environment_profile_repo_bindings", + "tableTo": "kilocode_users", + "columnsFrom": [ + "owned_by_user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "agent_env_profile_repo_bindings_owner_check": { + "name": "agent_env_profile_repo_bindings_owner_check", + "value": "(\n (\"agent_environment_profile_repo_bindings\".\"owned_by_user_id\" IS NOT NULL AND \"agent_environment_profile_repo_bindings\".\"owned_by_organization_id\" IS NULL) OR\n (\"agent_environment_profile_repo_bindings\".\"owned_by_user_id\" IS NULL AND \"agent_environment_profile_repo_bindings\".\"owned_by_organization_id\" IS NOT NULL)\n )" + } + }, + "isRLSEnabled": false + }, + "public.agent_environment_profile_skills": { + "name": "agent_environment_profile_skills", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "pg_catalog.gen_random_uuid()" + }, + "profile_id": { + "name": "profile_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "source_type": { + "name": "source_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "source_url": { + "name": "source_url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "raw_markdown": { + "name": "raw_markdown", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "files": { + "name": "files", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "enabled": { + "name": "enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "IDX_agent_env_profile_skills_profile_id": { + "name": "IDX_agent_env_profile_skills_profile_id", + "columns": [ + { + "expression": "profile_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "agent_environment_profile_skills_profile_id_agent_environment_profiles_id_fk": { + "name": "agent_environment_profile_skills_profile_id_agent_environment_profiles_id_fk", + "tableFrom": "agent_environment_profile_skills", + "tableTo": "agent_environment_profiles", + "columnsFrom": [ + "profile_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "UQ_agent_env_profile_skills_profile_name": { + "name": "UQ_agent_env_profile_skills_profile_name", + "nullsNotDistinct": false, + "columns": [ + "profile_id", + "name" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.agent_environment_profile_vars": { + "name": "agent_environment_profile_vars", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "pg_catalog.gen_random_uuid()" + }, + "profile_id": { + "name": "profile_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "key": { + "name": "key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "value": { + "name": "value", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "is_secret": { + "name": "is_secret", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "IDX_agent_env_profile_vars_profile_id": { + "name": "IDX_agent_env_profile_vars_profile_id", + "columns": [ + { + "expression": "profile_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "agent_environment_profile_vars_profile_id_agent_environment_profiles_id_fk": { + "name": "agent_environment_profile_vars_profile_id_agent_environment_profiles_id_fk", + "tableFrom": "agent_environment_profile_vars", + "tableTo": "agent_environment_profiles", + "columnsFrom": [ + "profile_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "UQ_agent_env_profile_vars_profile_key": { + "name": "UQ_agent_env_profile_vars_profile_key", + "nullsNotDistinct": false, + "columns": [ + "profile_id", + "key" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.agent_environment_profiles": { + "name": "agent_environment_profiles", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "pg_catalog.gen_random_uuid()" + }, + "owned_by_organization_id": { + "name": "owned_by_organization_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "owned_by_user_id": { + "name": "owned_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_by_user_id": { + "name": "created_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "is_default": { + "name": "is_default", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "UQ_agent_env_profiles_org_name": { + "name": "UQ_agent_env_profiles_org_name", + "columns": [ + { + "expression": "owned_by_organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "name", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"agent_environment_profiles\".\"owned_by_organization_id\" is not null", + "concurrently": false, + "method": "btree", + "with": {} + }, + "UQ_agent_env_profiles_user_name": { + "name": "UQ_agent_env_profiles_user_name", + "columns": [ + { + "expression": "owned_by_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "name", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"agent_environment_profiles\".\"owned_by_user_id\" is not null", + "concurrently": false, + "method": "btree", + "with": {} + }, + "UQ_agent_env_profiles_org_default": { + "name": "UQ_agent_env_profiles_org_default", + "columns": [ + { + "expression": "owned_by_organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"agent_environment_profiles\".\"is_default\" = true AND \"agent_environment_profiles\".\"owned_by_organization_id\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "UQ_agent_env_profiles_user_default": { + "name": "UQ_agent_env_profiles_user_default", + "columns": [ + { + "expression": "owned_by_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"agent_environment_profiles\".\"is_default\" = true AND \"agent_environment_profiles\".\"owned_by_user_id\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_agent_env_profiles_org_id": { + "name": "IDX_agent_env_profiles_org_id", + "columns": [ + { + "expression": "owned_by_organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_agent_env_profiles_user_id": { + "name": "IDX_agent_env_profiles_user_id", + "columns": [ + { + "expression": "owned_by_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_agent_env_profiles_created_by_user_id": { + "name": "IDX_agent_env_profiles_created_by_user_id", + "columns": [ + { + "expression": "created_by_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "agent_environment_profiles_owned_by_organization_id_organizations_id_fk": { + "name": "agent_environment_profiles_owned_by_organization_id_organizations_id_fk", + "tableFrom": "agent_environment_profiles", + "tableTo": "organizations", + "columnsFrom": [ + "owned_by_organization_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "agent_environment_profiles_owned_by_user_id_kilocode_users_id_fk": { + "name": "agent_environment_profiles_owned_by_user_id_kilocode_users_id_fk", + "tableFrom": "agent_environment_profiles", + "tableTo": "kilocode_users", + "columnsFrom": [ + "owned_by_user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "agent_env_profiles_owner_check": { + "name": "agent_env_profiles_owner_check", + "value": "(\n (\"agent_environment_profiles\".\"owned_by_user_id\" IS NOT NULL AND \"agent_environment_profiles\".\"owned_by_organization_id\" IS NULL) OR\n (\"agent_environment_profiles\".\"owned_by_user_id\" IS NULL AND \"agent_environment_profiles\".\"owned_by_organization_id\" IS NOT NULL)\n )" + } + }, + "isRLSEnabled": false + }, + "public.ai_gateway_config": { + "name": "ai_gateway_config", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "default": 1 + }, + "config": { + "name": "config", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "ai_gateway_config_singleton": { + "name": "ai_gateway_config_singleton", + "value": "\"ai_gateway_config\".\"id\" = 1" + } + }, + "isRLSEnabled": false + }, + "public.ai_gateway_request_logging_opt_ins": { + "name": "ai_gateway_request_logging_opt_ins", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "default": 1 + }, + "opt_ins": { + "name": "opt_ins", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "ai_gateway_request_logging_opt_ins_singleton": { + "name": "ai_gateway_request_logging_opt_ins_singleton", + "value": "\"ai_gateway_request_logging_opt_ins\".\"id\" = 1" + } + }, + "isRLSEnabled": false + }, + "public.ai_gateway_sync_providers_state": { + "name": "ai_gateway_sync_providers_state", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "default": 1 + }, + "last_completed_at": { + "name": "last_completed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "stale_alert_last_posted_at": { + "name": "stale_alert_last_posted_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "ai_gateway_sync_providers_state_singleton": { + "name": "ai_gateway_sync_providers_state_singleton", + "value": "\"ai_gateway_sync_providers_state\".\"id\" = 1" + } + }, + "isRLSEnabled": false + }, + "public.analytics_event_outbox": { + "name": "analytics_event_outbox", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "pg_catalog.gen_random_uuid()" + }, + "event_uuid": { + "name": "event_uuid", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "event_name": { + "name": "event_name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "distinct_id": { + "name": "distinct_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "properties": { + "name": "properties", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "attempts": { + "name": "attempts", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "next_attempt_at": { + "name": "next_attempt_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "claimed_at": { + "name": "claimed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "delivered_at": { + "name": "delivered_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "last_error": { + "name": "last_error", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "UQ_analytics_event_outbox_event_uuid": { + "name": "UQ_analytics_event_outbox_event_uuid", + "columns": [ + { + "expression": "event_uuid", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_analytics_event_outbox_status_next_attempt_at": { + "name": "IDX_analytics_event_outbox_status_next_attempt_at", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "next_attempt_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.api_kind": { + "name": "api_kind", + "schema": "", + "columns": { + "api_kind_id": { + "name": "api_kind_id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "api_kind": { + "name": "api_kind", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "UQ_api_kind": { + "name": "UQ_api_kind", + "columns": [ + { + "expression": "api_kind", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.api_request_log": { + "name": "api_request_log", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "bigserial", + "primaryKey": true, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "kilo_user_id": { + "name": "kilo_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "organization_id": { + "name": "organization_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "session_id": { + "name": "session_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "vercel_request_id": { + "name": "vercel_request_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "provider": { + "name": "provider", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "model": { + "name": "model", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status_code": { + "name": "status_code", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "request": { + "name": "request", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "response": { + "name": "response", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "error": { + "name": "error", + "type": "jsonb", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "idx_api_request_log_created_at": { + "name": "idx_api_request_log_created_at", + "columns": [ + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.app_builder_feedback": { + "name": "app_builder_feedback", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "pg_catalog.gen_random_uuid()" + }, + "kilo_user_id": { + "name": "kilo_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "project_id": { + "name": "project_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "session_id": { + "name": "session_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "model": { + "name": "model", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "preview_status": { + "name": "preview_status", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "is_streaming": { + "name": "is_streaming", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "message_count": { + "name": "message_count", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "feedback_text": { + "name": "feedback_text", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "recent_messages": { + "name": "recent_messages", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "IDX_app_builder_feedback_created_at": { + "name": "IDX_app_builder_feedback_created_at", + "columns": [ + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_app_builder_feedback_kilo_user_id": { + "name": "IDX_app_builder_feedback_kilo_user_id", + "columns": [ + { + "expression": "kilo_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_app_builder_feedback_project_id": { + "name": "IDX_app_builder_feedback_project_id", + "columns": [ + { + "expression": "project_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "app_builder_feedback_kilo_user_id_kilocode_users_id_fk": { + "name": "app_builder_feedback_kilo_user_id_kilocode_users_id_fk", + "tableFrom": "app_builder_feedback", + "tableTo": "kilocode_users", + "columnsFrom": [ + "kilo_user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "cascade" + }, + "app_builder_feedback_project_id_app_builder_projects_id_fk": { + "name": "app_builder_feedback_project_id_app_builder_projects_id_fk", + "tableFrom": "app_builder_feedback", + "tableTo": "app_builder_projects", + "columnsFrom": [ + "project_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.app_builder_project_sessions": { + "name": "app_builder_project_sessions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "project_id": { + "name": "project_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "cloud_agent_session_id": { + "name": "cloud_agent_session_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "ended_at": { + "name": "ended_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "reason": { + "name": "reason", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "worker_version": { + "name": "worker_version", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'v2'" + } + }, + "indexes": { + "IDX_app_builder_project_sessions_project_id": { + "name": "IDX_app_builder_project_sessions_project_id", + "columns": [ + { + "expression": "project_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "app_builder_project_sessions_project_id_app_builder_projects_id_fk": { + "name": "app_builder_project_sessions_project_id_app_builder_projects_id_fk", + "tableFrom": "app_builder_project_sessions", + "tableTo": "app_builder_projects", + "columnsFrom": [ + "project_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "UQ_app_builder_project_sessions_cloud_agent_session_id": { + "name": "UQ_app_builder_project_sessions_cloud_agent_session_id", + "nullsNotDistinct": false, + "columns": [ + "cloud_agent_session_id" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.app_builder_projects": { + "name": "app_builder_projects", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "created_by_user_id": { + "name": "created_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "owned_by_user_id": { + "name": "owned_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "owned_by_organization_id": { + "name": "owned_by_organization_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "session_id": { + "name": "session_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "model_id": { + "name": "model_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "template": { + "name": "template", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "deployment_id": { + "name": "deployment_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "last_message_at": { + "name": "last_message_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "git_repo_full_name": { + "name": "git_repo_full_name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "git_platform_integration_id": { + "name": "git_platform_integration_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "migrated_at": { + "name": "migrated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "IDX_app_builder_projects_created_by_user_id": { + "name": "IDX_app_builder_projects_created_by_user_id", + "columns": [ + { + "expression": "created_by_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_app_builder_projects_owned_by_user_id": { + "name": "IDX_app_builder_projects_owned_by_user_id", + "columns": [ + { + "expression": "owned_by_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_app_builder_projects_owned_by_organization_id": { + "name": "IDX_app_builder_projects_owned_by_organization_id", + "columns": [ + { + "expression": "owned_by_organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_app_builder_projects_created_at": { + "name": "IDX_app_builder_projects_created_at", + "columns": [ + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_app_builder_projects_last_message_at": { + "name": "IDX_app_builder_projects_last_message_at", + "columns": [ + { + "expression": "last_message_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_app_builder_projects_git_repo_integration": { + "name": "IDX_app_builder_projects_git_repo_integration", + "columns": [ + { + "expression": "git_repo_full_name", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "git_platform_integration_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"app_builder_projects\".\"git_repo_full_name\" is not null", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "app_builder_projects_owned_by_user_id_kilocode_users_id_fk": { + "name": "app_builder_projects_owned_by_user_id_kilocode_users_id_fk", + "tableFrom": "app_builder_projects", + "tableTo": "kilocode_users", + "columnsFrom": [ + "owned_by_user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "app_builder_projects_owned_by_organization_id_organizations_id_fk": { + "name": "app_builder_projects_owned_by_organization_id_organizations_id_fk", + "tableFrom": "app_builder_projects", + "tableTo": "organizations", + "columnsFrom": [ + "owned_by_organization_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "app_builder_projects_deployment_id_deployments_id_fk": { + "name": "app_builder_projects_deployment_id_deployments_id_fk", + "tableFrom": "app_builder_projects", + "tableTo": "deployments", + "columnsFrom": [ + "deployment_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "app_builder_projects_git_platform_integration_id_platform_integrations_id_fk": { + "name": "app_builder_projects_git_platform_integration_id_platform_integrations_id_fk", + "tableFrom": "app_builder_projects", + "tableTo": "platform_integrations", + "columnsFrom": [ + "git_platform_integration_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "app_builder_projects_owner_check": { + "name": "app_builder_projects_owner_check", + "value": "(\n (\"app_builder_projects\".\"owned_by_user_id\" IS NOT NULL AND \"app_builder_projects\".\"owned_by_organization_id\" IS NULL) OR\n (\"app_builder_projects\".\"owned_by_user_id\" IS NULL AND \"app_builder_projects\".\"owned_by_organization_id\" IS NOT NULL)\n )" + } + }, + "isRLSEnabled": false + }, + "public.app_min_versions": { + "name": "app_min_versions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "pg_catalog.gen_random_uuid()" + }, + "ios_min_version": { + "name": "ios_min_version", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'1.0.0'" + }, + "android_min_version": { + "name": "android_min_version", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'1.0.0'" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.app_reported_messages": { + "name": "app_reported_messages", + "schema": "", + "columns": { + "report_id": { + "name": "report_id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "pg_catalog.gen_random_uuid()" + }, + "report_type": { + "name": "report_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "signature": { + "name": "signature", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "message": { + "name": "message", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "cli_session_id": { + "name": "cli_session_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "mode": { + "name": "mode", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "model": { + "name": "model", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "app_reported_messages_cli_session_id_cli_sessions_session_id_fk": { + "name": "app_reported_messages_cli_session_id_cli_sessions_session_id_fk", + "tableFrom": "app_reported_messages", + "tableTo": "cli_sessions", + "columnsFrom": [ + "cli_session_id" + ], + "columnsTo": [ + "session_id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.auto_fix_tickets": { + "name": "auto_fix_tickets", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "pg_catalog.gen_random_uuid()" + }, + "owned_by_organization_id": { + "name": "owned_by_organization_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "owned_by_user_id": { + "name": "owned_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "platform_integration_id": { + "name": "platform_integration_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "triage_ticket_id": { + "name": "triage_ticket_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "platform": { + "name": "platform", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'github'" + }, + "repo_full_name": { + "name": "repo_full_name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "issue_number": { + "name": "issue_number", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "issue_url": { + "name": "issue_url", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "issue_title": { + "name": "issue_title", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "issue_body": { + "name": "issue_body", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "issue_author": { + "name": "issue_author", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "issue_labels": { + "name": "issue_labels", + "type": "text[]", + "primaryKey": false, + "notNull": false, + "default": "'{}'" + }, + "trigger_source": { + "name": "trigger_source", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'label'" + }, + "review_comment_id": { + "name": "review_comment_id", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "review_comment_body": { + "name": "review_comment_body", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "file_path": { + "name": "file_path", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "line_number": { + "name": "line_number", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "diff_hunk": { + "name": "diff_hunk", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "pr_head_ref": { + "name": "pr_head_ref", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "classification": { + "name": "classification", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "confidence": { + "name": "confidence", + "type": "numeric(3, 2)", + "primaryKey": false, + "notNull": false + }, + "intent_summary": { + "name": "intent_summary", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "related_files": { + "name": "related_files", + "type": "text[]", + "primaryKey": false, + "notNull": false + }, + "session_id": { + "name": "session_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "cli_session_id": { + "name": "cli_session_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "pr_number": { + "name": "pr_number", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "pr_url": { + "name": "pr_url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "pr_branch": { + "name": "pr_branch", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "error_message": { + "name": "error_message", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "started_at": { + "name": "started_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "completed_at": { + "name": "completed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "UQ_auto_fix_tickets_repo_issue": { + "name": "UQ_auto_fix_tickets_repo_issue", + "columns": [ + { + "expression": "repo_full_name", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "issue_number", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"auto_fix_tickets\".\"trigger_source\" = 'label'", + "concurrently": false, + "method": "btree", + "with": {} + }, + "UQ_auto_fix_tickets_repo_review_comment": { + "name": "UQ_auto_fix_tickets_repo_review_comment", + "columns": [ + { + "expression": "repo_full_name", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "review_comment_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"auto_fix_tickets\".\"review_comment_id\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_auto_fix_tickets_owned_by_org": { + "name": "IDX_auto_fix_tickets_owned_by_org", + "columns": [ + { + "expression": "owned_by_organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_auto_fix_tickets_owned_by_user": { + "name": "IDX_auto_fix_tickets_owned_by_user", + "columns": [ + { + "expression": "owned_by_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_auto_fix_tickets_status": { + "name": "IDX_auto_fix_tickets_status", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_auto_fix_tickets_created_at": { + "name": "IDX_auto_fix_tickets_created_at", + "columns": [ + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_auto_fix_tickets_triage_ticket_id": { + "name": "IDX_auto_fix_tickets_triage_ticket_id", + "columns": [ + { + "expression": "triage_ticket_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_auto_fix_tickets_session_id": { + "name": "IDX_auto_fix_tickets_session_id", + "columns": [ + { + "expression": "session_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "auto_fix_tickets_owned_by_organization_id_organizations_id_fk": { + "name": "auto_fix_tickets_owned_by_organization_id_organizations_id_fk", + "tableFrom": "auto_fix_tickets", + "tableTo": "organizations", + "columnsFrom": [ + "owned_by_organization_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "auto_fix_tickets_owned_by_user_id_kilocode_users_id_fk": { + "name": "auto_fix_tickets_owned_by_user_id_kilocode_users_id_fk", + "tableFrom": "auto_fix_tickets", + "tableTo": "kilocode_users", + "columnsFrom": [ + "owned_by_user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "auto_fix_tickets_platform_integration_id_platform_integrations_id_fk": { + "name": "auto_fix_tickets_platform_integration_id_platform_integrations_id_fk", + "tableFrom": "auto_fix_tickets", + "tableTo": "platform_integrations", + "columnsFrom": [ + "platform_integration_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "auto_fix_tickets_triage_ticket_id_auto_triage_tickets_id_fk": { + "name": "auto_fix_tickets_triage_ticket_id_auto_triage_tickets_id_fk", + "tableFrom": "auto_fix_tickets", + "tableTo": "auto_triage_tickets", + "columnsFrom": [ + "triage_ticket_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "auto_fix_tickets_cli_session_id_cli_sessions_session_id_fk": { + "name": "auto_fix_tickets_cli_session_id_cli_sessions_session_id_fk", + "tableFrom": "auto_fix_tickets", + "tableTo": "cli_sessions", + "columnsFrom": [ + "cli_session_id" + ], + "columnsTo": [ + "session_id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "auto_fix_tickets_owner_check": { + "name": "auto_fix_tickets_owner_check", + "value": "(\n (\"auto_fix_tickets\".\"owned_by_user_id\" IS NOT NULL AND \"auto_fix_tickets\".\"owned_by_organization_id\" IS NULL) OR\n (\"auto_fix_tickets\".\"owned_by_user_id\" IS NULL AND \"auto_fix_tickets\".\"owned_by_organization_id\" IS NOT NULL)\n )" + }, + "auto_fix_tickets_status_check": { + "name": "auto_fix_tickets_status_check", + "value": "\"auto_fix_tickets\".\"status\" IN ('pending', 'running', 'completed', 'failed', 'cancelled')" + }, + "auto_fix_tickets_classification_check": { + "name": "auto_fix_tickets_classification_check", + "value": "\"auto_fix_tickets\".\"classification\" IN ('bug', 'feature', 'question', 'unclear')" + }, + "auto_fix_tickets_confidence_check": { + "name": "auto_fix_tickets_confidence_check", + "value": "\"auto_fix_tickets\".\"confidence\" >= 0 AND \"auto_fix_tickets\".\"confidence\" <= 1" + }, + "auto_fix_tickets_trigger_source_check": { + "name": "auto_fix_tickets_trigger_source_check", + "value": "\"auto_fix_tickets\".\"trigger_source\" IN ('label', 'review_comment')" + } + }, + "isRLSEnabled": false + }, + "public.auto_model": { + "name": "auto_model", + "schema": "", + "columns": { + "auto_model_id": { + "name": "auto_model_id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "auto_model": { + "name": "auto_model", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "UQ_auto_model": { + "name": "UQ_auto_model", + "columns": [ + { + "expression": "auto_model", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.auto_top_up_configs": { + "name": "auto_top_up_configs", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "pg_catalog.gen_random_uuid()" + }, + "owned_by_user_id": { + "name": "owned_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "owned_by_organization_id": { + "name": "owned_by_organization_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "created_by_user_id": { + "name": "created_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "stripe_payment_method_id": { + "name": "stripe_payment_method_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "amount_cents": { + "name": "amount_cents", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 5000 + }, + "last_auto_top_up_at": { + "name": "last_auto_top_up_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "attempt_started_at": { + "name": "attempt_started_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "disabled_reason": { + "name": "disabled_reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "UQ_auto_top_up_configs_owned_by_user_id": { + "name": "UQ_auto_top_up_configs_owned_by_user_id", + "columns": [ + { + "expression": "owned_by_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"auto_top_up_configs\".\"owned_by_user_id\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "UQ_auto_top_up_configs_owned_by_organization_id": { + "name": "UQ_auto_top_up_configs_owned_by_organization_id", + "columns": [ + { + "expression": "owned_by_organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"auto_top_up_configs\".\"owned_by_organization_id\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "auto_top_up_configs_owned_by_user_id_kilocode_users_id_fk": { + "name": "auto_top_up_configs_owned_by_user_id_kilocode_users_id_fk", + "tableFrom": "auto_top_up_configs", + "tableTo": "kilocode_users", + "columnsFrom": [ + "owned_by_user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "cascade" + }, + "auto_top_up_configs_owned_by_organization_id_organizations_id_fk": { + "name": "auto_top_up_configs_owned_by_organization_id_organizations_id_fk", + "tableFrom": "auto_top_up_configs", + "tableTo": "organizations", + "columnsFrom": [ + "owned_by_organization_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "auto_top_up_configs_exactly_one_owner": { + "name": "auto_top_up_configs_exactly_one_owner", + "value": "(\"auto_top_up_configs\".\"owned_by_user_id\" IS NOT NULL AND \"auto_top_up_configs\".\"owned_by_organization_id\" IS NULL) OR (\"auto_top_up_configs\".\"owned_by_user_id\" IS NULL AND \"auto_top_up_configs\".\"owned_by_organization_id\" IS NOT NULL)" + } + }, + "isRLSEnabled": false + }, + "public.auto_triage_tickets": { + "name": "auto_triage_tickets", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "pg_catalog.gen_random_uuid()" + }, + "owned_by_organization_id": { + "name": "owned_by_organization_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "owned_by_user_id": { + "name": "owned_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "platform_integration_id": { + "name": "platform_integration_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "platform": { + "name": "platform", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'github'" + }, + "repo_full_name": { + "name": "repo_full_name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "issue_number": { + "name": "issue_number", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "issue_url": { + "name": "issue_url", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "issue_title": { + "name": "issue_title", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "issue_body": { + "name": "issue_body", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "issue_author": { + "name": "issue_author", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "issue_type": { + "name": "issue_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "issue_labels": { + "name": "issue_labels", + "type": "text[]", + "primaryKey": false, + "notNull": false, + "default": "'{}'" + }, + "classification": { + "name": "classification", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "confidence": { + "name": "confidence", + "type": "numeric(3, 2)", + "primaryKey": false, + "notNull": false + }, + "intent_summary": { + "name": "intent_summary", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "related_files": { + "name": "related_files", + "type": "text[]", + "primaryKey": false, + "notNull": false + }, + "is_duplicate": { + "name": "is_duplicate", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "duplicate_of_ticket_id": { + "name": "duplicate_of_ticket_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "similarity_score": { + "name": "similarity_score", + "type": "numeric(3, 2)", + "primaryKey": false, + "notNull": false + }, + "qdrant_point_id": { + "name": "qdrant_point_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "session_id": { + "name": "session_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "should_auto_fix": { + "name": "should_auto_fix", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "action_taken": { + "name": "action_taken", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "action_metadata": { + "name": "action_metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "error_message": { + "name": "error_message", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "started_at": { + "name": "started_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "completed_at": { + "name": "completed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "UQ_auto_triage_tickets_repo_issue": { + "name": "UQ_auto_triage_tickets_repo_issue", + "columns": [ + { + "expression": "repo_full_name", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "issue_number", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_auto_triage_tickets_owned_by_org": { + "name": "IDX_auto_triage_tickets_owned_by_org", + "columns": [ + { + "expression": "owned_by_organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_auto_triage_tickets_owned_by_user": { + "name": "IDX_auto_triage_tickets_owned_by_user", + "columns": [ + { + "expression": "owned_by_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_auto_triage_tickets_status": { + "name": "IDX_auto_triage_tickets_status", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_auto_triage_tickets_created_at": { + "name": "IDX_auto_triage_tickets_created_at", + "columns": [ + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_auto_triage_tickets_qdrant_point_id": { + "name": "IDX_auto_triage_tickets_qdrant_point_id", + "columns": [ + { + "expression": "qdrant_point_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_auto_triage_tickets_owner_status_created": { + "name": "IDX_auto_triage_tickets_owner_status_created", + "columns": [ + { + "expression": "owned_by_organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_auto_triage_tickets_user_status_created": { + "name": "IDX_auto_triage_tickets_user_status_created", + "columns": [ + { + "expression": "owned_by_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_auto_triage_tickets_repo_classification": { + "name": "IDX_auto_triage_tickets_repo_classification", + "columns": [ + { + "expression": "repo_full_name", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "classification", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "auto_triage_tickets_owned_by_organization_id_organizations_id_fk": { + "name": "auto_triage_tickets_owned_by_organization_id_organizations_id_fk", + "tableFrom": "auto_triage_tickets", + "tableTo": "organizations", + "columnsFrom": [ + "owned_by_organization_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "auto_triage_tickets_owned_by_user_id_kilocode_users_id_fk": { + "name": "auto_triage_tickets_owned_by_user_id_kilocode_users_id_fk", + "tableFrom": "auto_triage_tickets", + "tableTo": "kilocode_users", + "columnsFrom": [ + "owned_by_user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "auto_triage_tickets_platform_integration_id_platform_integrations_id_fk": { + "name": "auto_triage_tickets_platform_integration_id_platform_integrations_id_fk", + "tableFrom": "auto_triage_tickets", + "tableTo": "platform_integrations", + "columnsFrom": [ + "platform_integration_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "auto_triage_tickets_duplicate_of_ticket_id_auto_triage_tickets_id_fk": { + "name": "auto_triage_tickets_duplicate_of_ticket_id_auto_triage_tickets_id_fk", + "tableFrom": "auto_triage_tickets", + "tableTo": "auto_triage_tickets", + "columnsFrom": [ + "duplicate_of_ticket_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "auto_triage_tickets_owner_check": { + "name": "auto_triage_tickets_owner_check", + "value": "(\n (\"auto_triage_tickets\".\"owned_by_user_id\" IS NOT NULL AND \"auto_triage_tickets\".\"owned_by_organization_id\" IS NULL) OR\n (\"auto_triage_tickets\".\"owned_by_user_id\" IS NULL AND \"auto_triage_tickets\".\"owned_by_organization_id\" IS NOT NULL)\n )" + }, + "auto_triage_tickets_issue_type_check": { + "name": "auto_triage_tickets_issue_type_check", + "value": "\"auto_triage_tickets\".\"issue_type\" IN ('issue', 'pull_request')" + }, + "auto_triage_tickets_classification_check": { + "name": "auto_triage_tickets_classification_check", + "value": "\"auto_triage_tickets\".\"classification\" IN ('bug', 'feature', 'question', 'duplicate', 'unclear')" + }, + "auto_triage_tickets_confidence_check": { + "name": "auto_triage_tickets_confidence_check", + "value": "\"auto_triage_tickets\".\"confidence\" >= 0 AND \"auto_triage_tickets\".\"confidence\" <= 1" + }, + "auto_triage_tickets_similarity_score_check": { + "name": "auto_triage_tickets_similarity_score_check", + "value": "\"auto_triage_tickets\".\"similarity_score\" >= 0 AND \"auto_triage_tickets\".\"similarity_score\" <= 1" + }, + "auto_triage_tickets_status_check": { + "name": "auto_triage_tickets_status_check", + "value": "\"auto_triage_tickets\".\"status\" IN ('pending', 'analyzing', 'actioned', 'failed', 'skipped')" + }, + "auto_triage_tickets_action_taken_check": { + "name": "auto_triage_tickets_action_taken_check", + "value": "\"auto_triage_tickets\".\"action_taken\" IN ('pr_created', 'comment_posted', 'closed_duplicate', 'needs_clarification')" + } + }, + "isRLSEnabled": false + }, + "public.bot_request_cloud_agent_sessions": { + "name": "bot_request_cloud_agent_sessions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "pg_catalog.gen_random_uuid()" + }, + "bot_request_id": { + "name": "bot_request_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "spawn_group_id": { + "name": "spawn_group_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "cloud_agent_session_id": { + "name": "cloud_agent_session_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "kilo_session_id": { + "name": "kilo_session_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "execution_id": { + "name": "execution_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'running'" + }, + "mode": { + "name": "mode", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "github_repo": { + "name": "github_repo", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "gitlab_project": { + "name": "gitlab_project", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "callback_step": { + "name": "callback_step", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "error_message": { + "name": "error_message", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "final_message": { + "name": "final_message", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "final_message_fetched_at": { + "name": "final_message_fetched_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "final_message_error": { + "name": "final_message_error", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "terminal_at": { + "name": "terminal_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "continuation_started_at": { + "name": "continuation_started_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "UQ_bot_request_cas_cloud_agent_session_id": { + "name": "UQ_bot_request_cas_cloud_agent_session_id", + "columns": [ + { + "expression": "cloud_agent_session_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_bot_request_cas_bot_request_id": { + "name": "IDX_bot_request_cas_bot_request_id", + "columns": [ + { + "expression": "bot_request_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_bot_request_cas_bot_request_id_spawn_group_id": { + "name": "IDX_bot_request_cas_bot_request_id_spawn_group_id", + "columns": [ + { + "expression": "bot_request_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "spawn_group_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_bot_request_cas_bot_request_id_spawn_group_id_status": { + "name": "IDX_bot_request_cas_bot_request_id_spawn_group_id_status", + "columns": [ + { + "expression": "bot_request_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "spawn_group_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "bot_request_cloud_agent_sessions_bot_request_id_bot_requests_id_fk": { + "name": "bot_request_cloud_agent_sessions_bot_request_id_bot_requests_id_fk", + "tableFrom": "bot_request_cloud_agent_sessions", + "tableTo": "bot_requests", + "columnsFrom": [ + "bot_request_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.bot_requests": { + "name": "bot_requests", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "pg_catalog.gen_random_uuid()" + }, + "created_by": { + "name": "created_by", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "organization_id": { + "name": "organization_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "platform_integration_id": { + "name": "platform_integration_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "platform": { + "name": "platform", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "platform_thread_id": { + "name": "platform_thread_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "platform_message_id": { + "name": "platform_message_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "user_message": { + "name": "user_message", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "error_message": { + "name": "error_message", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "model_used": { + "name": "model_used", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "steps": { + "name": "steps", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "cloud_agent_session_id": { + "name": "cloud_agent_session_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "response_time_ms": { + "name": "response_time_ms", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "IDX_bot_requests_created_at": { + "name": "IDX_bot_requests_created_at", + "columns": [ + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_bot_requests_created_by": { + "name": "IDX_bot_requests_created_by", + "columns": [ + { + "expression": "created_by", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_bot_requests_organization_id": { + "name": "IDX_bot_requests_organization_id", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_bot_requests_platform_integration_id": { + "name": "IDX_bot_requests_platform_integration_id", + "columns": [ + { + "expression": "platform_integration_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_bot_requests_status": { + "name": "IDX_bot_requests_status", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "bot_requests_created_by_kilocode_users_id_fk": { + "name": "bot_requests_created_by_kilocode_users_id_fk", + "tableFrom": "bot_requests", + "tableTo": "kilocode_users", + "columnsFrom": [ + "created_by" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "bot_requests_organization_id_organizations_id_fk": { + "name": "bot_requests_organization_id_organizations_id_fk", + "tableFrom": "bot_requests", + "tableTo": "organizations", + "columnsFrom": [ + "organization_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "bot_requests_platform_integration_id_platform_integrations_id_fk": { + "name": "bot_requests_platform_integration_id_platform_integrations_id_fk", + "tableFrom": "bot_requests", + "tableTo": "platform_integrations", + "columnsFrom": [ + "platform_integration_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.byok_api_keys": { + "name": "byok_api_keys", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "pg_catalog.gen_random_uuid()" + }, + "organization_id": { + "name": "organization_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "kilo_user_id": { + "name": "kilo_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "provider_id": { + "name": "provider_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "encrypted_api_key": { + "name": "encrypted_api_key", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "management_source": { + "name": "management_source", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'user'" + }, + "is_enabled": { + "name": "is_enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "created_by": { + "name": "created_by", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "IDX_byok_api_keys_organization_id": { + "name": "IDX_byok_api_keys_organization_id", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_byok_api_keys_kilo_user_id": { + "name": "IDX_byok_api_keys_kilo_user_id", + "columns": [ + { + "expression": "kilo_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_byok_api_keys_provider_id": { + "name": "IDX_byok_api_keys_provider_id", + "columns": [ + { + "expression": "provider_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "byok_api_keys_organization_id_organizations_id_fk": { + "name": "byok_api_keys_organization_id_organizations_id_fk", + "tableFrom": "byok_api_keys", + "tableTo": "organizations", + "columnsFrom": [ + "organization_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "byok_api_keys_kilo_user_id_kilocode_users_id_fk": { + "name": "byok_api_keys_kilo_user_id_kilocode_users_id_fk", + "tableFrom": "byok_api_keys", + "tableTo": "kilocode_users", + "columnsFrom": [ + "kilo_user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "UQ_byok_api_keys_org_provider": { + "name": "UQ_byok_api_keys_org_provider", + "nullsNotDistinct": false, + "columns": [ + "organization_id", + "provider_id" + ] + }, + "UQ_byok_api_keys_user_provider": { + "name": "UQ_byok_api_keys_user_provider", + "nullsNotDistinct": false, + "columns": [ + "kilo_user_id", + "provider_id" + ] + } + }, + "policies": {}, + "checkConstraints": { + "byok_api_keys_management_source_check": { + "name": "byok_api_keys_management_source_check", + "value": "\"byok_api_keys\".\"management_source\" IN ('user', 'coding_plan')" + }, + "byok_api_keys_owner_check": { + "name": "byok_api_keys_owner_check", + "value": "(\n (\"byok_api_keys\".\"kilo_user_id\" IS NOT NULL AND \"byok_api_keys\".\"organization_id\" IS NULL) OR\n (\"byok_api_keys\".\"kilo_user_id\" IS NULL AND \"byok_api_keys\".\"organization_id\" IS NOT NULL)\n )" + } + }, + "isRLSEnabled": false + }, + "public.cli_sessions": { + "name": "cli_sessions", + "schema": "", + "columns": { + "session_id": { + "name": "session_id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "pg_catalog.gen_random_uuid()" + }, + "kilo_user_id": { + "name": "kilo_user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_on_platform": { + "name": "created_on_platform", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'unknown'" + }, + "api_conversation_history_blob_url": { + "name": "api_conversation_history_blob_url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "task_metadata_blob_url": { + "name": "task_metadata_blob_url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "ui_messages_blob_url": { + "name": "ui_messages_blob_url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "git_state_blob_url": { + "name": "git_state_blob_url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "git_url": { + "name": "git_url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "forked_from": { + "name": "forked_from", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "parent_session_id": { + "name": "parent_session_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "cloud_agent_session_id": { + "name": "cloud_agent_session_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "organization_id": { + "name": "organization_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "last_mode": { + "name": "last_mode", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "last_model": { + "name": "last_model", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "version": { + "name": "version", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "IDX_cli_sessions_kilo_user_id": { + "name": "IDX_cli_sessions_kilo_user_id", + "columns": [ + { + "expression": "kilo_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_cli_sessions_created_at": { + "name": "IDX_cli_sessions_created_at", + "columns": [ + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_cli_sessions_updated_at": { + "name": "IDX_cli_sessions_updated_at", + "columns": [ + { + "expression": "updated_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_cli_sessions_organization_id": { + "name": "IDX_cli_sessions_organization_id", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_cli_sessions_user_updated": { + "name": "IDX_cli_sessions_user_updated", + "columns": [ + { + "expression": "kilo_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "updated_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "cli_sessions_kilo_user_id_kilocode_users_id_fk": { + "name": "cli_sessions_kilo_user_id_kilocode_users_id_fk", + "tableFrom": "cli_sessions", + "tableTo": "kilocode_users", + "columnsFrom": [ + "kilo_user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "restrict", + "onUpdate": "no action" + }, + "cli_sessions_forked_from_cli_sessions_session_id_fk": { + "name": "cli_sessions_forked_from_cli_sessions_session_id_fk", + "tableFrom": "cli_sessions", + "tableTo": "cli_sessions", + "columnsFrom": [ + "forked_from" + ], + "columnsTo": [ + "session_id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "cli_sessions_parent_session_id_cli_sessions_session_id_fk": { + "name": "cli_sessions_parent_session_id_cli_sessions_session_id_fk", + "tableFrom": "cli_sessions", + "tableTo": "cli_sessions", + "columnsFrom": [ + "parent_session_id" + ], + "columnsTo": [ + "session_id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "cli_sessions_organization_id_organizations_id_fk": { + "name": "cli_sessions_organization_id_organizations_id_fk", + "tableFrom": "cli_sessions", + "tableTo": "organizations", + "columnsFrom": [ + "organization_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "cli_sessions_cloud_agent_session_id_unique": { + "name": "cli_sessions_cloud_agent_session_id_unique", + "nullsNotDistinct": false, + "columns": [ + "cloud_agent_session_id" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.cli_sessions_v2": { + "name": "cli_sessions_v2", + "schema": "", + "columns": { + "session_id": { + "name": "session_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "kilo_user_id": { + "name": "kilo_user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "version": { + "name": "version", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "public_id": { + "name": "public_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "parent_session_id": { + "name": "parent_session_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "organization_id": { + "name": "organization_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "profile_id": { + "name": "profile_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "cloud_agent_session_id": { + "name": "cloud_agent_session_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "cloud_agent_session_scope_id": { + "name": "cloud_agent_session_scope_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "cloud_agent_worktree_id": { + "name": "cloud_agent_worktree_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_on_platform": { + "name": "created_on_platform", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'unknown'" + }, + "git_url": { + "name": "git_url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "git_branch": { + "name": "git_branch", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "platform": { + "name": "platform", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "pr_url": { + "name": "pr_url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "pr_number": { + "name": "pr_number", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status_updated_at": { + "name": "status_updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "last_activity_at": { + "name": "last_activity_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "total_cost_microdollars": { + "name": "total_cost_microdollars", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "IDX_cli_sessions_v2_parent_session_id_kilo_user_id": { + "name": "IDX_cli_sessions_v2_parent_session_id_kilo_user_id", + "columns": [ + { + "expression": "parent_session_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "kilo_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_cli_sessions_v2_profile_id": { + "name": "IDX_cli_sessions_v2_profile_id", + "columns": [ + { + "expression": "profile_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": true, + "method": "btree", + "with": {} + }, + "UQ_cli_sessions_v2_public_id": { + "name": "UQ_cli_sessions_v2_public_id", + "columns": [ + { + "expression": "public_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"cli_sessions_v2\".\"public_id\" is not null", + "concurrently": false, + "method": "btree", + "with": {} + }, + "UQ_cli_sessions_v2_cloud_agent_session_id": { + "name": "UQ_cli_sessions_v2_cloud_agent_session_id", + "columns": [ + { + "expression": "cloud_agent_session_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"cli_sessions_v2\".\"cloud_agent_session_id\" is not null", + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_cli_sessions_v2_organization_id": { + "name": "IDX_cli_sessions_v2_organization_id", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_cli_sessions_v2_user_updated": { + "name": "IDX_cli_sessions_v2_user_updated", + "columns": [ + { + "expression": "kilo_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "updated_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_cli_sessions_v2_user_created": { + "name": "IDX_cli_sessions_v2_user_created", + "columns": [ + { + "expression": "kilo_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": true, + "method": "btree", + "with": {} + }, + "IDX_cli_sessions_v2_user_worktree_updated": { + "name": "IDX_cli_sessions_v2_user_worktree_updated", + "columns": [ + { + "expression": "kilo_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "cloud_agent_worktree_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "updated_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"cli_sessions_v2\".\"cloud_agent_worktree_id\" is not null", + "concurrently": true, + "method": "btree", + "with": {} + }, + "cli_sessions_v2_git_url_branch_idx": { + "name": "cli_sessions_v2_git_url_branch_idx", + "columns": [ + { + "expression": "git_url", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "git_branch", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "cli_sessions_v2_kilo_user_id_kilocode_users_id_fk": { + "name": "cli_sessions_v2_kilo_user_id_kilocode_users_id_fk", + "tableFrom": "cli_sessions_v2", + "tableTo": "kilocode_users", + "columnsFrom": [ + "kilo_user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "restrict", + "onUpdate": "no action" + }, + "cli_sessions_v2_organization_id_organizations_id_fk": { + "name": "cli_sessions_v2_organization_id_organizations_id_fk", + "tableFrom": "cli_sessions_v2", + "tableTo": "organizations", + "columnsFrom": [ + "organization_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "cli_sessions_v2_profile_id_agent_environment_profiles_id_fk": { + "name": "cli_sessions_v2_profile_id_agent_environment_profiles_id_fk", + "tableFrom": "cli_sessions_v2", + "tableTo": "agent_environment_profiles", + "columnsFrom": [ + "profile_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "cli_sessions_v2_parent_session_id_kilo_user_id_fk": { + "name": "cli_sessions_v2_parent_session_id_kilo_user_id_fk", + "tableFrom": "cli_sessions_v2", + "tableTo": "cli_sessions_v2", + "columnsFrom": [ + "parent_session_id", + "kilo_user_id" + ], + "columnsTo": [ + "session_id", + "kilo_user_id" + ], + "onDelete": "restrict", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "cli_sessions_v2_session_id_kilo_user_id_pk": { + "name": "cli_sessions_v2_session_id_kilo_user_id_pk", + "columns": [ + "session_id", + "kilo_user_id" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.cloud_agent_code_review_attempts": { + "name": "cloud_agent_code_review_attempts", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "pg_catalog.gen_random_uuid()" + }, + "code_review_id": { + "name": "code_review_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "attempt_number": { + "name": "attempt_number", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "retry_of_attempt_id": { + "name": "retry_of_attempt_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "retry_reason": { + "name": "retry_reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "session_id": { + "name": "session_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "cli_session_id": { + "name": "cli_session_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "execution_id": { + "name": "execution_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "analytics_enabled_at_dispatch": { + "name": "analytics_enabled_at_dispatch", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "error_message": { + "name": "error_message", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "terminal_reason": { + "name": "terminal_reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "started_at": { + "name": "started_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "completed_at": { + "name": "completed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "UQ_cloud_agent_code_review_attempts_review_attempt_number": { + "name": "UQ_cloud_agent_code_review_attempts_review_attempt_number", + "columns": [ + { + "expression": "code_review_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "attempt_number", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_cloud_agent_code_review_attempts_code_review_id": { + "name": "idx_cloud_agent_code_review_attempts_code_review_id", + "columns": [ + { + "expression": "code_review_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_cloud_agent_code_review_attempts_retry_of_attempt_id": { + "name": "idx_cloud_agent_code_review_attempts_retry_of_attempt_id", + "columns": [ + { + "expression": "retry_of_attempt_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": true, + "method": "btree", + "with": {} + }, + "idx_cloud_agent_code_review_attempts_session_id": { + "name": "idx_cloud_agent_code_review_attempts_session_id", + "columns": [ + { + "expression": "session_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_cloud_agent_code_review_attempts_cli_session_id": { + "name": "idx_cloud_agent_code_review_attempts_cli_session_id", + "columns": [ + { + "expression": "cli_session_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_cloud_agent_code_review_attempts_status": { + "name": "idx_cloud_agent_code_review_attempts_status", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_cloud_agent_code_review_attempts_retry_reason": { + "name": "idx_cloud_agent_code_review_attempts_retry_reason", + "columns": [ + { + "expression": "retry_reason", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "cloud_agent_code_review_attempts_code_review_id_cloud_agent_code_reviews_id_fk": { + "name": "cloud_agent_code_review_attempts_code_review_id_cloud_agent_code_reviews_id_fk", + "tableFrom": "cloud_agent_code_review_attempts", + "tableTo": "cloud_agent_code_reviews", + "columnsFrom": [ + "code_review_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "cloud_agent_code_review_attempts_retry_of_attempt_id_cloud_agent_code_review_attempts_id_fk": { + "name": "cloud_agent_code_review_attempts_retry_of_attempt_id_cloud_agent_code_review_attempts_id_fk", + "tableFrom": "cloud_agent_code_review_attempts", + "tableTo": "cloud_agent_code_review_attempts", + "columnsFrom": [ + "retry_of_attempt_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "cloud_agent_code_review_attempts_attempt_number_check": { + "name": "cloud_agent_code_review_attempts_attempt_number_check", + "value": "\"cloud_agent_code_review_attempts\".\"attempt_number\" >= 1" + } + }, + "isRLSEnabled": false + }, + "public.cloud_agent_code_reviews": { + "name": "cloud_agent_code_reviews", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "pg_catalog.gen_random_uuid()" + }, + "owned_by_organization_id": { + "name": "owned_by_organization_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "owned_by_user_id": { + "name": "owned_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "platform_integration_id": { + "name": "platform_integration_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "manual_config": { + "name": "manual_config", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "review_type": { + "name": "review_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'standard'" + }, + "trigger_source": { + "name": "trigger_source", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "council_result": { + "name": "council_result", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "repo_full_name": { + "name": "repo_full_name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "pr_number": { + "name": "pr_number", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "pr_url": { + "name": "pr_url", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "pr_title": { + "name": "pr_title", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "pr_author": { + "name": "pr_author", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "pr_author_github_id": { + "name": "pr_author_github_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "base_ref": { + "name": "base_ref", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "head_ref": { + "name": "head_ref", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "head_sha": { + "name": "head_sha", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "platform": { + "name": "platform", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'github'" + }, + "platform_project_id": { + "name": "platform_project_id", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "session_id": { + "name": "session_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "cli_session_id": { + "name": "cli_session_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "dispatch_reservation_id": { + "name": "dispatch_reservation_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "error_message": { + "name": "error_message", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "terminal_reason": { + "name": "terminal_reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "agent_version": { + "name": "agent_version", + "type": "text", + "primaryKey": false, + "notNull": false, + "default": "'v1'" + }, + "check_run_id": { + "name": "check_run_id", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "repository_review_instructions_used": { + "name": "repository_review_instructions_used", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "repository_review_instructions_ref": { + "name": "repository_review_instructions_ref", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "repository_review_instructions_truncated": { + "name": "repository_review_instructions_truncated", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "previous_summary_body": { + "name": "previous_summary_body", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "previous_summary_head_sha": { + "name": "previous_summary_head_sha", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "model": { + "name": "model", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "total_tokens_in": { + "name": "total_tokens_in", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "total_tokens_out": { + "name": "total_tokens_out", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "total_cost_musd": { + "name": "total_cost_musd", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "started_at": { + "name": "started_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "completed_at": { + "name": "completed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "UQ_cloud_agent_code_reviews_webhook_integration_repo_pr_sha": { + "name": "UQ_cloud_agent_code_reviews_webhook_integration_repo_pr_sha", + "columns": [ + { + "expression": "platform_integration_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "repo_full_name", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "pr_number", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "head_sha", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"cloud_agent_code_reviews\".\"manual_config\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "UQ_cloud_agent_code_reviews_active_provider_publisher": { + "name": "UQ_cloud_agent_code_reviews_active_provider_publisher", + "columns": [ + { + "expression": "platform_integration_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "repo_full_name", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "pr_number", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"cloud_agent_code_reviews\".\"platform_integration_id\" IS NOT NULL\n AND \"cloud_agent_code_reviews\".\"status\" IN ('pending', 'queued', 'running')\n AND (\"cloud_agent_code_reviews\".\"manual_config\" IS NULL OR \"cloud_agent_code_reviews\".\"manual_config\"->>'outputMode' = 'provider')", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_cloud_agent_code_reviews_owned_by_org_id": { + "name": "idx_cloud_agent_code_reviews_owned_by_org_id", + "columns": [ + { + "expression": "owned_by_organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_cloud_agent_code_reviews_owned_by_user_id": { + "name": "idx_cloud_agent_code_reviews_owned_by_user_id", + "columns": [ + { + "expression": "owned_by_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_cloud_agent_code_reviews_session_id": { + "name": "idx_cloud_agent_code_reviews_session_id", + "columns": [ + { + "expression": "session_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_cloud_agent_code_reviews_cli_session_id": { + "name": "idx_cloud_agent_code_reviews_cli_session_id", + "columns": [ + { + "expression": "cli_session_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_cloud_agent_code_reviews_status": { + "name": "idx_cloud_agent_code_reviews_status", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_cloud_agent_code_reviews_repo": { + "name": "idx_cloud_agent_code_reviews_repo", + "columns": [ + { + "expression": "repo_full_name", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_cloud_agent_code_reviews_pr_number": { + "name": "idx_cloud_agent_code_reviews_pr_number", + "columns": [ + { + "expression": "repo_full_name", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "pr_number", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_cloud_agent_code_reviews_created_at": { + "name": "idx_cloud_agent_code_reviews_created_at", + "columns": [ + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_cloud_agent_code_reviews_pr_author_github_id": { + "name": "idx_cloud_agent_code_reviews_pr_author_github_id", + "columns": [ + { + "expression": "pr_author_github_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "cloud_agent_code_reviews_owned_by_organization_id_organizations_id_fk": { + "name": "cloud_agent_code_reviews_owned_by_organization_id_organizations_id_fk", + "tableFrom": "cloud_agent_code_reviews", + "tableTo": "organizations", + "columnsFrom": [ + "owned_by_organization_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "cloud_agent_code_reviews_owned_by_user_id_kilocode_users_id_fk": { + "name": "cloud_agent_code_reviews_owned_by_user_id_kilocode_users_id_fk", + "tableFrom": "cloud_agent_code_reviews", + "tableTo": "kilocode_users", + "columnsFrom": [ + "owned_by_user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "cloud_agent_code_reviews_platform_integration_id_platform_integrations_id_fk": { + "name": "cloud_agent_code_reviews_platform_integration_id_platform_integrations_id_fk", + "tableFrom": "cloud_agent_code_reviews", + "tableTo": "platform_integrations", + "columnsFrom": [ + "platform_integration_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "cloud_agent_code_reviews_owner_check": { + "name": "cloud_agent_code_reviews_owner_check", + "value": "(\n (\"cloud_agent_code_reviews\".\"owned_by_user_id\" IS NOT NULL AND \"cloud_agent_code_reviews\".\"owned_by_organization_id\" IS NULL) OR\n (\"cloud_agent_code_reviews\".\"owned_by_user_id\" IS NULL AND \"cloud_agent_code_reviews\".\"owned_by_organization_id\" IS NOT NULL)\n )" + } + }, + "isRLSEnabled": false + }, + "public.cloud_agent_feedback": { + "name": "cloud_agent_feedback", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "pg_catalog.gen_random_uuid()" + }, + "kilo_user_id": { + "name": "kilo_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "cloud_agent_session_id": { + "name": "cloud_agent_session_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "session_type": { + "name": "session_type", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "organization_id": { + "name": "organization_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "model": { + "name": "model", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "repository": { + "name": "repository", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "is_streaming": { + "name": "is_streaming", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "message_count": { + "name": "message_count", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "feedback_text": { + "name": "feedback_text", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "recent_messages": { + "name": "recent_messages", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "IDX_cloud_agent_feedback_created_at": { + "name": "IDX_cloud_agent_feedback_created_at", + "columns": [ + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_cloud_agent_feedback_kilo_user_id": { + "name": "IDX_cloud_agent_feedback_kilo_user_id", + "columns": [ + { + "expression": "kilo_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_cloud_agent_feedback_cloud_agent_session_id": { + "name": "IDX_cloud_agent_feedback_cloud_agent_session_id", + "columns": [ + { + "expression": "cloud_agent_session_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "cloud_agent_feedback_kilo_user_id_kilocode_users_id_fk": { + "name": "cloud_agent_feedback_kilo_user_id_kilocode_users_id_fk", + "tableFrom": "cloud_agent_feedback", + "tableTo": "kilocode_users", + "columnsFrom": [ + "kilo_user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "cascade" + }, + "cloud_agent_feedback_organization_id_organizations_id_fk": { + "name": "cloud_agent_feedback_organization_id_organizations_id_fk", + "tableFrom": "cloud_agent_feedback", + "tableTo": "organizations", + "columnsFrom": [ + "organization_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.cloud_agent_pending_uploads": { + "name": "cloud_agent_pending_uploads", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "kilo_user_id": { + "name": "kilo_user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "object_key": { + "name": "object_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "message_uuid": { + "name": "message_uuid", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "attachment_id": { + "name": "attachment_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "byte_size": { + "name": "byte_size", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "IDX_cloud_agent_pending_uploads_user_message_status": { + "name": "IDX_cloud_agent_pending_uploads_user_message_status", + "columns": [ + { + "expression": "kilo_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "message_uuid", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_cloud_agent_pending_uploads_expired": { + "name": "IDX_cloud_agent_pending_uploads_expired", + "columns": [ + { + "expression": "expires_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"cloud_agent_pending_uploads\".\"status\" = 'pending'", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "cloud_agent_pending_uploads_object_key_unique": { + "name": "cloud_agent_pending_uploads_object_key_unique", + "nullsNotDistinct": false, + "columns": [ + "object_key" + ] + } + }, + "policies": {}, + "checkConstraints": { + "cloud_agent_pending_uploads_status_check": { + "name": "cloud_agent_pending_uploads_status_check", + "value": "\"cloud_agent_pending_uploads\".\"status\" IN ('pending', 'linked', 'reaped')" + } + }, + "isRLSEnabled": false + }, + "public.cloud_agent_session_runs": { + "name": "cloud_agent_session_runs", + "schema": "", + "columns": { + "cloud_agent_session_id": { + "name": "cloud_agent_session_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "message_id": { + "name": "message_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "wrapper_run_id": { + "name": "wrapper_run_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "queued_at": { + "name": "queued_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "dispatch_accepted_at": { + "name": "dispatch_accepted_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "agent_activity_observed_at": { + "name": "agent_activity_observed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "terminal_at": { + "name": "terminal_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "failure_stage": { + "name": "failure_stage", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "failure_code": { + "name": "failure_code", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "failure_responsibility": { + "name": "failure_responsibility", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "failure_reason": { + "name": "failure_reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "error_message_redacted": { + "name": "error_message_redacted", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "error_expires_at": { + "name": "error_expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "IDX_cloud_agent_session_runs_wrapper_run_id": { + "name": "IDX_cloud_agent_session_runs_wrapper_run_id", + "columns": [ + { + "expression": "wrapper_run_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"cloud_agent_session_runs\".\"wrapper_run_id\" is not null", + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_cloud_agent_session_runs_session_queued": { + "name": "IDX_cloud_agent_session_runs_session_queued", + "columns": [ + { + "expression": "cloud_agent_session_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "queued_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_cloud_agent_session_runs_queued_at": { + "name": "IDX_cloud_agent_session_runs_queued_at", + "columns": [ + { + "expression": "queued_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_cloud_agent_session_runs_terminal_at": { + "name": "IDX_cloud_agent_session_runs_terminal_at", + "columns": [ + { + "expression": "terminal_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_cloud_agent_session_runs_status_terminal": { + "name": "IDX_cloud_agent_session_runs_status_terminal", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "terminal_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_cloud_agent_session_runs_failure_terminal": { + "name": "IDX_cloud_agent_session_runs_failure_terminal", + "columns": [ + { + "expression": "failure_stage", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "failure_code", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "terminal_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_cloud_agent_session_runs_responsibility_reason_terminal": { + "name": "IDX_cloud_agent_session_runs_responsibility_reason_terminal", + "columns": [ + { + "expression": "failure_responsibility", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "failure_reason", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "terminal_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"cloud_agent_session_runs\".\"status\" = 'failed'", + "concurrently": true, + "method": "btree", + "with": {} + }, + "IDX_cloud_agent_session_runs_error_expires_at": { + "name": "IDX_cloud_agent_session_runs_error_expires_at", + "columns": [ + { + "expression": "error_expires_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"cloud_agent_session_runs\".\"error_expires_at\" is not null", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "cloud_agent_session_runs_cloud_agent_session_id_cloud_agent_sessions_cloud_agent_session_id_fk": { + "name": "cloud_agent_session_runs_cloud_agent_session_id_cloud_agent_sessions_cloud_agent_session_id_fk", + "tableFrom": "cloud_agent_session_runs", + "tableTo": "cloud_agent_sessions", + "columnsFrom": [ + "cloud_agent_session_id" + ], + "columnsTo": [ + "cloud_agent_session_id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "cloud_agent_session_runs_cloud_agent_session_id_message_id_pk": { + "name": "cloud_agent_session_runs_cloud_agent_session_id_message_id_pk", + "columns": [ + "cloud_agent_session_id", + "message_id" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "cloud_agent_session_runs_status_check": { + "name": "cloud_agent_session_runs_status_check", + "value": "\"cloud_agent_session_runs\".\"status\" IN ('queued', 'accepted', 'completed', 'failed', 'interrupted')" + }, + "cloud_agent_session_runs_error_message_bounded_check": { + "name": "cloud_agent_session_runs_error_message_bounded_check", + "value": "\"cloud_agent_session_runs\".\"error_message_redacted\" IS NULL OR char_length(\"cloud_agent_session_runs\".\"error_message_redacted\") <= 4096" + }, + "cloud_agent_session_runs_error_expiry_check": { + "name": "cloud_agent_session_runs_error_expiry_check", + "value": "(\"cloud_agent_session_runs\".\"error_message_redacted\" IS NULL AND \"cloud_agent_session_runs\".\"error_expires_at\" IS NULL) OR\n (\"cloud_agent_session_runs\".\"error_message_redacted\" IS NOT NULL AND \"cloud_agent_session_runs\".\"error_expires_at\" IS NOT NULL)" + } + }, + "isRLSEnabled": false + }, + "public.cloud_agent_sessions": { + "name": "cloud_agent_sessions", + "schema": "", + "columns": { + "cloud_agent_session_id": { + "name": "cloud_agent_session_id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "kilo_session_id": { + "name": "kilo_session_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "initial_message_id": { + "name": "initial_message_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "sandbox_id": { + "name": "sandbox_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "failure_at": { + "name": "failure_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "failure_stage": { + "name": "failure_stage", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "failure_code": { + "name": "failure_code", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "failure_responsibility": { + "name": "failure_responsibility", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "failure_reason": { + "name": "failure_reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "error_message_redacted": { + "name": "error_message_redacted", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "error_expires_at": { + "name": "error_expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "UQ_cloud_agent_sessions_kilo_session_id": { + "name": "UQ_cloud_agent_sessions_kilo_session_id", + "columns": [ + { + "expression": "kilo_session_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "UQ_cloud_agent_sessions_initial_message_id": { + "name": "UQ_cloud_agent_sessions_initial_message_id", + "columns": [ + { + "expression": "initial_message_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_cloud_agent_sessions_sandbox_id": { + "name": "IDX_cloud_agent_sessions_sandbox_id", + "columns": [ + { + "expression": "sandbox_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"cloud_agent_sessions\".\"sandbox_id\" is not null", + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_cloud_agent_sessions_created_at": { + "name": "IDX_cloud_agent_sessions_created_at", + "columns": [ + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_cloud_agent_sessions_failure_created": { + "name": "IDX_cloud_agent_sessions_failure_created", + "columns": [ + { + "expression": "failure_stage", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "failure_code", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_cloud_agent_sessions_failure_at": { + "name": "IDX_cloud_agent_sessions_failure_at", + "columns": [ + { + "expression": "failure_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"cloud_agent_sessions\".\"failure_at\" is not null", + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_cloud_agent_sessions_failure_classification_at": { + "name": "IDX_cloud_agent_sessions_failure_classification_at", + "columns": [ + { + "expression": "failure_stage", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "failure_code", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "failure_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"cloud_agent_sessions\".\"failure_at\" is not null", + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_cloud_agent_sessions_error_expires_at": { + "name": "IDX_cloud_agent_sessions_error_expires_at", + "columns": [ + { + "expression": "error_expires_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"cloud_agent_sessions\".\"error_expires_at\" is not null", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "cloud_agent_sessions_failure_classification_check": { + "name": "cloud_agent_sessions_failure_classification_check", + "value": "(\"cloud_agent_sessions\".\"failure_at\" IS NULL AND \"cloud_agent_sessions\".\"failure_stage\" IS NULL AND \"cloud_agent_sessions\".\"failure_code\" IS NULL) OR\n (\"cloud_agent_sessions\".\"failure_at\" IS NOT NULL AND \"cloud_agent_sessions\".\"failure_stage\" = 'sandbox_identity' AND \"cloud_agent_sessions\".\"failure_code\" = 'sandbox_id_derivation_failed') OR\n (\"cloud_agent_sessions\".\"failure_at\" IS NOT NULL AND \"cloud_agent_sessions\".\"failure_stage\" = 'registration' AND \"cloud_agent_sessions\".\"failure_code\" = 'do_registration_rejected') OR\n (\"cloud_agent_sessions\".\"failure_at\" IS NOT NULL AND \"cloud_agent_sessions\".\"failure_stage\" = 'initial_admission' AND \"cloud_agent_sessions\".\"failure_code\" IN ('initial_admission_rejected', 'initial_queue_full', 'invalid_initial_intent')) OR\n (\"cloud_agent_sessions\".\"failure_at\" IS NOT NULL AND \"cloud_agent_sessions\".\"failure_stage\" = 'transport' AND \"cloud_agent_sessions\".\"failure_code\" = 'do_rpc_outcome_unknown')" + }, + "cloud_agent_sessions_error_message_bounded_check": { + "name": "cloud_agent_sessions_error_message_bounded_check", + "value": "\"cloud_agent_sessions\".\"error_message_redacted\" IS NULL OR char_length(\"cloud_agent_sessions\".\"error_message_redacted\") <= 4096" + }, + "cloud_agent_sessions_error_expiry_check": { + "name": "cloud_agent_sessions_error_expiry_check", + "value": "(\"cloud_agent_sessions\".\"error_message_redacted\" IS NULL AND \"cloud_agent_sessions\".\"error_expires_at\" IS NULL) OR\n (\"cloud_agent_sessions\".\"error_message_redacted\" IS NOT NULL AND \"cloud_agent_sessions\".\"error_expires_at\" IS NOT NULL)" + } + }, + "isRLSEnabled": false + }, + "public.cloud_agent_webhook_triggers": { + "name": "cloud_agent_webhook_triggers", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "trigger_id": { + "name": "trigger_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "organization_id": { + "name": "organization_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "target_type": { + "name": "target_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'cloud_agent'" + }, + "kiloclaw_instance_id": { + "name": "kiloclaw_instance_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "activation_mode": { + "name": "activation_mode", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'webhook'" + }, + "cron_expression": { + "name": "cron_expression", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "cron_timezone": { + "name": "cron_timezone", + "type": "text", + "primaryKey": false, + "notNull": false, + "default": "'UTC'" + }, + "github_repo": { + "name": "github_repo", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "is_active": { + "name": "is_active", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "profile_id": { + "name": "profile_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "UQ_cloud_agent_webhook_triggers_user_trigger": { + "name": "UQ_cloud_agent_webhook_triggers_user_trigger", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "trigger_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"cloud_agent_webhook_triggers\".\"user_id\" is not null", + "concurrently": false, + "method": "btree", + "with": {} + }, + "UQ_cloud_agent_webhook_triggers_org_trigger": { + "name": "UQ_cloud_agent_webhook_triggers_org_trigger", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "trigger_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"cloud_agent_webhook_triggers\".\"organization_id\" is not null", + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_cloud_agent_webhook_triggers_user": { + "name": "IDX_cloud_agent_webhook_triggers_user", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_cloud_agent_webhook_triggers_org": { + "name": "IDX_cloud_agent_webhook_triggers_org", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_cloud_agent_webhook_triggers_active": { + "name": "IDX_cloud_agent_webhook_triggers_active", + "columns": [ + { + "expression": "is_active", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_cloud_agent_webhook_triggers_profile": { + "name": "IDX_cloud_agent_webhook_triggers_profile", + "columns": [ + { + "expression": "profile_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "cloud_agent_webhook_triggers_user_id_kilocode_users_id_fk": { + "name": "cloud_agent_webhook_triggers_user_id_kilocode_users_id_fk", + "tableFrom": "cloud_agent_webhook_triggers", + "tableTo": "kilocode_users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "cloud_agent_webhook_triggers_organization_id_organizations_id_fk": { + "name": "cloud_agent_webhook_triggers_organization_id_organizations_id_fk", + "tableFrom": "cloud_agent_webhook_triggers", + "tableTo": "organizations", + "columnsFrom": [ + "organization_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "cloud_agent_webhook_triggers_kiloclaw_instance_id_kiloclaw_instances_id_fk": { + "name": "cloud_agent_webhook_triggers_kiloclaw_instance_id_kiloclaw_instances_id_fk", + "tableFrom": "cloud_agent_webhook_triggers", + "tableTo": "kiloclaw_instances", + "columnsFrom": [ + "kiloclaw_instance_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "cloud_agent_webhook_triggers_profile_id_agent_environment_profiles_id_fk": { + "name": "cloud_agent_webhook_triggers_profile_id_agent_environment_profiles_id_fk", + "tableFrom": "cloud_agent_webhook_triggers", + "tableTo": "agent_environment_profiles", + "columnsFrom": [ + "profile_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "restrict", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "CHK_cloud_agent_webhook_triggers_owner": { + "name": "CHK_cloud_agent_webhook_triggers_owner", + "value": "(\n (\"cloud_agent_webhook_triggers\".\"user_id\" IS NOT NULL AND \"cloud_agent_webhook_triggers\".\"organization_id\" IS NULL) OR\n (\"cloud_agent_webhook_triggers\".\"user_id\" IS NULL AND \"cloud_agent_webhook_triggers\".\"organization_id\" IS NOT NULL)\n )" + }, + "CHK_cloud_agent_webhook_triggers_cloud_agent_fields": { + "name": "CHK_cloud_agent_webhook_triggers_cloud_agent_fields", + "value": "(\n \"cloud_agent_webhook_triggers\".\"target_type\" != 'cloud_agent' OR\n (\"cloud_agent_webhook_triggers\".\"github_repo\" IS NOT NULL AND \"cloud_agent_webhook_triggers\".\"profile_id\" IS NOT NULL)\n )" + }, + "CHK_cloud_agent_webhook_triggers_kiloclaw_fields": { + "name": "CHK_cloud_agent_webhook_triggers_kiloclaw_fields", + "value": "(\n \"cloud_agent_webhook_triggers\".\"target_type\" != 'kiloclaw_chat' OR\n \"cloud_agent_webhook_triggers\".\"kiloclaw_instance_id\" IS NOT NULL\n )" + }, + "CHK_cloud_agent_webhook_triggers_scheduled_fields": { + "name": "CHK_cloud_agent_webhook_triggers_scheduled_fields", + "value": "(\n \"cloud_agent_webhook_triggers\".\"activation_mode\" != 'scheduled' OR\n \"cloud_agent_webhook_triggers\".\"cron_expression\" IS NOT NULL\n )" + } + }, + "isRLSEnabled": false + }, + "public.cloud_agent_workspace_folders": { + "name": "cloud_agent_workspace_folders", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "pg_catalog.gen_random_uuid()" + }, + "kilo_user_id": { + "name": "kilo_user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "organization_id": { + "name": "organization_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "color": { + "name": "color", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'default'" + }, + "position": { + "name": "position", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "IDX_cloud_agent_workspace_folders_owner_scope_order": { + "name": "IDX_cloud_agent_workspace_folders_owner_scope_order", + "columns": [ + { + "expression": "kilo_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "position", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "cloud_agent_workspace_folders_kilo_user_id_kilocode_users_id_fk": { + "name": "cloud_agent_workspace_folders_kilo_user_id_kilocode_users_id_fk", + "tableFrom": "cloud_agent_workspace_folders", + "tableTo": "kilocode_users", + "columnsFrom": [ + "kilo_user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "cloud_agent_workspace_folders_organization_id_organizations_id_fk": { + "name": "cloud_agent_workspace_folders_organization_id_organizations_id_fk", + "tableFrom": "cloud_agent_workspace_folders", + "tableTo": "organizations", + "columnsFrom": [ + "organization_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "cloud_agent_workspace_folders_color_check": { + "name": "cloud_agent_workspace_folders_color_check", + "value": "\"cloud_agent_workspace_folders\".\"color\" IN ('default', 'red', 'orange', 'yellow', 'green', 'teal', 'blue', 'purple')" + }, + "cloud_agent_workspace_folders_name_check": { + "name": "cloud_agent_workspace_folders_name_check", + "value": "char_length(btrim(\"cloud_agent_workspace_folders\".\"name\")) BETWEEN 1 AND 200 AND \"cloud_agent_workspace_folders\".\"name\" = btrim(\"cloud_agent_workspace_folders\".\"name\")" + }, + "cloud_agent_workspace_folders_position_check": { + "name": "cloud_agent_workspace_folders_position_check", + "value": "\"cloud_agent_workspace_folders\".\"position\" >= 0" + } + }, + "isRLSEnabled": false + }, + "public.cloud_agent_worktrees": { + "name": "cloud_agent_worktrees", + "schema": "", + "columns": { + "worktree_id": { + "name": "worktree_id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "kilo_user_id": { + "name": "kilo_user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "organization_id": { + "name": "organization_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "folder_id": { + "name": "folder_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "deletion_started_at": { + "name": "deletion_started_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "deletion_completed_at": { + "name": "deletion_completed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "runtime_locations": { + "name": "runtime_locations", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "deletion_manifest": { + "name": "deletion_manifest", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "deleted_session_ids": { + "name": "deleted_session_ids", + "type": "text[]", + "primaryKey": false, + "notNull": true, + "default": "'{}'::text[]" + } + }, + "indexes": { + "IDX_cloud_agent_worktrees_owner_scope": { + "name": "IDX_cloud_agent_worktrees_owner_scope", + "columns": [ + { + "expression": "kilo_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_cloud_agent_worktrees_folder_id": { + "name": "IDX_cloud_agent_worktrees_folder_id", + "columns": [ + { + "expression": "folder_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"cloud_agent_worktrees\".\"folder_id\" is not null", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "cloud_agent_worktrees_kilo_user_id_kilocode_users_id_fk": { + "name": "cloud_agent_worktrees_kilo_user_id_kilocode_users_id_fk", + "tableFrom": "cloud_agent_worktrees", + "tableTo": "kilocode_users", + "columnsFrom": [ + "kilo_user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "restrict", + "onUpdate": "no action" + }, + "cloud_agent_worktrees_organization_id_organizations_id_fk": { + "name": "cloud_agent_worktrees_organization_id_organizations_id_fk", + "tableFrom": "cloud_agent_worktrees", + "tableTo": "organizations", + "columnsFrom": [ + "organization_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "restrict", + "onUpdate": "no action" + }, + "cloud_agent_worktrees_folder_id_cloud_agent_workspace_folders_id_fk": { + "name": "cloud_agent_worktrees_folder_id_cloud_agent_workspace_folders_id_fk", + "tableFrom": "cloud_agent_worktrees", + "tableTo": "cloud_agent_workspace_folders", + "columnsFrom": [ + "folder_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "cloud_agent_worktrees_deletion_check": { + "name": "cloud_agent_worktrees_deletion_check", + "value": "\"cloud_agent_worktrees\".\"deletion_completed_at\" IS NULL OR (\"cloud_agent_worktrees\".\"deletion_started_at\" IS NOT NULL AND \"cloud_agent_worktrees\".\"name\" IS NULL AND \"cloud_agent_worktrees\".\"deletion_manifest\" IS NULL AND \"cloud_agent_worktrees\".\"runtime_locations\" = '[]'::jsonb)" + } + }, + "isRLSEnabled": false + }, + "public.cloud_billing_sku": { + "name": "cloud_billing_sku", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "unit": { + "name": "unit", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "rate_cents_per_unit": { + "name": "rate_cents_per_unit", + "type": "numeric(24, 12)", + "primaryKey": false, + "notNull": true + }, + "accepts_new_usage": { + "name": "accepts_new_usage", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "created_by_user_id": { + "name": "created_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "cloud_billing_sku_created_by_user_id_kilocode_users_id_fk": { + "name": "cloud_billing_sku_created_by_user_id_kilocode_users_id_fk", + "tableFrom": "cloud_billing_sku", + "tableTo": "kilocode_users", + "columnsFrom": [ + "created_by_user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "cloud_billing_sku_id_format": { + "name": "cloud_billing_sku_id_format", + "value": "\"cloud_billing_sku\".\"id\" ~ '^[a-z0-9][a-z0-9-]{2,79}$'" + }, + "cloud_billing_sku_name_nonempty": { + "name": "cloud_billing_sku_name_nonempty", + "value": "length(btrim(\"cloud_billing_sku\".\"name\")) > 0" + }, + "cloud_billing_sku_rate_positive": { + "name": "cloud_billing_sku_rate_positive", + "value": "\"cloud_billing_sku\".\"rate_cents_per_unit\" > 0" + } + }, + "isRLSEnabled": false + }, + "public.code_indexing_manifest": { + "name": "code_indexing_manifest", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "organization_id": { + "name": "organization_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "kilo_user_id": { + "name": "kilo_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "project_id": { + "name": "project_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "git_branch": { + "name": "git_branch", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "file_hash": { + "name": "file_hash", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "file_path": { + "name": "file_path", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "chunk_count": { + "name": "chunk_count", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "total_lines": { + "name": "total_lines", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "total_ai_lines": { + "name": "total_ai_lines", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "IDX_code_indexing_manifest_organization_id": { + "name": "IDX_code_indexing_manifest_organization_id", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_code_indexing_manifest_kilo_user_id": { + "name": "IDX_code_indexing_manifest_kilo_user_id", + "columns": [ + { + "expression": "kilo_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_code_indexing_manifest_project_id": { + "name": "IDX_code_indexing_manifest_project_id", + "columns": [ + { + "expression": "project_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_code_indexing_manifest_git_branch": { + "name": "IDX_code_indexing_manifest_git_branch", + "columns": [ + { + "expression": "git_branch", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_code_indexing_manifest_created_at": { + "name": "IDX_code_indexing_manifest_created_at", + "columns": [ + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "code_indexing_manifest_kilo_user_id_kilocode_users_id_fk": { + "name": "code_indexing_manifest_kilo_user_id_kilocode_users_id_fk", + "tableFrom": "code_indexing_manifest", + "tableTo": "kilocode_users", + "columnsFrom": [ + "kilo_user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "UQ_code_indexing_manifest_org_user_project_hash_branch": { + "name": "UQ_code_indexing_manifest_org_user_project_hash_branch", + "nullsNotDistinct": true, + "columns": [ + "organization_id", + "kilo_user_id", + "project_id", + "file_path", + "git_branch" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.code_indexing_search": { + "name": "code_indexing_search", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "organization_id": { + "name": "organization_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "kilo_user_id": { + "name": "kilo_user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "query": { + "name": "query", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "project_id": { + "name": "project_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "IDX_code_indexing_search_organization_id": { + "name": "IDX_code_indexing_search_organization_id", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_code_indexing_search_kilo_user_id": { + "name": "IDX_code_indexing_search_kilo_user_id", + "columns": [ + { + "expression": "kilo_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_code_indexing_search_project_id": { + "name": "IDX_code_indexing_search_project_id", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "project_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_code_indexing_search_created_at": { + "name": "IDX_code_indexing_search_created_at", + "columns": [ + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "code_indexing_search_kilo_user_id_kilocode_users_id_fk": { + "name": "code_indexing_search_kilo_user_id_kilocode_users_id_fk", + "tableFrom": "code_indexing_search", + "tableTo": "kilocode_users", + "columnsFrom": [ + "kilo_user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.code_review_analytics_findings": { + "name": "code_review_analytics_findings", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "pg_catalog.gen_random_uuid()" + }, + "analytics_result_id": { + "name": "analytics_result_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "ordinal": { + "name": "ordinal", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "severity": { + "name": "severity", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "category": { + "name": "category", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "security_class": { + "name": "security_class", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "code_review_analytics_findings_analytics_result_id_code_review_analytics_results_id_fk": { + "name": "code_review_analytics_findings_analytics_result_id_code_review_analytics_results_id_fk", + "tableFrom": "code_review_analytics_findings", + "tableTo": "code_review_analytics_results", + "columnsFrom": [ + "analytics_result_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "UQ_code_review_analytics_findings_result_ordinal": { + "name": "UQ_code_review_analytics_findings_result_ordinal", + "nullsNotDistinct": false, + "columns": [ + "analytics_result_id", + "ordinal" + ] + } + }, + "policies": {}, + "checkConstraints": { + "code_review_analytics_findings_severity_check": { + "name": "code_review_analytics_findings_severity_check", + "value": "\"code_review_analytics_findings\".\"severity\" IN ('critical', 'warning', 'suggestion')" + }, + "code_review_analytics_findings_category_check": { + "name": "code_review_analytics_findings_category_check", + "value": "\"code_review_analytics_findings\".\"category\" IN ('security', 'correctness', 'reliability', 'data_integrity', 'performance', 'compatibility', 'maintainability', 'test_quality', 'documentation', 'accessibility', 'other')" + }, + "code_review_analytics_findings_security_class_check": { + "name": "code_review_analytics_findings_security_class_check", + "value": "\"code_review_analytics_findings\".\"security_class\" IN ('auth_access', 'injection', 'data_protection', 'request_resource_boundary', 'deserialization_object_integrity', 'dependency_supply_chain', 'memory_safety', 'availability', 'concurrency', 'security_configuration', 'other')" + }, + "code_review_analytics_findings_ordinal_check": { + "name": "code_review_analytics_findings_ordinal_check", + "value": "\"code_review_analytics_findings\".\"ordinal\" >= 0" + }, + "code_review_analytics_findings_security_class_presence_check": { + "name": "code_review_analytics_findings_security_class_presence_check", + "value": "(\n (\"code_review_analytics_findings\".\"category\" = 'security' AND \"code_review_analytics_findings\".\"security_class\" IS NOT NULL) OR\n (\"code_review_analytics_findings\".\"category\" <> 'security' AND \"code_review_analytics_findings\".\"security_class\" IS NULL)\n )" + } + }, + "isRLSEnabled": false + }, + "public.code_review_analytics_results": { + "name": "code_review_analytics_results", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "pg_catalog.gen_random_uuid()" + }, + "code_review_id": { + "name": "code_review_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "source_attempt_id": { + "name": "source_attempt_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "capture_status": { + "name": "capture_status", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "schema_version": { + "name": "schema_version", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 1 + }, + "taxonomy_version": { + "name": "taxonomy_version", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 1 + }, + "change_type": { + "name": "change_type", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "impact_level": { + "name": "impact_level", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "complexity_level": { + "name": "complexity_level", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "classification_confidence": { + "name": "classification_confidence", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "finalized_at": { + "name": "finalized_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "idx_code_review_analytics_results_source_attempt_id": { + "name": "idx_code_review_analytics_results_source_attempt_id", + "columns": [ + { + "expression": "source_attempt_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_code_review_analytics_results_finalized_at": { + "name": "idx_code_review_analytics_results_finalized_at", + "columns": [ + { + "expression": "finalized_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "code_review_analytics_results_code_review_id_cloud_agent_code_reviews_id_fk": { + "name": "code_review_analytics_results_code_review_id_cloud_agent_code_reviews_id_fk", + "tableFrom": "code_review_analytics_results", + "tableTo": "cloud_agent_code_reviews", + "columnsFrom": [ + "code_review_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "code_review_analytics_results_source_attempt_id_cloud_agent_code_review_attempts_id_fk": { + "name": "code_review_analytics_results_source_attempt_id_cloud_agent_code_review_attempts_id_fk", + "tableFrom": "code_review_analytics_results", + "tableTo": "cloud_agent_code_review_attempts", + "columnsFrom": [ + "source_attempt_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "UQ_code_review_analytics_results_code_review_id": { + "name": "UQ_code_review_analytics_results_code_review_id", + "nullsNotDistinct": false, + "columns": [ + "code_review_id" + ] + } + }, + "policies": {}, + "checkConstraints": { + "code_review_analytics_results_capture_status_check": { + "name": "code_review_analytics_results_capture_status_check", + "value": "\"code_review_analytics_results\".\"capture_status\" IN ('captured', 'missing', 'invalid', 'omitted')" + }, + "code_review_analytics_results_change_type_check": { + "name": "code_review_analytics_results_change_type_check", + "value": "\"code_review_analytics_results\".\"change_type\" IN ('bug_fix', 'feature', 'refactor', 'maintenance', 'dependency', 'test', 'documentation', 'mixed', 'other')" + }, + "code_review_analytics_results_impact_level_check": { + "name": "code_review_analytics_results_impact_level_check", + "value": "\"code_review_analytics_results\".\"impact_level\" IN ('low', 'medium', 'high')" + }, + "code_review_analytics_results_complexity_level_check": { + "name": "code_review_analytics_results_complexity_level_check", + "value": "\"code_review_analytics_results\".\"complexity_level\" IN ('low', 'medium', 'high')" + }, + "code_review_analytics_results_classification_confidence_check": { + "name": "code_review_analytics_results_classification_confidence_check", + "value": "\"code_review_analytics_results\".\"classification_confidence\" IN ('low', 'medium', 'high')" + }, + "code_review_analytics_results_classification_presence_check": { + "name": "code_review_analytics_results_classification_presence_check", + "value": "(\n (\n \"code_review_analytics_results\".\"capture_status\" = 'captured'\n AND \"code_review_analytics_results\".\"change_type\" IS NOT NULL\n AND \"code_review_analytics_results\".\"impact_level\" IS NOT NULL\n AND \"code_review_analytics_results\".\"complexity_level\" IS NOT NULL\n AND \"code_review_analytics_results\".\"classification_confidence\" IS NOT NULL\n ) OR (\n \"code_review_analytics_results\".\"capture_status\" <> 'captured'\n AND \"code_review_analytics_results\".\"change_type\" IS NULL\n AND \"code_review_analytics_results\".\"impact_level\" IS NULL\n AND \"code_review_analytics_results\".\"complexity_level\" IS NULL\n AND \"code_review_analytics_results\".\"classification_confidence\" IS NULL\n )\n )" + } + }, + "isRLSEnabled": false + }, + "public.code_review_feedback_events": { + "name": "code_review_feedback_events", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "pg_catalog.gen_random_uuid()" + }, + "owned_by_organization_id": { + "name": "owned_by_organization_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "owned_by_user_id": { + "name": "owned_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "platform": { + "name": "platform", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "repo_full_name": { + "name": "repo_full_name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "pr_number": { + "name": "pr_number", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "kilo_comment_id": { + "name": "kilo_comment_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "reply_excerpt": { + "name": "reply_excerpt", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "kilo_comment_excerpt": { + "name": "kilo_comment_excerpt", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "dedupe_hash": { + "name": "dedupe_hash", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "occurred_at": { + "name": "occurred_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "idx_code_review_feedback_events_owned_by_org_id": { + "name": "idx_code_review_feedback_events_owned_by_org_id", + "columns": [ + { + "expression": "owned_by_organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_code_review_feedback_events_owned_by_user_id": { + "name": "idx_code_review_feedback_events_owned_by_user_id", + "columns": [ + { + "expression": "owned_by_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_code_review_feedback_events_platform_repo": { + "name": "idx_code_review_feedback_events_platform_repo", + "columns": [ + { + "expression": "platform", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "repo_full_name", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_code_review_feedback_events_created_at": { + "name": "idx_code_review_feedback_events_created_at", + "columns": [ + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "code_review_feedback_events_owned_by_organization_id_organizations_id_fk": { + "name": "code_review_feedback_events_owned_by_organization_id_organizations_id_fk", + "tableFrom": "code_review_feedback_events", + "tableTo": "organizations", + "columnsFrom": [ + "owned_by_organization_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "code_review_feedback_events_owned_by_user_id_kilocode_users_id_fk": { + "name": "code_review_feedback_events_owned_by_user_id_kilocode_users_id_fk", + "tableFrom": "code_review_feedback_events", + "tableTo": "kilocode_users", + "columnsFrom": [ + "owned_by_user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "UQ_code_review_feedback_events_dedupe_hash": { + "name": "UQ_code_review_feedback_events_dedupe_hash", + "nullsNotDistinct": false, + "columns": [ + "dedupe_hash" + ] + } + }, + "policies": {}, + "checkConstraints": { + "code_review_feedback_events_owner_check": { + "name": "code_review_feedback_events_owner_check", + "value": "(\n (\"code_review_feedback_events\".\"owned_by_user_id\" IS NOT NULL AND \"code_review_feedback_events\".\"owned_by_organization_id\" IS NULL) OR\n (\"code_review_feedback_events\".\"owned_by_user_id\" IS NULL AND \"code_review_feedback_events\".\"owned_by_organization_id\" IS NOT NULL)\n )" + } + }, + "isRLSEnabled": false + }, + "public.code_review_memory_proposals": { + "name": "code_review_memory_proposals", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "pg_catalog.gen_random_uuid()" + }, + "owned_by_organization_id": { + "name": "owned_by_organization_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "owned_by_user_id": { + "name": "owned_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "platform": { + "name": "platform", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "repo_full_name": { + "name": "repo_full_name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'open'" + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "rationale": { + "name": "rationale", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "proposed_markdown": { + "name": "proposed_markdown", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "evidence": { + "name": "evidence", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "positive_count": { + "name": "positive_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "negative_count": { + "name": "negative_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "neutral_count": { + "name": "neutral_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "change_request_url": { + "name": "change_request_url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "idx_code_review_memory_proposals_owned_by_org_id": { + "name": "idx_code_review_memory_proposals_owned_by_org_id", + "columns": [ + { + "expression": "owned_by_organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_code_review_memory_proposals_owned_by_user_id": { + "name": "idx_code_review_memory_proposals_owned_by_user_id", + "columns": [ + { + "expression": "owned_by_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_code_review_memory_proposals_platform_repo_status": { + "name": "idx_code_review_memory_proposals_platform_repo_status", + "columns": [ + { + "expression": "platform", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "repo_full_name", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_code_review_memory_proposals_updated_at": { + "name": "idx_code_review_memory_proposals_updated_at", + "columns": [ + { + "expression": "updated_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "UQ_code_review_memory_proposals_org_active_scope": { + "name": "UQ_code_review_memory_proposals_org_active_scope", + "columns": [ + { + "expression": "owned_by_organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "platform", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "repo_full_name", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"code_review_memory_proposals\".\"owned_by_organization_id\" IS NOT NULL AND \"code_review_memory_proposals\".\"status\" IN ('open', 'edited', 'opening_change_request')", + "concurrently": false, + "method": "btree", + "with": {} + }, + "UQ_code_review_memory_proposals_user_active_scope": { + "name": "UQ_code_review_memory_proposals_user_active_scope", + "columns": [ + { + "expression": "owned_by_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "platform", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "repo_full_name", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"code_review_memory_proposals\".\"owned_by_user_id\" IS NOT NULL AND \"code_review_memory_proposals\".\"status\" IN ('open', 'edited', 'opening_change_request')", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "code_review_memory_proposals_owned_by_organization_id_organizations_id_fk": { + "name": "code_review_memory_proposals_owned_by_organization_id_organizations_id_fk", + "tableFrom": "code_review_memory_proposals", + "tableTo": "organizations", + "columnsFrom": [ + "owned_by_organization_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "code_review_memory_proposals_owned_by_user_id_kilocode_users_id_fk": { + "name": "code_review_memory_proposals_owned_by_user_id_kilocode_users_id_fk", + "tableFrom": "code_review_memory_proposals", + "tableTo": "kilocode_users", + "columnsFrom": [ + "owned_by_user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "code_review_memory_proposals_owner_check": { + "name": "code_review_memory_proposals_owner_check", + "value": "(\n (\"code_review_memory_proposals\".\"owned_by_user_id\" IS NOT NULL AND \"code_review_memory_proposals\".\"owned_by_organization_id\" IS NULL) OR\n (\"code_review_memory_proposals\".\"owned_by_user_id\" IS NULL AND \"code_review_memory_proposals\".\"owned_by_organization_id\" IS NOT NULL)\n )" + } + }, + "isRLSEnabled": false + }, + "public.coding_plan_availability_intents": { + "name": "coding_plan_availability_intents", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "plan_id": { + "name": "plan_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "UQ_coding_plan_availability_intents_user_plan": { + "name": "UQ_coding_plan_availability_intents_user_plan", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "plan_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_coding_plan_availability_intents_plan": { + "name": "IDX_coding_plan_availability_intents_plan", + "columns": [ + { + "expression": "plan_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "coding_plan_availability_intents_user_id_kilocode_users_id_fk": { + "name": "coding_plan_availability_intents_user_id_kilocode_users_id_fk", + "tableFrom": "coding_plan_availability_intents", + "tableTo": "kilocode_users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.coding_plan_key_inventory": { + "name": "coding_plan_key_inventory", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "plan_id": { + "name": "plan_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "provider_id": { + "name": "provider_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "upstream_plan_id": { + "name": "upstream_plan_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "upstream_usage_id": { + "name": "upstream_usage_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "encrypted_api_key": { + "name": "encrypted_api_key", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "credential_fingerprint": { + "name": "credential_fingerprint", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'available'" + }, + "assigned_to_user_id": { + "name": "assigned_to_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "assigned_at": { + "name": "assigned_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "revocation_requested_at": { + "name": "revocation_requested_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "revoked_at": { + "name": "revoked_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "revocation_attempt_count": { + "name": "revocation_attempt_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "last_revocation_error": { + "name": "last_revocation_error", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "UQ_coding_plan_key_inv_fingerprint": { + "name": "UQ_coding_plan_key_inv_fingerprint", + "columns": [ + { + "expression": "credential_fingerprint", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "UQ_coding_plan_key_inv_provider_usage_id": { + "name": "UQ_coding_plan_key_inv_provider_usage_id", + "columns": [ + { + "expression": "provider_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "upstream_usage_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"coding_plan_key_inventory\".\"upstream_usage_id\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_coding_plan_key_inv_plan_status": { + "name": "IDX_coding_plan_key_inv_plan_status", + "columns": [ + { + "expression": "plan_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_coding_plan_key_inv_available": { + "name": "IDX_coding_plan_key_inv_available", + "columns": [ + { + "expression": "plan_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"coding_plan_key_inventory\".\"status\" = 'available'", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "coding_plan_key_inventory_assigned_to_user_id_kilocode_users_id_fk": { + "name": "coding_plan_key_inventory_assigned_to_user_id_kilocode_users_id_fk", + "tableFrom": "coding_plan_key_inventory", + "tableTo": "kilocode_users", + "columnsFrom": [ + "assigned_to_user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "coding_plan_key_inventory_status_check": { + "name": "coding_plan_key_inventory_status_check", + "value": "\"coding_plan_key_inventory\".\"status\" IN ('available', 'assigned', 'revocation_pending', 'revoked', 'revocation_failed')" + } + }, + "isRLSEnabled": false + }, + "public.coding_plan_subscriptions": { + "name": "coding_plan_subscriptions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "plan_id": { + "name": "plan_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "provider_id": { + "name": "provider_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "key_inventory_id": { + "name": "key_inventory_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "installed_byok_key_id": { + "name": "installed_byok_key_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "cost_microdollars": { + "name": "cost_microdollars", + "type": "bigint", + "primaryKey": false, + "notNull": true + }, + "billing_period_days": { + "name": "billing_period_days", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "current_period_start": { + "name": "current_period_start", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "current_period_end": { + "name": "current_period_end", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "credit_renewal_at": { + "name": "credit_renewal_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "cancel_at_period_end": { + "name": "cancel_at_period_end", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "past_due_started_at": { + "name": "past_due_started_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "payment_grace_expires_at": { + "name": "payment_grace_expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "auto_top_up_attempted_for_due": { + "name": "auto_top_up_attempted_for_due", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "canceled_at": { + "name": "canceled_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "cancellation_reason": { + "name": "cancellation_reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "UQ_coding_plan_sub_live_user_plan": { + "name": "UQ_coding_plan_sub_live_user_plan", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "plan_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"coding_plan_subscriptions\".\"status\" IN ('active', 'past_due')", + "concurrently": false, + "method": "btree", + "with": {} + }, + "UQ_coding_plan_sub_live_user_provider": { + "name": "UQ_coding_plan_sub_live_user_provider", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "provider_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"coding_plan_subscriptions\".\"status\" IN ('active', 'past_due')", + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_coding_plan_sub_status": { + "name": "IDX_coding_plan_sub_status", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_coding_plan_sub_renewal": { + "name": "IDX_coding_plan_sub_renewal", + "columns": [ + { + "expression": "credit_renewal_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_coding_plan_sub_inventory": { + "name": "IDX_coding_plan_sub_inventory", + "columns": [ + { + "expression": "key_inventory_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "coding_plan_subscriptions_user_id_kilocode_users_id_fk": { + "name": "coding_plan_subscriptions_user_id_kilocode_users_id_fk", + "tableFrom": "coding_plan_subscriptions", + "tableTo": "kilocode_users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "coding_plan_subscriptions_key_inventory_id_coding_plan_key_inventory_id_fk": { + "name": "coding_plan_subscriptions_key_inventory_id_coding_plan_key_inventory_id_fk", + "tableFrom": "coding_plan_subscriptions", + "tableTo": "coding_plan_key_inventory", + "columnsFrom": [ + "key_inventory_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "coding_plan_subscriptions_installed_byok_key_id_byok_api_keys_id_fk": { + "name": "coding_plan_subscriptions_installed_byok_key_id_byok_api_keys_id_fk", + "tableFrom": "coding_plan_subscriptions", + "tableTo": "byok_api_keys", + "columnsFrom": [ + "installed_byok_key_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "coding_plan_subscriptions_status_check": { + "name": "coding_plan_subscriptions_status_check", + "value": "\"coding_plan_subscriptions\".\"status\" IN ('active', 'past_due', 'canceled')" + }, + "coding_plan_subscriptions_live_access_check": { + "name": "coding_plan_subscriptions_live_access_check", + "value": "\"coding_plan_subscriptions\".\"status\" = 'canceled' OR \"coding_plan_subscriptions\".\"key_inventory_id\" IS NOT NULL" + } + }, + "isRLSEnabled": false + }, + "public.coding_plan_terms": { + "name": "coding_plan_terms", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "subscription_id": { + "name": "subscription_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "plan_id": { + "name": "plan_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "kind": { + "name": "kind", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "idempotency_key": { + "name": "idempotency_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "period_start": { + "name": "period_start", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "period_end": { + "name": "period_end", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "cost_microdollars": { + "name": "cost_microdollars", + "type": "bigint", + "primaryKey": false, + "notNull": true + }, + "credit_transaction_id": { + "name": "credit_transaction_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "UQ_coding_plan_terms_request": { + "name": "UQ_coding_plan_terms_request", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "plan_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "idempotency_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_coding_plan_terms_subscription": { + "name": "IDX_coding_plan_terms_subscription", + "columns": [ + { + "expression": "subscription_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "coding_plan_terms_subscription_id_coding_plan_subscriptions_id_fk": { + "name": "coding_plan_terms_subscription_id_coding_plan_subscriptions_id_fk", + "tableFrom": "coding_plan_terms", + "tableTo": "coding_plan_subscriptions", + "columnsFrom": [ + "subscription_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "coding_plan_terms_user_id_kilocode_users_id_fk": { + "name": "coding_plan_terms_user_id_kilocode_users_id_fk", + "tableFrom": "coding_plan_terms", + "tableTo": "kilocode_users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "coding_plan_terms_credit_transaction_id_credit_transactions_id_fk": { + "name": "coding_plan_terms_credit_transaction_id_credit_transactions_id_fk", + "tableFrom": "coding_plan_terms", + "tableTo": "credit_transactions", + "columnsFrom": [ + "credit_transaction_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "coding_plan_terms_kind_check": { + "name": "coding_plan_terms_kind_check", + "value": "\"coding_plan_terms\".\"kind\" IN ('activation', 'extension', 'renewal')" + } + }, + "isRLSEnabled": false + }, + "public.compute_usage_charge": { + "name": "compute_usage_charge", + "schema": "", + "columns": { + "usage_source": { + "name": "usage_source", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "usage_source_id": { + "name": "usage_source_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "organization_id": { + "name": "organization_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "cloud_billing_sku_id": { + "name": "cloud_billing_sku_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "quantity": { + "name": "quantity", + "type": "numeric(24, 12)", + "primaryKey": false, + "notNull": true + }, + "settled_quantity_after": { + "name": "settled_quantity_after", + "type": "numeric(24, 12)", + "primaryKey": false, + "notNull": false + }, + "rate_cents_per_unit": { + "name": "rate_cents_per_unit", + "type": "numeric(24, 12)", + "primaryKey": false, + "notNull": true + }, + "amount_microdollars": { + "name": "amount_microdollars", + "type": "bigint", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "IDX_compute_usage_charge_user_created": { + "name": "IDX_compute_usage_charge_user_created", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_compute_usage_charge_organization_created": { + "name": "IDX_compute_usage_charge_organization_created", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "compute_usage_charge_user_id_kilocode_users_id_fk": { + "name": "compute_usage_charge_user_id_kilocode_users_id_fk", + "tableFrom": "compute_usage_charge", + "tableTo": "kilocode_users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "restrict", + "onUpdate": "no action" + }, + "compute_usage_charge_organization_id_organizations_id_fk": { + "name": "compute_usage_charge_organization_id_organizations_id_fk", + "tableFrom": "compute_usage_charge", + "tableTo": "organizations", + "columnsFrom": [ + "organization_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "restrict", + "onUpdate": "no action" + }, + "compute_usage_charge_cloud_billing_sku_id_cloud_billing_sku_id_fk": { + "name": "compute_usage_charge_cloud_billing_sku_id_cloud_billing_sku_id_fk", + "tableFrom": "compute_usage_charge", + "tableTo": "cloud_billing_sku", + "columnsFrom": [ + "cloud_billing_sku_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "restrict", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "compute_usage_charge_usage_source_usage_source_id_created_at_pk": { + "name": "compute_usage_charge_usage_source_usage_source_id_created_at_pk", + "columns": [ + "usage_source", + "usage_source_id", + "created_at" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "compute_usage_charge_exactly_one_payer": { + "name": "compute_usage_charge_exactly_one_payer", + "value": "(\"compute_usage_charge\".\"user_id\" IS NULL) <> (\"compute_usage_charge\".\"organization_id\" IS NULL)" + }, + "compute_usage_charge_quantity_positive": { + "name": "compute_usage_charge_quantity_positive", + "value": "\"compute_usage_charge\".\"quantity\" > 0" + }, + "compute_usage_charge_settled_quantity_positive": { + "name": "compute_usage_charge_settled_quantity_positive", + "value": "\"compute_usage_charge\".\"settled_quantity_after\" IS NULL OR \"compute_usage_charge\".\"settled_quantity_after\" > 0" + }, + "compute_usage_charge_rate_positive": { + "name": "compute_usage_charge_rate_positive", + "value": "\"compute_usage_charge\".\"rate_cents_per_unit\" > 0" + }, + "compute_usage_charge_amount_positive": { + "name": "compute_usage_charge_amount_positive", + "value": "\"compute_usage_charge\".\"amount_microdollars\" > 0" + } + }, + "isRLSEnabled": false + }, + "public.container_usage_interval": { + "name": "container_usage_interval", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "service": { + "name": "service", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "instance_id": { + "name": "instance_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "start_epoch_ms": { + "name": "start_epoch_ms", + "type": "bigint", + "primaryKey": false, + "notNull": true + }, + "cloud_billing_sku_id": { + "name": "cloud_billing_sku_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "context_fingerprint": { + "name": "context_fingerprint", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "subject_type": { + "name": "subject_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "subject_id": { + "name": "subject_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "actor_type": { + "name": "actor_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "actor_id": { + "name": "actor_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "session_id": { + "name": "session_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "started_at": { + "name": "started_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "last_seen_at": { + "name": "last_seen_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "last_heartbeat_seq": { + "name": "last_heartbeat_seq", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "confirmed_seconds": { + "name": "confirmed_seconds", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "billing_mode": { + "name": "billing_mode", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'shadow'" + }, + "rate_cents_per_unit": { + "name": "rate_cents_per_unit", + "type": "numeric(24, 12)", + "primaryKey": false, + "notNull": false + }, + "settled_billable_seconds": { + "name": "settled_billable_seconds", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "stopped_at": { + "name": "stopped_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "close_reason": { + "name": "close_reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "exit_code": { + "name": "exit_code", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "final_stop_seq": { + "name": "final_stop_seq", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'open'" + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "IDX_container_usage_interval_sweep": { + "name": "IDX_container_usage_interval_sweep", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "last_seen_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_container_usage_interval_subject_started": { + "name": "IDX_container_usage_interval_subject_started", + "columns": [ + { + "expression": "subject_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "subject_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "started_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "UQ_container_usage_interval_single_open": { + "name": "UQ_container_usage_interval_single_open", + "columns": [ + { + "expression": "service", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "instance_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"container_usage_interval\".\"status\" = 'open'", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "container_usage_interval_cloud_billing_sku_id_cloud_billing_sku_id_fk": { + "name": "container_usage_interval_cloud_billing_sku_id_cloud_billing_sku_id_fk", + "tableFrom": "container_usage_interval", + "tableTo": "cloud_billing_sku", + "columnsFrom": [ + "cloud_billing_sku_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "container_usage_interval_subject_type": { + "name": "container_usage_interval_subject_type", + "value": "\"container_usage_interval\".\"subject_type\" IN ('user', 'org')" + }, + "container_usage_interval_actor_type": { + "name": "container_usage_interval_actor_type", + "value": "\"container_usage_interval\".\"actor_type\" IN ('user', 'bot')" + }, + "container_usage_interval_context_fingerprint": { + "name": "container_usage_interval_context_fingerprint", + "value": "\"container_usage_interval\".\"context_fingerprint\" ~ '^[a-f0-9]{64}$'" + }, + "container_usage_interval_attribution": { + "name": "container_usage_interval_attribution", + "value": "\"container_usage_interval\".\"actor_type\" = 'bot' OR (\"container_usage_interval\".\"actor_type\" = 'user' AND (\"container_usage_interval\".\"subject_type\" <> 'user' OR \"container_usage_interval\".\"actor_id\" = \"container_usage_interval\".\"subject_id\"))" + }, + "container_usage_interval_status": { + "name": "container_usage_interval_status", + "value": "\"container_usage_interval\".\"status\" IN ('open', 'closed')" + }, + "container_usage_interval_billing_mode": { + "name": "container_usage_interval_billing_mode", + "value": "\"container_usage_interval\".\"billing_mode\" IN ('shadow', 'paid')" + }, + "container_usage_interval_paid_rate": { + "name": "container_usage_interval_paid_rate", + "value": "(\"container_usage_interval\".\"billing_mode\" = 'shadow' AND \"container_usage_interval\".\"rate_cents_per_unit\" IS NULL) OR (\"container_usage_interval\".\"billing_mode\" = 'paid' AND \"container_usage_interval\".\"rate_cents_per_unit\" > 0)" + }, + "container_usage_interval_open_closed_shape": { + "name": "container_usage_interval_open_closed_shape", + "value": "(\"container_usage_interval\".\"status\" = 'open' AND \"container_usage_interval\".\"stopped_at\" IS NULL AND \"container_usage_interval\".\"close_reason\" IS NULL) OR (\"container_usage_interval\".\"status\" = 'closed' AND \"container_usage_interval\".\"stopped_at\" IS NOT NULL AND \"container_usage_interval\".\"close_reason\" IS NOT NULL)" + }, + "container_usage_interval_time_order": { + "name": "container_usage_interval_time_order", + "value": "\"container_usage_interval\".\"last_seen_at\" >= \"container_usage_interval\".\"started_at\" AND (\"container_usage_interval\".\"stopped_at\" IS NULL OR (\"container_usage_interval\".\"stopped_at\" >= \"container_usage_interval\".\"started_at\" AND \"container_usage_interval\".\"stopped_at\" <= \"container_usage_interval\".\"last_seen_at\"))" + }, + "container_usage_interval_last_heartbeat_seq_nonnegative": { + "name": "container_usage_interval_last_heartbeat_seq_nonnegative", + "value": "\"container_usage_interval\".\"last_heartbeat_seq\" >= 0" + }, + "container_usage_interval_confirmed_seconds_nonnegative": { + "name": "container_usage_interval_confirmed_seconds_nonnegative", + "value": "\"container_usage_interval\".\"confirmed_seconds\" >= 0" + }, + "container_usage_interval_settled_billable_seconds_nonnegative": { + "name": "container_usage_interval_settled_billable_seconds_nonnegative", + "value": "\"container_usage_interval\".\"settled_billable_seconds\" >= 0 AND \"container_usage_interval\".\"settled_billable_seconds\" <= \"container_usage_interval\".\"confirmed_seconds\"" + }, + "container_usage_interval_final_stop_seq_positive": { + "name": "container_usage_interval_final_stop_seq_positive", + "value": "\"container_usage_interval\".\"final_stop_seq\" IS NULL OR \"container_usage_interval\".\"final_stop_seq\" > 0" + } + }, + "isRLSEnabled": false + }, + "public.container_usage_segment": { + "name": "container_usage_segment", + "schema": "", + "columns": { + "interval_id": { + "name": "interval_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "seq": { + "name": "seq", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "idempotency_key": { + "name": "idempotency_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "reported_seconds": { + "name": "reported_seconds", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "usage_seconds": { + "name": "usage_seconds", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "received_at": { + "name": "received_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "IDX_container_usage_segment_received": { + "name": "IDX_container_usage_segment_received", + "columns": [ + { + "expression": "received_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "container_usage_segment_interval_id_container_usage_interval_id_fk": { + "name": "container_usage_segment_interval_id_container_usage_interval_id_fk", + "tableFrom": "container_usage_segment", + "tableTo": "container_usage_interval", + "columnsFrom": [ + "interval_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "container_usage_segment_interval_id_seq_pk": { + "name": "container_usage_segment_interval_id_seq_pk", + "columns": [ + "interval_id", + "seq" + ] + } + }, + "uniqueConstraints": { + "container_usage_segment_idempotency_key_unique": { + "name": "container_usage_segment_idempotency_key_unique", + "nullsNotDistinct": false, + "columns": [ + "idempotency_key" + ] + } + }, + "policies": {}, + "checkConstraints": { + "container_usage_segment_seq_positive": { + "name": "container_usage_segment_seq_positive", + "value": "\"container_usage_segment\".\"seq\" > 0" + }, + "container_usage_segment_reported_seconds_nonnegative": { + "name": "container_usage_segment_reported_seconds_nonnegative", + "value": "\"container_usage_segment\".\"reported_seconds\" >= 0" + }, + "container_usage_segment_usage_seconds_nonnegative": { + "name": "container_usage_segment_usage_seconds_nonnegative", + "value": "\"container_usage_segment\".\"usage_seconds\" >= 0" + }, + "container_usage_segment_usage_within_reported": { + "name": "container_usage_segment_usage_within_reported", + "value": "\"container_usage_segment\".\"usage_seconds\" <= \"container_usage_segment\".\"reported_seconds\"" + } + }, + "isRLSEnabled": false + }, + "public.content_moderation_reports": { + "name": "content_moderation_reports", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "pg_catalog.gen_random_uuid()" + }, + "kilo_user_id": { + "name": "kilo_user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "surface": { + "name": "surface", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "target_kind": { + "name": "target_kind", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "target_id": { + "name": "target_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "model_id": { + "name": "model_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "session_id": { + "name": "session_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "reason": { + "name": "reason", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "context_json": { + "name": "context_json", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "receipt_id": { + "name": "receipt_id", + "type": "uuid", + "primaryKey": false, + "notNull": true, + "default": "pg_catalog.gen_random_uuid()" + }, + "triage_status": { + "name": "triage_status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'received'" + }, + "appeal_status": { + "name": "appeal_status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'none'" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "IDX_content_moderation_reports_user_created": { + "name": "IDX_content_moderation_reports_user_created", + "columns": [ + { + "expression": "kilo_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_content_moderation_reports_target": { + "name": "IDX_content_moderation_reports_target", + "columns": [ + { + "expression": "target_kind", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "target_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "content_moderation_reports_receipt_id_unique": { + "name": "content_moderation_reports_receipt_id_unique", + "nullsNotDistinct": false, + "columns": [ + "receipt_id" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.contributor_champion_contributors": { + "name": "contributor_champion_contributors", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "pg_catalog.gen_random_uuid()" + }, + "github_login": { + "name": "github_login", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "github_profile_url": { + "name": "github_profile_url", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "github_user_id": { + "name": "github_user_id", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "first_contribution_at": { + "name": "first_contribution_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "last_contribution_at": { + "name": "last_contribution_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "all_time_contributions": { + "name": "all_time_contributions", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "manual_email": { + "name": "manual_email", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "IDX_contributor_champion_contributors_last_contribution_at": { + "name": "IDX_contributor_champion_contributors_last_contribution_at", + "columns": [ + { + "expression": "last_contribution_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_contributor_champion_contributors_manual_email": { + "name": "IDX_contributor_champion_contributors_manual_email", + "columns": [ + { + "expression": "manual_email", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "UQ_contributor_champion_contributors_github_login": { + "name": "UQ_contributor_champion_contributors_github_login", + "nullsNotDistinct": false, + "columns": [ + "github_login" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.contributor_champion_events": { + "name": "contributor_champion_events", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "pg_catalog.gen_random_uuid()" + }, + "contributor_id": { + "name": "contributor_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "repo_full_name": { + "name": "repo_full_name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "github_pr_number": { + "name": "github_pr_number", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "github_pr_url": { + "name": "github_pr_url", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "github_pr_title": { + "name": "github_pr_title", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "github_author_login": { + "name": "github_author_login", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "github_author_email": { + "name": "github_author_email", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "merged_at": { + "name": "merged_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "IDX_contributor_champion_events_contributor_id": { + "name": "IDX_contributor_champion_events_contributor_id", + "columns": [ + { + "expression": "contributor_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_contributor_champion_events_merged_at": { + "name": "IDX_contributor_champion_events_merged_at", + "columns": [ + { + "expression": "merged_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_contributor_champion_events_author_email": { + "name": "IDX_contributor_champion_events_author_email", + "columns": [ + { + "expression": "github_author_email", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "contributor_champion_events_contributor_id_contributor_champion_contributors_id_fk": { + "name": "contributor_champion_events_contributor_id_contributor_champion_contributors_id_fk", + "tableFrom": "contributor_champion_events", + "tableTo": "contributor_champion_contributors", + "columnsFrom": [ + "contributor_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "UQ_contributor_champion_events_repo_pr": { + "name": "UQ_contributor_champion_events_repo_pr", + "nullsNotDistinct": false, + "columns": [ + "repo_full_name", + "github_pr_number" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.contributor_champion_memberships": { + "name": "contributor_champion_memberships", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "pg_catalog.gen_random_uuid()" + }, + "contributor_id": { + "name": "contributor_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "selected_tier": { + "name": "selected_tier", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "enrolled_tier": { + "name": "enrolled_tier", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "enrolled_at": { + "name": "enrolled_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "credit_amount_microdollars": { + "name": "credit_amount_microdollars", + "type": "bigint", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "credits_last_granted_at": { + "name": "credits_last_granted_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "linked_kilo_user_id": { + "name": "linked_kilo_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "IDX_contributor_champion_memberships_credits_due": { + "name": "IDX_contributor_champion_memberships_credits_due", + "columns": [ + { + "expression": "credits_last_granted_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"contributor_champion_memberships\".\"enrolled_tier\" IS NOT NULL AND \"contributor_champion_memberships\".\"credit_amount_microdollars\" > 0", + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_contributor_champion_memberships_linked_kilo_user_id": { + "name": "IDX_contributor_champion_memberships_linked_kilo_user_id", + "columns": [ + { + "expression": "linked_kilo_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "contributor_champion_memberships_contributor_id_contributor_champion_contributors_id_fk": { + "name": "contributor_champion_memberships_contributor_id_contributor_champion_contributors_id_fk", + "tableFrom": "contributor_champion_memberships", + "tableTo": "contributor_champion_contributors", + "columnsFrom": [ + "contributor_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "cascade" + }, + "contributor_champion_memberships_linked_kilo_user_id_kilocode_users_id_fk": { + "name": "contributor_champion_memberships_linked_kilo_user_id_kilocode_users_id_fk", + "tableFrom": "contributor_champion_memberships", + "tableTo": "kilocode_users", + "columnsFrom": [ + "linked_kilo_user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "UQ_contributor_champion_memberships_contributor_id": { + "name": "UQ_contributor_champion_memberships_contributor_id", + "nullsNotDistinct": false, + "columns": [ + "contributor_id" + ] + } + }, + "policies": {}, + "checkConstraints": { + "contributor_champion_memberships_selected_tier_check": { + "name": "contributor_champion_memberships_selected_tier_check", + "value": "\"contributor_champion_memberships\".\"selected_tier\" IS NULL OR \"contributor_champion_memberships\".\"selected_tier\" IN ('contributor', 'ambassador', 'champion')" + }, + "contributor_champion_memberships_enrolled_tier_check": { + "name": "contributor_champion_memberships_enrolled_tier_check", + "value": "\"contributor_champion_memberships\".\"enrolled_tier\" IS NULL OR \"contributor_champion_memberships\".\"enrolled_tier\" IN ('contributor', 'ambassador', 'champion')" + } + }, + "isRLSEnabled": false + }, + "public.contributor_champion_sync_state": { + "name": "contributor_champion_sync_state", + "schema": "", + "columns": { + "repo_full_name": { + "name": "repo_full_name", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "last_merged_at": { + "name": "last_merged_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "last_synced_at": { + "name": "last_synced_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.credit_campaigns": { + "name": "credit_campaigns", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "slug": { + "name": "slug", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "credit_category": { + "name": "credit_category", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "amount_microdollars": { + "name": "amount_microdollars", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "credit_expiry_hours": { + "name": "credit_expiry_hours", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "campaign_ends_at": { + "name": "campaign_ends_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "total_redemptions_allowed": { + "name": "total_redemptions_allowed", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "active": { + "name": "active", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_by_kilo_user_id": { + "name": "created_by_kilo_user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "UQ_credit_campaigns_slug": { + "name": "UQ_credit_campaigns_slug", + "columns": [ + { + "expression": "slug", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "UQ_credit_campaigns_credit_category": { + "name": "UQ_credit_campaigns_credit_category", + "columns": [ + { + "expression": "credit_category", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "credit_campaigns_slug_format_check": { + "name": "credit_campaigns_slug_format_check", + "value": "\"credit_campaigns\".\"slug\" ~ '^[a-z0-9-]{5,40}$'" + }, + "credit_campaigns_amount_positive_check": { + "name": "credit_campaigns_amount_positive_check", + "value": "\"credit_campaigns\".\"amount_microdollars\" > 0" + }, + "credit_campaigns_credit_expiry_hours_positive_check": { + "name": "credit_campaigns_credit_expiry_hours_positive_check", + "value": "\"credit_campaigns\".\"credit_expiry_hours\" IS NULL OR \"credit_campaigns\".\"credit_expiry_hours\" > 0" + }, + "credit_campaigns_total_redemptions_allowed_positive_check": { + "name": "credit_campaigns_total_redemptions_allowed_positive_check", + "value": "\"credit_campaigns\".\"total_redemptions_allowed\" > 0" + } + }, + "isRLSEnabled": false + }, + "public.credit_transactions": { + "name": "credit_transactions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "pg_catalog.gen_random_uuid()" + }, + "kilo_user_id": { + "name": "kilo_user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "amount_microdollars": { + "name": "amount_microdollars", + "type": "bigint", + "primaryKey": false, + "notNull": true + }, + "expiration_baseline_microdollars_used": { + "name": "expiration_baseline_microdollars_used", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "original_baseline_microdollars_used": { + "name": "original_baseline_microdollars_used", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "is_free": { + "name": "is_free", + "type": "boolean", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "original_transaction_id": { + "name": "original_transaction_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "stripe_payment_id": { + "name": "stripe_payment_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "coinbase_credit_block_id": { + "name": "coinbase_credit_block_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "credit_category": { + "name": "credit_category", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "expiry_date": { + "name": "expiry_date", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "organization_id": { + "name": "organization_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "created_by_kilo_user_id": { + "name": "created_by_kilo_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "check_category_uniqueness": { + "name": "check_category_uniqueness", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + } + }, + "indexes": { + "IDX_credit_transactions_created_at": { + "name": "IDX_credit_transactions_created_at", + "columns": [ + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_credit_transactions_is_free": { + "name": "IDX_credit_transactions_is_free", + "columns": [ + { + "expression": "is_free", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_credit_transactions_kilo_user_id": { + "name": "IDX_credit_transactions_kilo_user_id", + "columns": [ + { + "expression": "kilo_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_credit_transactions_credit_category": { + "name": "IDX_credit_transactions_credit_category", + "columns": [ + { + "expression": "credit_category", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_credit_transactions_stripe_payment_id": { + "name": "IDX_credit_transactions_stripe_payment_id", + "columns": [ + { + "expression": "stripe_payment_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_credit_transactions_original_transaction_id": { + "name": "IDX_credit_transactions_original_transaction_id", + "columns": [ + { + "expression": "original_transaction_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_credit_transactions_coinbase_credit_block_id": { + "name": "IDX_credit_transactions_coinbase_credit_block_id", + "columns": [ + { + "expression": "coinbase_credit_block_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_credit_transactions_organization_id": { + "name": "IDX_credit_transactions_organization_id", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_credit_transactions_unique_category": { + "name": "IDX_credit_transactions_unique_category", + "columns": [ + { + "expression": "kilo_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "credit_category", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"credit_transactions\".\"check_category_uniqueness\" = TRUE", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "credit_transactions_created_by_kilo_user_id_kilocode_users_id_fk": { + "name": "credit_transactions_created_by_kilo_user_id_kilocode_users_id_fk", + "tableFrom": "credit_transactions", + "tableTo": "kilocode_users", + "columnsFrom": [ + "created_by_kilo_user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.custom_llm2": { + "name": "custom_llm2", + "schema": "", + "columns": { + "public_id": { + "name": "public_id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "definition": { + "name": "definition", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "encrypted_api_key": { + "name": "encrypted_api_key", + "type": "jsonb", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.deleted_user_email_tombstones": { + "name": "deleted_user_email_tombstones", + "schema": "", + "columns": { + "normalized_email_hash": { + "name": "normalized_email_hash", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.deployment_builds": { + "name": "deployment_builds", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "deployment_id": { + "name": "deployment_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "started_at": { + "name": "started_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "completed_at": { + "name": "completed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "idx_deployment_builds_deployment_id": { + "name": "idx_deployment_builds_deployment_id", + "columns": [ + { + "expression": "deployment_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_deployment_builds_status": { + "name": "idx_deployment_builds_status", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "deployment_builds_deployment_id_deployments_id_fk": { + "name": "deployment_builds_deployment_id_deployments_id_fk", + "tableFrom": "deployment_builds", + "tableTo": "deployments", + "columnsFrom": [ + "deployment_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.deployment_env_vars": { + "name": "deployment_env_vars", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "pg_catalog.gen_random_uuid()" + }, + "deployment_id": { + "name": "deployment_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "key": { + "name": "key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "value": { + "name": "value", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "is_secret": { + "name": "is_secret", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "idx_deployment_env_vars_deployment_id": { + "name": "idx_deployment_env_vars_deployment_id", + "columns": [ + { + "expression": "deployment_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "deployment_env_vars_deployment_id_deployments_id_fk": { + "name": "deployment_env_vars_deployment_id_deployments_id_fk", + "tableFrom": "deployment_env_vars", + "tableTo": "deployments", + "columnsFrom": [ + "deployment_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "UQ_deployment_env_vars_deployment_key": { + "name": "UQ_deployment_env_vars_deployment_key", + "nullsNotDistinct": false, + "columns": [ + "deployment_id", + "key" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.deployment_events": { + "name": "deployment_events", + "schema": "", + "columns": { + "build_id": { + "name": "build_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "event_id": { + "name": "event_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "event_type": { + "name": "event_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'log'" + }, + "timestamp": { + "name": "timestamp", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "payload": { + "name": "payload", + "type": "jsonb", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "idx_deployment_events_build_id": { + "name": "idx_deployment_events_build_id", + "columns": [ + { + "expression": "build_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_deployment_events_timestamp": { + "name": "idx_deployment_events_timestamp", + "columns": [ + { + "expression": "timestamp", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_deployment_events_type": { + "name": "idx_deployment_events_type", + "columns": [ + { + "expression": "event_type", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "deployment_events_build_id_deployment_builds_id_fk": { + "name": "deployment_events_build_id_deployment_builds_id_fk", + "tableFrom": "deployment_events", + "tableTo": "deployment_builds", + "columnsFrom": [ + "build_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "deployment_events_build_id_event_id_pk": { + "name": "deployment_events_build_id_event_id_pk", + "columns": [ + "build_id", + "event_id" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.deployment_threat_detections": { + "name": "deployment_threat_detections", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "deployment_id": { + "name": "deployment_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "build_id": { + "name": "build_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "threat_type": { + "name": "threat_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "idx_deployment_threat_detections_deployment_id": { + "name": "idx_deployment_threat_detections_deployment_id", + "columns": [ + { + "expression": "deployment_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_deployment_threat_detections_created_at": { + "name": "idx_deployment_threat_detections_created_at", + "columns": [ + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "deployment_threat_detections_deployment_id_deployments_id_fk": { + "name": "deployment_threat_detections_deployment_id_deployments_id_fk", + "tableFrom": "deployment_threat_detections", + "tableTo": "deployments", + "columnsFrom": [ + "deployment_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "deployment_threat_detections_build_id_deployment_builds_id_fk": { + "name": "deployment_threat_detections_build_id_deployment_builds_id_fk", + "tableFrom": "deployment_threat_detections", + "tableTo": "deployment_builds", + "columnsFrom": [ + "build_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.deployments": { + "name": "deployments", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "pg_catalog.gen_random_uuid()" + }, + "created_by_user_id": { + "name": "created_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "owned_by_user_id": { + "name": "owned_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "owned_by_organization_id": { + "name": "owned_by_organization_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "deployment_slug": { + "name": "deployment_slug", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "internal_worker_name": { + "name": "internal_worker_name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "repository_source": { + "name": "repository_source", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "branch": { + "name": "branch", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "deployment_url": { + "name": "deployment_url", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "platform_integration_id": { + "name": "platform_integration_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "source_type": { + "name": "source_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'github'" + }, + "git_auth_token": { + "name": "git_auth_token", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "last_deployed_at": { + "name": "last_deployed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "last_build_id": { + "name": "last_build_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "threat_status": { + "name": "threat_status", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_from": { + "name": "created_from", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "idx_deployments_owned_by_user_id": { + "name": "idx_deployments_owned_by_user_id", + "columns": [ + { + "expression": "owned_by_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_deployments_owned_by_organization_id": { + "name": "idx_deployments_owned_by_organization_id", + "columns": [ + { + "expression": "owned_by_organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_deployments_platform_integration_id": { + "name": "idx_deployments_platform_integration_id", + "columns": [ + { + "expression": "platform_integration_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_deployments_repository_source_branch": { + "name": "idx_deployments_repository_source_branch", + "columns": [ + { + "expression": "repository_source", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "branch", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_deployments_threat_status_pending": { + "name": "idx_deployments_threat_status_pending", + "columns": [ + { + "expression": "threat_status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"deployments\".\"threat_status\" = 'pending_scan'", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "deployments_owned_by_user_id_kilocode_users_id_fk": { + "name": "deployments_owned_by_user_id_kilocode_users_id_fk", + "tableFrom": "deployments", + "tableTo": "kilocode_users", + "columnsFrom": [ + "owned_by_user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "deployments_owned_by_organization_id_organizations_id_fk": { + "name": "deployments_owned_by_organization_id_organizations_id_fk", + "tableFrom": "deployments", + "tableTo": "organizations", + "columnsFrom": [ + "owned_by_organization_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "UQ_deployments_deployment_slug": { + "name": "UQ_deployments_deployment_slug", + "nullsNotDistinct": false, + "columns": [ + "deployment_slug" + ] + } + }, + "policies": {}, + "checkConstraints": { + "deployments_owner_check": { + "name": "deployments_owner_check", + "value": "(\n (\"deployments\".\"owned_by_user_id\" IS NOT NULL AND \"deployments\".\"owned_by_organization_id\" IS NULL) OR\n (\"deployments\".\"owned_by_user_id\" IS NULL AND \"deployments\".\"owned_by_organization_id\" IS NOT NULL)\n )" + }, + "deployments_source_type_check": { + "name": "deployments_source_type_check", + "value": "\"deployments\".\"source_type\" IN ('github', 'git', 'app-builder')" + } + }, + "isRLSEnabled": false + }, + "public.deployments_ephemeral": { + "name": "deployments_ephemeral", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "pg_catalog.gen_random_uuid()" + }, + "owned_by_user_id": { + "name": "owned_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "source_type": { + "name": "source_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "internal_worker_name": { + "name": "internal_worker_name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "deployment_slug": { + "name": "deployment_slug", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "next_cleanup_at": { + "name": "next_cleanup_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "cleanup_claim_token": { + "name": "cleanup_claim_token", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "cleanup_claimed_until": { + "name": "cleanup_claimed_until", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "idx_deployments_ephemeral_owned_by_user_id": { + "name": "idx_deployments_ephemeral_owned_by_user_id", + "columns": [ + { + "expression": "owned_by_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_deployments_ephemeral_next_cleanup_at": { + "name": "idx_deployments_ephemeral_next_cleanup_at", + "columns": [ + { + "expression": "next_cleanup_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "deployments_ephemeral_owned_by_user_id_kilocode_users_id_fk": { + "name": "deployments_ephemeral_owned_by_user_id_kilocode_users_id_fk", + "tableFrom": "deployments_ephemeral", + "tableTo": "kilocode_users", + "columnsFrom": [ + "owned_by_user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "UQ_deployments_ephemeral_internal_worker_name": { + "name": "UQ_deployments_ephemeral_internal_worker_name", + "nullsNotDistinct": false, + "columns": [ + "internal_worker_name" + ] + }, + "UQ_deployments_ephemeral_deployment_slug": { + "name": "UQ_deployments_ephemeral_deployment_slug", + "nullsNotDistinct": false, + "columns": [ + "deployment_slug" + ] + } + }, + "policies": {}, + "checkConstraints": { + "deployments_ephemeral_source_type_check": { + "name": "deployments_ephemeral_source_type_check", + "value": "\"deployments_ephemeral\".\"source_type\" IN ('html')" + }, + "deployments_ephemeral_status_check": { + "name": "deployments_ephemeral_status_check", + "value": "\"deployments_ephemeral\".\"status\" IN ('pending', 'active', 'cleanup_retry')" + }, + "deployments_ephemeral_claim_fields_check": { + "name": "deployments_ephemeral_claim_fields_check", + "value": "(\"deployments_ephemeral\".\"cleanup_claim_token\" IS NULL) = (\"deployments_ephemeral\".\"cleanup_claimed_until\" IS NULL)" + }, + "deployments_ephemeral_active_fields_check": { + "name": "deployments_ephemeral_active_fields_check", + "value": "\"deployments_ephemeral\".\"status\" <> 'active' OR (\"deployments_ephemeral\".\"deployment_slug\" IS NOT NULL AND \"deployments_ephemeral\".\"expires_at\" IS NOT NULL)" + } + }, + "isRLSEnabled": false + }, + "public.device_auth_requests": { + "name": "device_auth_requests", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "pg_catalog.gen_random_uuid()" + }, + "code": { + "name": "code", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "kilo_user_id": { + "name": "kilo_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "approved_at": { + "name": "approved_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "consumed_at": { + "name": "consumed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "user_code": { + "name": "user_code", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "device_code_hash": { + "name": "device_code_hash", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "user_agent": { + "name": "user_agent", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "ip_address": { + "name": "ip_address", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "UQ_device_auth_requests_code": { + "name": "UQ_device_auth_requests_code", + "columns": [ + { + "expression": "code", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_device_auth_requests_status": { + "name": "IDX_device_auth_requests_status", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_device_auth_requests_expires_at": { + "name": "IDX_device_auth_requests_expires_at", + "columns": [ + { + "expression": "expires_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_device_auth_requests_kilo_user_id": { + "name": "IDX_device_auth_requests_kilo_user_id", + "columns": [ + { + "expression": "kilo_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "UQ_device_auth_requests_device_code_hash": { + "name": "UQ_device_auth_requests_device_code_hash", + "columns": [ + { + "expression": "device_code_hash", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"device_auth_requests\".\"device_code_hash\" IS NOT NULL", + "concurrently": true, + "method": "btree", + "with": {} + }, + "IDX_device_auth_requests_user_code": { + "name": "IDX_device_auth_requests_user_code", + "columns": [ + { + "expression": "user_code", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"device_auth_requests\".\"user_code\" IS NOT NULL", + "concurrently": true, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "device_auth_requests_kilo_user_id_kilocode_users_id_fk": { + "name": "device_auth_requests_kilo_user_id_kilocode_users_id_fk", + "tableFrom": "device_auth_requests", + "tableTo": "kilocode_users", + "columnsFrom": [ + "kilo_user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.device_refresh_tokens": { + "name": "device_refresh_tokens", + "schema": "", + "columns": { + "token_hash": { + "name": "token_hash", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "device_session_id": { + "name": "device_session_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "consumed_at": { + "name": "consumed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "IDX_device_refresh_tokens_device_session_id": { + "name": "IDX_device_refresh_tokens_device_session_id", + "columns": [ + { + "expression": "device_session_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_device_refresh_tokens_expires_at": { + "name": "IDX_device_refresh_tokens_expires_at", + "columns": [ + { + "expression": "expires_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "device_refresh_tokens_device_session_id_device_sessions_id_fk": { + "name": "device_refresh_tokens_device_session_id_device_sessions_id_fk", + "tableFrom": "device_refresh_tokens", + "tableTo": "device_sessions", + "columnsFrom": [ + "device_session_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.device_sessions": { + "name": "device_sessions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "pg_catalog.gen_random_uuid()" + }, + "kilo_user_id": { + "name": "kilo_user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "device_auth_request_id": { + "name": "device_auth_request_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "user_agent": { + "name": "user_agent", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "last_seen_at": { + "name": "last_seen_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "revoked_at": { + "name": "revoked_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "revoked_reason": { + "name": "revoked_reason", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "IDX_device_sessions_kilo_user_id": { + "name": "IDX_device_sessions_kilo_user_id", + "columns": [ + { + "expression": "kilo_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_device_sessions_revoked_at": { + "name": "IDX_device_sessions_revoked_at", + "columns": [ + { + "expression": "revoked_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "device_sessions_kilo_user_id_kilocode_users_id_fk": { + "name": "device_sessions_kilo_user_id_kilocode_users_id_fk", + "tableFrom": "device_sessions", + "tableTo": "kilocode_users", + "columnsFrom": [ + "kilo_user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.direct_byok_model_lists": { + "name": "direct_byok_model_lists", + "schema": "", + "columns": { + "provider_id": { + "name": "provider_id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "models": { + "name": "models", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "synced_at": { + "name": "synced_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.discord_gateway_listener": { + "name": "discord_gateway_listener", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "default": 1 + }, + "listener_id": { + "name": "listener_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "started_at": { + "name": "started_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.editor_name": { + "name": "editor_name", + "schema": "", + "columns": { + "editor_name_id": { + "name": "editor_name_id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "editor_name": { + "name": "editor_name", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "UQ_editor_name": { + "name": "UQ_editor_name", + "columns": [ + { + "expression": "editor_name", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.enkrypt_sync_state": { + "name": "enkrypt_sync_state", + "schema": "", + "columns": { + "job_name": { + "name": "job_name", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "attempt_id": { + "name": "attempt_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "last_attempt_at": { + "name": "last_attempt_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "last_completed_at": { + "name": "last_completed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "last_success_at": { + "name": "last_success_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "last_outcome": { + "name": "last_outcome", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "last_failure_category": { + "name": "last_failure_category", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "last_counts": { + "name": "last_counts", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "last_success_counts": { + "name": "last_success_counts", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "verified_models": { + "name": "verified_models", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "baseline_matched_count": { + "name": "baseline_matched_count", + "type": "integer", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "enkrypt_sync_state_singleton": { + "name": "enkrypt_sync_state_singleton", + "value": "\"enkrypt_sync_state\".\"job_name\" = 'enkrypt'" + }, + "enkrypt_sync_state_outcome": { + "name": "enkrypt_sync_state_outcome", + "value": "\"enkrypt_sync_state\".\"last_outcome\" IN ('running', 'succeeded', 'failed')" + }, + "enkrypt_sync_state_baseline": { + "name": "enkrypt_sync_state_baseline", + "value": "\"enkrypt_sync_state\".\"baseline_matched_count\" >= 0" + } + }, + "isRLSEnabled": false + }, + "public.enrichment_data": { + "name": "enrichment_data", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "pg_catalog.gen_random_uuid()" + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "github_enrichment_data": { + "name": "github_enrichment_data", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "linkedin_enrichment_data": { + "name": "linkedin_enrichment_data", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "clay_enrichment_data": { + "name": "clay_enrichment_data", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "IDX_enrichment_data_user_id": { + "name": "IDX_enrichment_data_user_id", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "enrichment_data_user_id_kilocode_users_id_fk": { + "name": "enrichment_data_user_id_kilocode_users_id_fk", + "tableFrom": "enrichment_data", + "tableTo": "kilocode_users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "UQ_enrichment_data_user_id": { + "name": "UQ_enrichment_data_user_id", + "nullsNotDistinct": false, + "columns": [ + "user_id" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.exa_monthly_usage": { + "name": "exa_monthly_usage", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "pg_catalog.gen_random_uuid()" + }, + "kilo_user_id": { + "name": "kilo_user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "organization_id": { + "name": "organization_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "month": { + "name": "month", + "type": "date", + "primaryKey": false, + "notNull": true + }, + "total_cost_microdollars": { + "name": "total_cost_microdollars", + "type": "bigint", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "total_charged_microdollars": { + "name": "total_charged_microdollars", + "type": "bigint", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "request_count": { + "name": "request_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "free_allowance_microdollars": { + "name": "free_allowance_microdollars", + "type": "bigint", + "primaryKey": false, + "notNull": true, + "default": 10000000 + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "idx_exa_monthly_usage_personal": { + "name": "idx_exa_monthly_usage_personal", + "columns": [ + { + "expression": "kilo_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "month", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"exa_monthly_usage\".\"organization_id\" is null", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_exa_monthly_usage_org": { + "name": "idx_exa_monthly_usage_org", + "columns": [ + { + "expression": "kilo_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "month", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"exa_monthly_usage\".\"organization_id\" is not null", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.exa_usage_log": { + "name": "exa_usage_log", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": false, + "notNull": true, + "default": "pg_catalog.gen_random_uuid()" + }, + "kilo_user_id": { + "name": "kilo_user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "organization_id": { + "name": "organization_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "path": { + "name": "path", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "cost_microdollars": { + "name": "cost_microdollars", + "type": "bigint", + "primaryKey": false, + "notNull": true + }, + "charged_to_balance": { + "name": "charged_to_balance", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "feature_id": { + "name": "feature_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "idx_exa_usage_log_user_created": { + "name": "idx_exa_usage_log_user_created", + "columns": [ + { + "expression": "kilo_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": { + "exa_usage_log_id_created_at_pk": { + "name": "exa_usage_log_id_created_at_pk", + "columns": [ + "id", + "created_at" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.external_side_effect_outbox": { + "name": "external_side_effect_outbox", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "pg_catalog.gen_random_uuid()" + }, + "operation": { + "name": "operation", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'send_org_invite_email'" + }, + "invitation_id": { + "name": "invitation_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "payload": { + "name": "payload", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "attempts": { + "name": "attempts", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "next_attempt_at": { + "name": "next_attempt_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "claimed_at": { + "name": "claimed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "delivered_at": { + "name": "delivered_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "last_error": { + "name": "last_error", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "UQ_external_side_effect_outbox_invitation_id": { + "name": "UQ_external_side_effect_outbox_invitation_id", + "columns": [ + { + "expression": "invitation_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_external_side_effect_outbox_status_next_attempt_at": { + "name": "IDX_external_side_effect_outbox_status_next_attempt_at", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "next_attempt_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.feature": { + "name": "feature", + "schema": "", + "columns": { + "feature_id": { + "name": "feature_id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "feature": { + "name": "feature", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "UQ_feature": { + "name": "UQ_feature", + "columns": [ + { + "expression": "feature", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.finish_reason": { + "name": "finish_reason", + "schema": "", + "columns": { + "finish_reason_id": { + "name": "finish_reason_id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "finish_reason": { + "name": "finish_reason", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "UQ_finish_reason": { + "name": "UQ_finish_reason", + "columns": [ + { + "expression": "finish_reason", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.free_model_usage": { + "name": "free_model_usage", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "pg_catalog.gen_random_uuid()" + }, + "ip_address": { + "name": "ip_address", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "model": { + "name": "model", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "kilo_user_id": { + "name": "kilo_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "idx_free_model_usage_ip_created_at": { + "name": "idx_free_model_usage_ip_created_at", + "columns": [ + { + "expression": "ip_address", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_free_model_usage_created_at": { + "name": "idx_free_model_usage_created_at", + "columns": [ + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.github_app_installations": { + "name": "github_app_installations", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "pg_catalog.gen_random_uuid()" + }, + "github_app_type": { + "name": "github_app_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "installation_id": { + "name": "installation_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "account_id": { + "name": "account_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "account_login": { + "name": "account_login", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "account_type": { + "name": "account_type", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "permissions": { + "name": "permissions", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "scopes": { + "name": "scopes", + "type": "text[]", + "primaryKey": false, + "notNull": false + }, + "repository_access": { + "name": "repository_access", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "repositories": { + "name": "repositories", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "repositories_synced_at": { + "name": "repositories_synced_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "lifecycle_state": { + "name": "lifecycle_state", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'unknown'" + }, + "suspended_at": { + "name": "suspended_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "deleted_at": { + "name": "deleted_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "auth_invalid_at": { + "name": "auth_invalid_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "auth_invalid_reason": { + "name": "auth_invalid_reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "sharing_mode": { + "name": "sharing_mode", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'exclusive'" + }, + "sharing_admission_checked_at": { + "name": "sharing_admission_checked_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "revision": { + "name": "revision", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "observed_at": { + "name": "observed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "UQ_github_app_installations_app_installation": { + "name": "UQ_github_app_installations_app_installation", + "columns": [ + { + "expression": "github_app_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "installation_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "github_app_installations_app_type_check": { + "name": "github_app_installations_app_type_check", + "value": "\"github_app_installations\".\"github_app_type\" IN ('standard', 'lite')" + }, + "github_app_installations_installation_id_check": { + "name": "github_app_installations_installation_id_check", + "value": "\"github_app_installations\".\"installation_id\" ~ '^[1-9][0-9]*$'" + }, + "github_app_installations_lifecycle_state_check": { + "name": "github_app_installations_lifecycle_state_check", + "value": "\"github_app_installations\".\"lifecycle_state\" IN ('unknown', 'active', 'suspended', 'deleted')" + }, + "github_app_installations_sharing_mode_check": { + "name": "github_app_installations_sharing_mode_check", + "value": "\"github_app_installations\".\"sharing_mode\" IN ('exclusive', 'web_cloud_agent')" + } + }, + "isRLSEnabled": false + }, + "public.github_branch_pull_requests": { + "name": "github_branch_pull_requests", + "schema": "", + "columns": { + "git_url": { + "name": "git_url", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "git_branch": { + "name": "git_branch", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "owned_by_organization_id": { + "name": "owned_by_organization_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "owned_by_user_id": { + "name": "owned_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "pr_url": { + "name": "pr_url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "pr_number": { + "name": "pr_number", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "pr_state": { + "name": "pr_state", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "pr_title": { + "name": "pr_title", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "pr_head_sha": { + "name": "pr_head_sha", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "pr_review_decision": { + "name": "pr_review_decision", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "review_decision_pending": { + "name": "review_decision_pending", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "review_decision_fetching_at": { + "name": "review_decision_fetching_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "pr_last_synced_at": { + "name": "pr_last_synced_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "UQ_github_branch_prs_org": { + "name": "UQ_github_branch_prs_org", + "columns": [ + { + "expression": "git_url", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "git_branch", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "owned_by_organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"github_branch_pull_requests\".\"owned_by_organization_id\" is not null", + "concurrently": false, + "method": "btree", + "with": {} + }, + "UQ_github_branch_prs_user": { + "name": "UQ_github_branch_prs_user", + "columns": [ + { + "expression": "git_url", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "git_branch", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "owned_by_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"github_branch_pull_requests\".\"owned_by_user_id\" is not null", + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_github_branch_prs_url_branch": { + "name": "IDX_github_branch_prs_url_branch", + "columns": [ + { + "expression": "git_url", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "git_branch", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": true, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "github_branch_pull_requests_owned_by_organization_id_organizations_id_fk": { + "name": "github_branch_pull_requests_owned_by_organization_id_organizations_id_fk", + "tableFrom": "github_branch_pull_requests", + "tableTo": "organizations", + "columnsFrom": [ + "owned_by_organization_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "github_branch_pull_requests_owned_by_user_id_kilocode_users_id_fk": { + "name": "github_branch_pull_requests_owned_by_user_id_kilocode_users_id_fk", + "tableFrom": "github_branch_pull_requests", + "tableTo": "kilocode_users", + "columnsFrom": [ + "owned_by_user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "github_branch_pull_requests_owner_check": { + "name": "github_branch_pull_requests_owner_check", + "value": "(\n (\"github_branch_pull_requests\".\"owned_by_organization_id\" IS NOT NULL AND \"github_branch_pull_requests\".\"owned_by_user_id\" IS NULL) OR\n (\"github_branch_pull_requests\".\"owned_by_organization_id\" IS NULL AND \"github_branch_pull_requests\".\"owned_by_user_id\" IS NOT NULL)\n )" + }, + "github_branch_pull_requests_review_decision_check": { + "name": "github_branch_pull_requests_review_decision_check", + "value": "\"github_branch_pull_requests\".\"pr_review_decision\" IS NULL OR \"github_branch_pull_requests\".\"pr_review_decision\" IN ('approved', 'changes_requested', 'review_required')" + } + }, + "isRLSEnabled": false + }, + "public.github_connection_attempts": { + "name": "github_connection_attempts", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "pg_catalog.gen_random_uuid()" + }, + "kilo_user_id": { + "name": "kilo_user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "owner_type": { + "name": "owner_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "owner_id": { + "name": "owner_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "github_app_type": { + "name": "github_app_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "return_to": { + "name": "return_to", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "selected_installation_id": { + "name": "selected_installation_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "github_user_id": { + "name": "github_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "eligible_installations": { + "name": "eligible_installations", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "completed_integration_id": { + "name": "completed_integration_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "consumed_at": { + "name": "consumed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "IDX_github_connection_attempts_expires_at": { + "name": "IDX_github_connection_attempts_expires_at", + "columns": [ + { + "expression": "expires_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_github_connection_attempts_completed_integration_id": { + "name": "IDX_github_connection_attempts_completed_integration_id", + "columns": [ + { + "expression": "completed_integration_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "github_connection_attempts_completed_integration_id_platform_integrations_id_fk": { + "name": "github_connection_attempts_completed_integration_id_platform_integrations_id_fk", + "tableFrom": "github_connection_attempts", + "tableTo": "platform_integrations", + "columnsFrom": [ + "completed_integration_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "github_connection_attempts_owner_type_check": { + "name": "github_connection_attempts_owner_type_check", + "value": "\"github_connection_attempts\".\"owner_type\" IN ('user', 'org')" + }, + "github_connection_attempts_app_type_check": { + "name": "github_connection_attempts_app_type_check", + "value": "\"github_connection_attempts\".\"github_app_type\" IN ('standard', 'lite')" + } + }, + "isRLSEnabled": false + }, + "public.github_install_states": { + "name": "github_install_states", + "schema": "", + "columns": { + "token": { + "name": "token", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "kilo_user_id": { + "name": "kilo_user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "owner_type": { + "name": "owner_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "owner_id": { + "name": "owner_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "github_app_type": { + "name": "github_app_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "return_to": { + "name": "return_to", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "consumed_at": { + "name": "consumed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "IDX_github_install_states_expires_at": { + "name": "IDX_github_install_states_expires_at", + "columns": [ + { + "expression": "expires_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "github_install_states_kilo_user_id_kilocode_users_id_fk": { + "name": "github_install_states_kilo_user_id_kilocode_users_id_fk", + "tableFrom": "github_install_states", + "tableTo": "kilocode_users", + "columnsFrom": [ + "kilo_user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "github_install_states_owner_type_check": { + "name": "github_install_states_owner_type_check", + "value": "\"github_install_states\".\"owner_type\" IN ('org', 'user')" + } + }, + "isRLSEnabled": false + }, + "public.github_installation_webhook_receipts": { + "name": "github_installation_webhook_receipts", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "pg_catalog.gen_random_uuid()" + }, + "github_installation_id": { + "name": "github_installation_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "delivery_id": { + "name": "delivery_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "event_type": { + "name": "event_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'completed'" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "UQ_github_installation_webhook_receipts_delivery": { + "name": "UQ_github_installation_webhook_receipts_delivery", + "columns": [ + { + "expression": "github_installation_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "delivery_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "github_installation_webhook_receipts_github_installation_id_github_app_installations_id_fk": { + "name": "github_installation_webhook_receipts_github_installation_id_github_app_installations_id_fk", + "tableFrom": "github_installation_webhook_receipts", + "tableTo": "github_app_installations", + "columnsFrom": [ + "github_installation_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "github_installation_webhook_receipts_status_check": { + "name": "github_installation_webhook_receipts_status_check", + "value": "\"github_installation_webhook_receipts\".\"status\" IN ('processing', 'completed')" + } + }, + "isRLSEnabled": false + }, + "public.http_ip": { + "name": "http_ip", + "schema": "", + "columns": { + "http_ip_id": { + "name": "http_ip_id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "http_ip": { + "name": "http_ip", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "UQ_http_ip": { + "name": "UQ_http_ip", + "columns": [ + { + "expression": "http_ip", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.http_user_agent": { + "name": "http_user_agent", + "schema": "", + "columns": { + "http_user_agent_id": { + "name": "http_user_agent_id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "http_user_agent": { + "name": "http_user_agent", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "UQ_http_user_agent": { + "name": "UQ_http_user_agent", + "columns": [ + { + "expression": "http_user_agent", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.impact_advocate_participants": { + "name": "impact_advocate_participants", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "pg_catalog.gen_random_uuid()" + }, + "program_key": { + "name": "program_key", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'kiloclaw'" + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "advocate_id": { + "name": "advocate_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "advocate_account_id": { + "name": "advocate_account_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "opaque_referral_identifier": { + "name": "opaque_referral_identifier", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "contact_email": { + "name": "contact_email", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "locale": { + "name": "locale", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "country_code": { + "name": "country_code", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "registration_state": { + "name": "registration_state", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "registered_at": { + "name": "registered_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "last_registration_attempt_at": { + "name": "last_registration_attempt_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "last_error_code": { + "name": "last_error_code", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "last_error_message": { + "name": "last_error_message", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "UQ_impact_advocate_participants_program_referral_identifier": { + "name": "UQ_impact_advocate_participants_program_referral_identifier", + "columns": [ + { + "expression": "program_key", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "opaque_referral_identifier", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"impact_advocate_participants\".\"opaque_referral_identifier\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_impact_advocate_participants_registration_state": { + "name": "IDX_impact_advocate_participants_registration_state", + "columns": [ + { + "expression": "registration_state", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "impact_advocate_participants_user_id_kilocode_users_id_fk": { + "name": "impact_advocate_participants_user_id_kilocode_users_id_fk", + "tableFrom": "impact_advocate_participants", + "tableTo": "kilocode_users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "UQ_impact_advocate_participants_program_user": { + "name": "UQ_impact_advocate_participants_program_user", + "nullsNotDistinct": false, + "columns": [ + "program_key", + "user_id" + ] + } + }, + "policies": {}, + "checkConstraints": { + "impact_advocate_participants_program_key_check": { + "name": "impact_advocate_participants_program_key_check", + "value": "\"impact_advocate_participants\".\"program_key\" IN ('kiloclaw', 'kilo_pass')" + }, + "impact_advocate_participants_registration_state_check": { + "name": "impact_advocate_participants_registration_state_check", + "value": "\"impact_advocate_participants\".\"registration_state\" IN ('pending', 'retrying', 'registered', 'failed')" + } + }, + "isRLSEnabled": false + }, + "public.impact_advocate_registration_attempts": { + "name": "impact_advocate_registration_attempts", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "pg_catalog.gen_random_uuid()" + }, + "program_key": { + "name": "program_key", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'kiloclaw'" + }, + "participant_id": { + "name": "participant_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "dedupe_key": { + "name": "dedupe_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "opaque_cookie_value": { + "name": "opaque_cookie_value", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "cookie_value_length": { + "name": "cookie_value_length", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "delivery_state": { + "name": "delivery_state", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'queued'" + }, + "request_payload": { + "name": "request_payload", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "response_payload": { + "name": "response_payload", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "response_status_code": { + "name": "response_status_code", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "attempt_count": { + "name": "attempt_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "next_retry_at": { + "name": "next_retry_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "claimed_at": { + "name": "claimed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "IDX_impact_advocate_registration_attempts_participant_id": { + "name": "IDX_impact_advocate_registration_attempts_participant_id", + "columns": [ + { + "expression": "participant_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_impact_advocate_registration_attempts_delivery_state": { + "name": "IDX_impact_advocate_registration_attempts_delivery_state", + "columns": [ + { + "expression": "delivery_state", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "impact_advocate_registration_attempts_participant_id_impact_advocate_participants_id_fk": { + "name": "impact_advocate_registration_attempts_participant_id_impact_advocate_participants_id_fk", + "tableFrom": "impact_advocate_registration_attempts", + "tableTo": "impact_advocate_participants", + "columnsFrom": [ + "participant_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "UQ_impact_advocate_registration_attempts_dedupe_key": { + "name": "UQ_impact_advocate_registration_attempts_dedupe_key", + "nullsNotDistinct": false, + "columns": [ + "dedupe_key" + ] + } + }, + "policies": {}, + "checkConstraints": { + "impact_advocate_registration_attempts_program_key_check": { + "name": "impact_advocate_registration_attempts_program_key_check", + "value": "\"impact_advocate_registration_attempts\".\"program_key\" IN ('kiloclaw', 'kilo_pass')" + }, + "impact_advocate_registration_attempts_delivery_state_check": { + "name": "impact_advocate_registration_attempts_delivery_state_check", + "value": "\"impact_advocate_registration_attempts\".\"delivery_state\" IN ('queued', 'sending', 'succeeded', 'failed')" + }, + "impact_advocate_registration_attempts_cookie_value_length_non_negative_check": { + "name": "impact_advocate_registration_attempts_cookie_value_length_non_negative_check", + "value": "\"impact_advocate_registration_attempts\".\"cookie_value_length\" >= 0" + }, + "impact_advocate_registration_attempts_attempt_count_non_negative_check": { + "name": "impact_advocate_registration_attempts_attempt_count_non_negative_check", + "value": "\"impact_advocate_registration_attempts\".\"attempt_count\" >= 0" + } + }, + "isRLSEnabled": false + }, + "public.impact_advocate_reward_redemptions": { + "name": "impact_advocate_reward_redemptions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "pg_catalog.gen_random_uuid()" + }, + "reward_id": { + "name": "reward_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "dedupe_key": { + "name": "dedupe_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "beneficiary_user_id": { + "name": "beneficiary_user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "state": { + "name": "state", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'queued'" + }, + "impact_reward_id": { + "name": "impact_reward_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "request_payload": { + "name": "request_payload", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "lookup_response_payload": { + "name": "lookup_response_payload", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "redeem_response_payload": { + "name": "redeem_response_payload", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "response_status_code": { + "name": "response_status_code", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "attempt_count": { + "name": "attempt_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "next_retry_at": { + "name": "next_retry_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "redeemed_at": { + "name": "redeemed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "IDX_impact_advocate_reward_redemptions_beneficiary_user_id": { + "name": "IDX_impact_advocate_reward_redemptions_beneficiary_user_id", + "columns": [ + { + "expression": "beneficiary_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_impact_advocate_reward_redemptions_state": { + "name": "IDX_impact_advocate_reward_redemptions_state", + "columns": [ + { + "expression": "state", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "impact_advocate_reward_redemptions_reward_id_impact_referral_rewards_id_fk": { + "name": "impact_advocate_reward_redemptions_reward_id_impact_referral_rewards_id_fk", + "tableFrom": "impact_advocate_reward_redemptions", + "tableTo": "impact_referral_rewards", + "columnsFrom": [ + "reward_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "cascade" + }, + "impact_advocate_reward_redemptions_beneficiary_user_id_kilocode_users_id_fk": { + "name": "impact_advocate_reward_redemptions_beneficiary_user_id_kilocode_users_id_fk", + "tableFrom": "impact_advocate_reward_redemptions", + "tableTo": "kilocode_users", + "columnsFrom": [ + "beneficiary_user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "UQ_impact_advocate_reward_redemptions_reward_id": { + "name": "UQ_impact_advocate_reward_redemptions_reward_id", + "nullsNotDistinct": false, + "columns": [ + "reward_id" + ] + }, + "UQ_impact_advocate_reward_redemptions_dedupe_key": { + "name": "UQ_impact_advocate_reward_redemptions_dedupe_key", + "nullsNotDistinct": false, + "columns": [ + "dedupe_key" + ] + } + }, + "policies": {}, + "checkConstraints": { + "impact_advocate_reward_redemptions_state_check": { + "name": "impact_advocate_reward_redemptions_state_check", + "value": "\"impact_advocate_reward_redemptions\".\"state\" IN ('queued', 'retrying', 'redeemed', 'failed')" + }, + "impact_advocate_reward_redemptions_attempt_count_non_negative_check": { + "name": "impact_advocate_reward_redemptions_attempt_count_non_negative_check", + "value": "\"impact_advocate_reward_redemptions\".\"attempt_count\" >= 0" + } + }, + "isRLSEnabled": false + }, + "public.impact_attribution_touches": { + "name": "impact_attribution_touches", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "pg_catalog.gen_random_uuid()" + }, + "product": { + "name": "product", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'kiloclaw'" + }, + "program_key": { + "name": "program_key", + "type": "text", + "primaryKey": false, + "notNull": false, + "default": "'kiloclaw'" + }, + "dedupe_key": { + "name": "dedupe_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "anonymous_id": { + "name": "anonymous_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "touch_type": { + "name": "touch_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "provider": { + "name": "provider", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "opaque_tracking_value": { + "name": "opaque_tracking_value", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "tracking_value_length": { + "name": "tracking_value_length", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "is_tracking_value_accepted": { + "name": "is_tracking_value_accepted", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "rs_code": { + "name": "rs_code", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "rs_share_medium": { + "name": "rs_share_medium", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "rs_engagement_medium": { + "name": "rs_engagement_medium", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "im_ref": { + "name": "im_ref", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "landing_path": { + "name": "landing_path", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "utm_source": { + "name": "utm_source", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "utm_medium": { + "name": "utm_medium", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "utm_campaign": { + "name": "utm_campaign", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "utm_term": { + "name": "utm_term", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "utm_content": { + "name": "utm_content", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "touched_at": { + "name": "touched_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "sale_attributed_at": { + "name": "sale_attributed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "IDX_impact_attribution_touches_product_user_id": { + "name": "IDX_impact_attribution_touches_product_user_id", + "columns": [ + { + "expression": "product", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_impact_attribution_touches_user_id": { + "name": "IDX_impact_attribution_touches_user_id", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_impact_attribution_touches_anonymous_id": { + "name": "IDX_impact_attribution_touches_anonymous_id", + "columns": [ + { + "expression": "anonymous_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_impact_attribution_touches_expires_at": { + "name": "IDX_impact_attribution_touches_expires_at", + "columns": [ + { + "expression": "expires_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_impact_attribution_touches_sale_attributed_at": { + "name": "IDX_impact_attribution_touches_sale_attributed_at", + "columns": [ + { + "expression": "sale_attributed_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "impact_attribution_touches_user_id_kilocode_users_id_fk": { + "name": "impact_attribution_touches_user_id_kilocode_users_id_fk", + "tableFrom": "impact_attribution_touches", + "tableTo": "kilocode_users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "UQ_impact_attribution_touches_dedupe_key": { + "name": "UQ_impact_attribution_touches_dedupe_key", + "nullsNotDistinct": false, + "columns": [ + "dedupe_key" + ] + } + }, + "policies": {}, + "checkConstraints": { + "impact_attribution_touches_product_check": { + "name": "impact_attribution_touches_product_check", + "value": "\"impact_attribution_touches\".\"product\" IN ('kiloclaw', 'kilo_pass')" + }, + "impact_attribution_touches_program_key_check": { + "name": "impact_attribution_touches_program_key_check", + "value": "\"impact_attribution_touches\".\"program_key\" IN ('kiloclaw', 'kilo_pass')" + }, + "impact_attribution_touches_touch_type_check": { + "name": "impact_attribution_touches_touch_type_check", + "value": "\"impact_attribution_touches\".\"touch_type\" IN ('affiliate', 'referral')" + }, + "impact_attribution_touches_provider_check": { + "name": "impact_attribution_touches_provider_check", + "value": "\"impact_attribution_touches\".\"provider\" IN ('impact_performance', 'impact_advocate')" + }, + "impact_attribution_touches_tracking_value_length_non_negative_check": { + "name": "impact_attribution_touches_tracking_value_length_non_negative_check", + "value": "\"impact_attribution_touches\".\"tracking_value_length\" >= 0" + } + }, + "isRLSEnabled": false + }, + "public.impact_conversion_reports": { + "name": "impact_conversion_reports", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "pg_catalog.gen_random_uuid()" + }, + "conversion_id": { + "name": "conversion_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "dedupe_key": { + "name": "dedupe_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "action_tracker_id": { + "name": "action_tracker_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "order_id": { + "name": "order_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "state": { + "name": "state", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'queued'" + }, + "request_payload": { + "name": "request_payload", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "response_payload": { + "name": "response_payload", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "response_status_code": { + "name": "response_status_code", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "attempt_count": { + "name": "attempt_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "next_retry_at": { + "name": "next_retry_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "delivered_at": { + "name": "delivered_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "IDX_impact_conversion_reports_conversion_id": { + "name": "IDX_impact_conversion_reports_conversion_id", + "columns": [ + { + "expression": "conversion_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_impact_conversion_reports_state": { + "name": "IDX_impact_conversion_reports_state", + "columns": [ + { + "expression": "state", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "impact_conversion_reports_conversion_id_impact_referral_conversions_id_fk": { + "name": "impact_conversion_reports_conversion_id_impact_referral_conversions_id_fk", + "tableFrom": "impact_conversion_reports", + "tableTo": "impact_referral_conversions", + "columnsFrom": [ + "conversion_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "UQ_impact_conversion_reports_dedupe_key": { + "name": "UQ_impact_conversion_reports_dedupe_key", + "nullsNotDistinct": false, + "columns": [ + "dedupe_key" + ] + } + }, + "policies": {}, + "checkConstraints": { + "impact_conversion_reports_state_check": { + "name": "impact_conversion_reports_state_check", + "value": "\"impact_conversion_reports\".\"state\" IN ('queued', 'retrying', 'delivered', 'failed')" + }, + "impact_conversion_reports_attempt_count_non_negative_check": { + "name": "impact_conversion_reports_attempt_count_non_negative_check", + "value": "\"impact_conversion_reports\".\"attempt_count\" >= 0" + } + }, + "isRLSEnabled": false + }, + "public.impact_referral_conversions": { + "name": "impact_referral_conversions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "pg_catalog.gen_random_uuid()" + }, + "product": { + "name": "product", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'kiloclaw'" + }, + "referee_user_id": { + "name": "referee_user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "referrer_user_id": { + "name": "referrer_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "source_touch_id": { + "name": "source_touch_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "winning_touch_type": { + "name": "winning_touch_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "payment_provider": { + "name": "payment_provider", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'credits'" + }, + "source_payment_id": { + "name": "source_payment_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "qualified": { + "name": "qualified", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "disqualification_reason": { + "name": "disqualification_reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "converted_at": { + "name": "converted_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "IDX_impact_referral_conversions_referee_user_id": { + "name": "IDX_impact_referral_conversions_referee_user_id", + "columns": [ + { + "expression": "referee_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_impact_referral_conversions_referrer_user_id": { + "name": "IDX_impact_referral_conversions_referrer_user_id", + "columns": [ + { + "expression": "referrer_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "impact_referral_conversions_referee_user_id_kilocode_users_id_fk": { + "name": "impact_referral_conversions_referee_user_id_kilocode_users_id_fk", + "tableFrom": "impact_referral_conversions", + "tableTo": "kilocode_users", + "columnsFrom": [ + "referee_user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "cascade" + }, + "impact_referral_conversions_referrer_user_id_kilocode_users_id_fk": { + "name": "impact_referral_conversions_referrer_user_id_kilocode_users_id_fk", + "tableFrom": "impact_referral_conversions", + "tableTo": "kilocode_users", + "columnsFrom": [ + "referrer_user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "cascade" + }, + "impact_referral_conversions_source_touch_id_impact_attribution_touches_id_fk": { + "name": "impact_referral_conversions_source_touch_id_impact_attribution_touches_id_fk", + "tableFrom": "impact_referral_conversions", + "tableTo": "impact_attribution_touches", + "columnsFrom": [ + "source_touch_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "UQ_impact_referral_conversions_product_payment_source": { + "name": "UQ_impact_referral_conversions_product_payment_source", + "nullsNotDistinct": false, + "columns": [ + "product", + "payment_provider", + "source_payment_id" + ] + } + }, + "policies": {}, + "checkConstraints": { + "impact_referral_conversions_product_check": { + "name": "impact_referral_conversions_product_check", + "value": "\"impact_referral_conversions\".\"product\" IN ('kiloclaw', 'kilo_pass')" + }, + "impact_referral_conversions_winning_touch_type_check": { + "name": "impact_referral_conversions_winning_touch_type_check", + "value": "\"impact_referral_conversions\".\"winning_touch_type\" IN ('referral', 'affiliate', 'none')" + }, + "impact_referral_conversions_payment_provider_check": { + "name": "impact_referral_conversions_payment_provider_check", + "value": "\"impact_referral_conversions\".\"payment_provider\" IN ('stripe', 'credits', 'app_store', 'google_play')" + } + }, + "isRLSEnabled": false + }, + "public.impact_referral_reward_applications": { + "name": "impact_referral_reward_applications", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "pg_catalog.gen_random_uuid()" + }, + "product": { + "name": "product", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'kiloclaw'" + }, + "reward_id": { + "name": "reward_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "beneficiary_user_id": { + "name": "beneficiary_user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "subscription_id": { + "name": "subscription_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "previous_renewal_boundary": { + "name": "previous_renewal_boundary", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "new_renewal_boundary": { + "name": "new_renewal_boundary", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "local_operation_id": { + "name": "local_operation_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "stripe_operation_id": { + "name": "stripe_operation_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "stripe_idempotency_key": { + "name": "stripe_idempotency_key", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "applied_at": { + "name": "applied_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "IDX_impact_referral_reward_applications_reward_id": { + "name": "IDX_impact_referral_reward_applications_reward_id", + "columns": [ + { + "expression": "reward_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_impact_referral_reward_applications_beneficiary_user_id": { + "name": "IDX_impact_referral_reward_applications_beneficiary_user_id", + "columns": [ + { + "expression": "beneficiary_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "impact_referral_reward_applications_reward_id_impact_referral_rewards_id_fk": { + "name": "impact_referral_reward_applications_reward_id_impact_referral_rewards_id_fk", + "tableFrom": "impact_referral_reward_applications", + "tableTo": "impact_referral_rewards", + "columnsFrom": [ + "reward_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "cascade" + }, + "impact_referral_reward_applications_beneficiary_user_id_kilocode_users_id_fk": { + "name": "impact_referral_reward_applications_beneficiary_user_id_kilocode_users_id_fk", + "tableFrom": "impact_referral_reward_applications", + "tableTo": "kilocode_users", + "columnsFrom": [ + "beneficiary_user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "impact_referral_reward_applications_product_check": { + "name": "impact_referral_reward_applications_product_check", + "value": "\"impact_referral_reward_applications\".\"product\" IN ('kiloclaw', 'kilo_pass')" + } + }, + "isRLSEnabled": false + }, + "public.impact_referral_reward_decisions": { + "name": "impact_referral_reward_decisions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "pg_catalog.gen_random_uuid()" + }, + "product": { + "name": "product", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'kiloclaw'" + }, + "conversion_id": { + "name": "conversion_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "beneficiary_user_id": { + "name": "beneficiary_user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "beneficiary_role": { + "name": "beneficiary_role", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "outcome": { + "name": "outcome", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "reason": { + "name": "reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "reward_kind": { + "name": "reward_kind", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'kiloclaw_free_month'" + }, + "months_granted": { + "name": "months_granted", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "reward_percent": { + "name": "reward_percent", + "type": "numeric(6, 4)", + "primaryKey": false, + "notNull": false + }, + "source_tier": { + "name": "source_tier", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "reward_amount_usd": { + "name": "reward_amount_usd", + "type": "numeric(12, 2)", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "IDX_impact_referral_reward_decisions_beneficiary_user_id": { + "name": "IDX_impact_referral_reward_decisions_beneficiary_user_id", + "columns": [ + { + "expression": "beneficiary_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "impact_referral_reward_decisions_conversion_id_impact_referral_conversions_id_fk": { + "name": "impact_referral_reward_decisions_conversion_id_impact_referral_conversions_id_fk", + "tableFrom": "impact_referral_reward_decisions", + "tableTo": "impact_referral_conversions", + "columnsFrom": [ + "conversion_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "cascade" + }, + "impact_referral_reward_decisions_beneficiary_user_id_kilocode_users_id_fk": { + "name": "impact_referral_reward_decisions_beneficiary_user_id_kilocode_users_id_fk", + "tableFrom": "impact_referral_reward_decisions", + "tableTo": "kilocode_users", + "columnsFrom": [ + "beneficiary_user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "UQ_impact_referral_reward_decisions_conversion_role": { + "name": "UQ_impact_referral_reward_decisions_conversion_role", + "nullsNotDistinct": false, + "columns": [ + "conversion_id", + "beneficiary_role" + ] + } + }, + "policies": {}, + "checkConstraints": { + "impact_referral_reward_decisions_product_check": { + "name": "impact_referral_reward_decisions_product_check", + "value": "\"impact_referral_reward_decisions\".\"product\" IN ('kiloclaw', 'kilo_pass')" + }, + "impact_referral_reward_decisions_beneficiary_role_check": { + "name": "impact_referral_reward_decisions_beneficiary_role_check", + "value": "\"impact_referral_reward_decisions\".\"beneficiary_role\" IN ('referrer', 'referee')" + }, + "impact_referral_reward_decisions_outcome_check": { + "name": "impact_referral_reward_decisions_outcome_check", + "value": "\"impact_referral_reward_decisions\".\"outcome\" IN ('granted', 'cap_limited', 'disqualified')" + }, + "impact_referral_reward_decisions_reward_kind_check": { + "name": "impact_referral_reward_decisions_reward_kind_check", + "value": "\"impact_referral_reward_decisions\".\"reward_kind\" IN ('kiloclaw_free_month', 'kilo_pass_bonus')" + }, + "impact_referral_reward_decisions_months_granted_non_negative_check": { + "name": "impact_referral_reward_decisions_months_granted_non_negative_check", + "value": "\"impact_referral_reward_decisions\".\"months_granted\" >= 0" + } + }, + "isRLSEnabled": false + }, + "public.impact_referral_rewards": { + "name": "impact_referral_rewards", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "pg_catalog.gen_random_uuid()" + }, + "product": { + "name": "product", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'kiloclaw'" + }, + "conversion_id": { + "name": "conversion_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "decision_id": { + "name": "decision_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "beneficiary_user_id": { + "name": "beneficiary_user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "beneficiary_role": { + "name": "beneficiary_role", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "reward_kind": { + "name": "reward_kind", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'kiloclaw_free_month'" + }, + "months_granted": { + "name": "months_granted", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 1 + }, + "reward_percent": { + "name": "reward_percent", + "type": "numeric(6, 4)", + "primaryKey": false, + "notNull": false + }, + "source_tier": { + "name": "source_tier", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "reward_amount_usd": { + "name": "reward_amount_usd", + "type": "numeric(12, 2)", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "applies_to_subscription_id": { + "name": "applies_to_subscription_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "applies_to_kilo_pass_subscription_id": { + "name": "applies_to_kilo_pass_subscription_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "consumed_kilo_pass_issuance_id": { + "name": "consumed_kilo_pass_issuance_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "consumed_kilo_pass_issuance_item_id": { + "name": "consumed_kilo_pass_issuance_item_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "earned_at": { + "name": "earned_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "applied_at": { + "name": "applied_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "reversed_at": { + "name": "reversed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "review_reason": { + "name": "review_reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "IDX_impact_referral_rewards_beneficiary_user_id": { + "name": "IDX_impact_referral_rewards_beneficiary_user_id", + "columns": [ + { + "expression": "beneficiary_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_impact_referral_rewards_status": { + "name": "IDX_impact_referral_rewards_status", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "impact_referral_rewards_conversion_id_impact_referral_conversions_id_fk": { + "name": "impact_referral_rewards_conversion_id_impact_referral_conversions_id_fk", + "tableFrom": "impact_referral_rewards", + "tableTo": "impact_referral_conversions", + "columnsFrom": [ + "conversion_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "cascade" + }, + "impact_referral_rewards_decision_id_impact_referral_reward_decisions_id_fk": { + "name": "impact_referral_rewards_decision_id_impact_referral_reward_decisions_id_fk", + "tableFrom": "impact_referral_rewards", + "tableTo": "impact_referral_reward_decisions", + "columnsFrom": [ + "decision_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "cascade" + }, + "impact_referral_rewards_beneficiary_user_id_kilocode_users_id_fk": { + "name": "impact_referral_rewards_beneficiary_user_id_kilocode_users_id_fk", + "tableFrom": "impact_referral_rewards", + "tableTo": "kilocode_users", + "columnsFrom": [ + "beneficiary_user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "cascade" + }, + "FK_impact_referral_rewards_kilo_pass_subscription": { + "name": "FK_impact_referral_rewards_kilo_pass_subscription", + "tableFrom": "impact_referral_rewards", + "tableTo": "kilo_pass_subscriptions", + "columnsFrom": [ + "applies_to_kilo_pass_subscription_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "cascade" + }, + "FK_impact_referral_rewards_kilo_pass_issuance": { + "name": "FK_impact_referral_rewards_kilo_pass_issuance", + "tableFrom": "impact_referral_rewards", + "tableTo": "kilo_pass_issuances", + "columnsFrom": [ + "consumed_kilo_pass_issuance_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "cascade" + }, + "FK_impact_referral_rewards_kilo_pass_issuance_item": { + "name": "FK_impact_referral_rewards_kilo_pass_issuance_item", + "tableFrom": "impact_referral_rewards", + "tableTo": "kilo_pass_issuance_items", + "columnsFrom": [ + "consumed_kilo_pass_issuance_item_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "UQ_impact_referral_rewards_conversion_role": { + "name": "UQ_impact_referral_rewards_conversion_role", + "nullsNotDistinct": false, + "columns": [ + "conversion_id", + "beneficiary_role" + ] + }, + "UQ_impact_referral_rewards_decision_id": { + "name": "UQ_impact_referral_rewards_decision_id", + "nullsNotDistinct": false, + "columns": [ + "decision_id" + ] + } + }, + "policies": {}, + "checkConstraints": { + "impact_referral_rewards_product_check": { + "name": "impact_referral_rewards_product_check", + "value": "\"impact_referral_rewards\".\"product\" IN ('kiloclaw', 'kilo_pass')" + }, + "impact_referral_rewards_beneficiary_role_check": { + "name": "impact_referral_rewards_beneficiary_role_check", + "value": "\"impact_referral_rewards\".\"beneficiary_role\" IN ('referrer', 'referee')" + }, + "impact_referral_rewards_reward_kind_check": { + "name": "impact_referral_rewards_reward_kind_check", + "value": "\"impact_referral_rewards\".\"reward_kind\" IN ('kiloclaw_free_month', 'kilo_pass_bonus')" + }, + "impact_referral_rewards_status_check": { + "name": "impact_referral_rewards_status_check", + "value": "\"impact_referral_rewards\".\"status\" IN ('pending', 'earned', 'applied', 'reversed', 'expired', 'canceled', 'review_required')" + }, + "impact_referral_rewards_months_granted_non_negative_check": { + "name": "impact_referral_rewards_months_granted_non_negative_check", + "value": "\"impact_referral_rewards\".\"months_granted\" >= 0" + } + }, + "isRLSEnabled": false + }, + "public.impact_referrals": { + "name": "impact_referrals", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "pg_catalog.gen_random_uuid()" + }, + "product": { + "name": "product", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'kiloclaw'" + }, + "referee_user_id": { + "name": "referee_user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "referrer_user_id": { + "name": "referrer_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "source_touch_id": { + "name": "source_touch_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "impact_referral_id": { + "name": "impact_referral_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "IDX_impact_referrals_referrer_user_id": { + "name": "IDX_impact_referrals_referrer_user_id", + "columns": [ + { + "expression": "referrer_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_impact_referrals_source_touch_id": { + "name": "IDX_impact_referrals_source_touch_id", + "columns": [ + { + "expression": "source_touch_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "impact_referrals_referee_user_id_kilocode_users_id_fk": { + "name": "impact_referrals_referee_user_id_kilocode_users_id_fk", + "tableFrom": "impact_referrals", + "tableTo": "kilocode_users", + "columnsFrom": [ + "referee_user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "cascade" + }, + "impact_referrals_referrer_user_id_kilocode_users_id_fk": { + "name": "impact_referrals_referrer_user_id_kilocode_users_id_fk", + "tableFrom": "impact_referrals", + "tableTo": "kilocode_users", + "columnsFrom": [ + "referrer_user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "cascade" + }, + "impact_referrals_source_touch_id_impact_attribution_touches_id_fk": { + "name": "impact_referrals_source_touch_id_impact_attribution_touches_id_fk", + "tableFrom": "impact_referrals", + "tableTo": "impact_attribution_touches", + "columnsFrom": [ + "source_touch_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "UQ_impact_referrals_product_referee_user_id": { + "name": "UQ_impact_referrals_product_referee_user_id", + "nullsNotDistinct": false, + "columns": [ + "product", + "referee_user_id" + ] + } + }, + "policies": {}, + "checkConstraints": { + "impact_referrals_product_check": { + "name": "impact_referrals_product_check", + "value": "\"impact_referrals\".\"product\" IN ('kiloclaw', 'kilo_pass')" + } + }, + "isRLSEnabled": false + }, + "public.ja4_digest": { + "name": "ja4_digest", + "schema": "", + "columns": { + "ja4_digest_id": { + "name": "ja4_digest_id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "ja4_digest": { + "name": "ja4_digest", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "UQ_ja4_digest": { + "name": "UQ_ja4_digest", + "columns": [ + { + "expression": "ja4_digest", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.kilo_pass_audit_log": { + "name": "kilo_pass_audit_log", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "pg_catalog.gen_random_uuid()" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "kilo_user_id": { + "name": "kilo_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "kilo_pass_subscription_id": { + "name": "kilo_pass_subscription_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "action": { + "name": "action", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "result": { + "name": "result", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "idempotency_key": { + "name": "idempotency_key", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "stripe_event_id": { + "name": "stripe_event_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "stripe_invoice_id": { + "name": "stripe_invoice_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "stripe_subscription_id": { + "name": "stripe_subscription_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "related_credit_transaction_id": { + "name": "related_credit_transaction_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "related_monthly_issuance_id": { + "name": "related_monthly_issuance_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "payload_json": { + "name": "payload_json", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + } + }, + "indexes": { + "IDX_kilo_pass_audit_log_created_at": { + "name": "IDX_kilo_pass_audit_log_created_at", + "columns": [ + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_kilo_pass_audit_log_kilo_user_id": { + "name": "IDX_kilo_pass_audit_log_kilo_user_id", + "columns": [ + { + "expression": "kilo_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_kilo_pass_audit_log_kilo_pass_subscription_id": { + "name": "IDX_kilo_pass_audit_log_kilo_pass_subscription_id", + "columns": [ + { + "expression": "kilo_pass_subscription_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_kilo_pass_audit_log_action": { + "name": "IDX_kilo_pass_audit_log_action", + "columns": [ + { + "expression": "action", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_kilo_pass_audit_log_result": { + "name": "IDX_kilo_pass_audit_log_result", + "columns": [ + { + "expression": "result", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_kilo_pass_audit_log_idempotency_key": { + "name": "IDX_kilo_pass_audit_log_idempotency_key", + "columns": [ + { + "expression": "idempotency_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_kilo_pass_audit_log_stripe_event_id": { + "name": "IDX_kilo_pass_audit_log_stripe_event_id", + "columns": [ + { + "expression": "stripe_event_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_kilo_pass_audit_log_stripe_invoice_id": { + "name": "IDX_kilo_pass_audit_log_stripe_invoice_id", + "columns": [ + { + "expression": "stripe_invoice_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_kilo_pass_audit_log_stripe_subscription_id": { + "name": "IDX_kilo_pass_audit_log_stripe_subscription_id", + "columns": [ + { + "expression": "stripe_subscription_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_kilo_pass_audit_log_related_credit_transaction_id": { + "name": "IDX_kilo_pass_audit_log_related_credit_transaction_id", + "columns": [ + { + "expression": "related_credit_transaction_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_kilo_pass_audit_log_related_monthly_issuance_id": { + "name": "IDX_kilo_pass_audit_log_related_monthly_issuance_id", + "columns": [ + { + "expression": "related_monthly_issuance_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "kilo_pass_audit_log_kilo_user_id_kilocode_users_id_fk": { + "name": "kilo_pass_audit_log_kilo_user_id_kilocode_users_id_fk", + "tableFrom": "kilo_pass_audit_log", + "tableTo": "kilocode_users", + "columnsFrom": [ + "kilo_user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "cascade" + }, + "kilo_pass_audit_log_kilo_pass_subscription_id_kilo_pass_subscriptions_id_fk": { + "name": "kilo_pass_audit_log_kilo_pass_subscription_id_kilo_pass_subscriptions_id_fk", + "tableFrom": "kilo_pass_audit_log", + "tableTo": "kilo_pass_subscriptions", + "columnsFrom": [ + "kilo_pass_subscription_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "cascade" + }, + "kilo_pass_audit_log_related_credit_transaction_id_credit_transactions_id_fk": { + "name": "kilo_pass_audit_log_related_credit_transaction_id_credit_transactions_id_fk", + "tableFrom": "kilo_pass_audit_log", + "tableTo": "credit_transactions", + "columnsFrom": [ + "related_credit_transaction_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "cascade" + }, + "kilo_pass_audit_log_related_monthly_issuance_id_kilo_pass_issuances_id_fk": { + "name": "kilo_pass_audit_log_related_monthly_issuance_id_kilo_pass_issuances_id_fk", + "tableFrom": "kilo_pass_audit_log", + "tableTo": "kilo_pass_issuances", + "columnsFrom": [ + "related_monthly_issuance_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "kilo_pass_audit_log_action_check": { + "name": "kilo_pass_audit_log_action_check", + "value": "\"kilo_pass_audit_log\".\"action\" IN ('stripe_webhook_received', 'kilo_pass_invoice_paid_handled', 'store_purchase_completed', 'store_notification_received', 'store_subscription_renewed', 'store_subscription_canceled', 'store_subscription_expired', 'store_subscription_refunded', 'base_credits_issued', 'bonus_credits_issued', 'bonus_credits_skipped_idempotent', 'first_month_50pct_promo_issued', 'yearly_monthly_base_cron_started', 'yearly_monthly_base_cron_completed', 'issue_yearly_remaining_credits', 'duplicate_card_subscription_canceled', 'yearly_monthly_bonus_cron_started', 'yearly_monthly_bonus_cron_completed')" + }, + "kilo_pass_audit_log_result_check": { + "name": "kilo_pass_audit_log_result_check", + "value": "\"kilo_pass_audit_log\".\"result\" IN ('success', 'skipped_idempotent', 'failed')" + } + }, + "isRLSEnabled": false + }, + "public.kilo_pass_issuance_items": { + "name": "kilo_pass_issuance_items", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "pg_catalog.gen_random_uuid()" + }, + "kilo_pass_issuance_id": { + "name": "kilo_pass_issuance_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "kind": { + "name": "kind", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "credit_transaction_id": { + "name": "credit_transaction_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "amount_usd": { + "name": "amount_usd", + "type": "numeric(12, 2)", + "primaryKey": false, + "notNull": true + }, + "bonus_percent_applied": { + "name": "bonus_percent_applied", + "type": "numeric(6, 4)", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "IDX_kilo_pass_issuance_items_issuance_id": { + "name": "IDX_kilo_pass_issuance_items_issuance_id", + "columns": [ + { + "expression": "kilo_pass_issuance_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_kilo_pass_issuance_items_credit_transaction_id": { + "name": "IDX_kilo_pass_issuance_items_credit_transaction_id", + "columns": [ + { + "expression": "credit_transaction_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "kilo_pass_issuance_items_kilo_pass_issuance_id_kilo_pass_issuances_id_fk": { + "name": "kilo_pass_issuance_items_kilo_pass_issuance_id_kilo_pass_issuances_id_fk", + "tableFrom": "kilo_pass_issuance_items", + "tableTo": "kilo_pass_issuances", + "columnsFrom": [ + "kilo_pass_issuance_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "cascade" + }, + "kilo_pass_issuance_items_credit_transaction_id_credit_transactions_id_fk": { + "name": "kilo_pass_issuance_items_credit_transaction_id_credit_transactions_id_fk", + "tableFrom": "kilo_pass_issuance_items", + "tableTo": "credit_transactions", + "columnsFrom": [ + "credit_transaction_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "restrict", + "onUpdate": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "kilo_pass_issuance_items_credit_transaction_id_unique": { + "name": "kilo_pass_issuance_items_credit_transaction_id_unique", + "nullsNotDistinct": false, + "columns": [ + "credit_transaction_id" + ] + }, + "UQ_kilo_pass_issuance_items_issuance_kind": { + "name": "UQ_kilo_pass_issuance_items_issuance_kind", + "nullsNotDistinct": false, + "columns": [ + "kilo_pass_issuance_id", + "kind" + ] + } + }, + "policies": {}, + "checkConstraints": { + "kilo_pass_issuance_items_bonus_percent_applied_range_check": { + "name": "kilo_pass_issuance_items_bonus_percent_applied_range_check", + "value": "\"kilo_pass_issuance_items\".\"bonus_percent_applied\" IS NULL OR (\"kilo_pass_issuance_items\".\"bonus_percent_applied\" >= 0 AND \"kilo_pass_issuance_items\".\"bonus_percent_applied\" <= 1)" + }, + "kilo_pass_issuance_items_amount_usd_non_negative_check": { + "name": "kilo_pass_issuance_items_amount_usd_non_negative_check", + "value": "\"kilo_pass_issuance_items\".\"amount_usd\" >= 0" + }, + "kilo_pass_issuance_items_kind_check": { + "name": "kilo_pass_issuance_items_kind_check", + "value": "\"kilo_pass_issuance_items\".\"kind\" IN ('base', 'bonus', 'promo_first_month_50pct', 'referral_bonus')" + } + }, + "isRLSEnabled": false + }, + "public.kilo_pass_issuances": { + "name": "kilo_pass_issuances", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "pg_catalog.gen_random_uuid()" + }, + "kilo_pass_subscription_id": { + "name": "kilo_pass_subscription_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "issue_month": { + "name": "issue_month", + "type": "date", + "primaryKey": false, + "notNull": true + }, + "source": { + "name": "source", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "stripe_invoice_id": { + "name": "stripe_invoice_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "initial_welcome_promo_eligibility_reason": { + "name": "initial_welcome_promo_eligibility_reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "UQ_kilo_pass_issuances_stripe_invoice_id": { + "name": "UQ_kilo_pass_issuances_stripe_invoice_id", + "columns": [ + { + "expression": "stripe_invoice_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"kilo_pass_issuances\".\"stripe_invoice_id\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_kilo_pass_issuances_subscription_id": { + "name": "IDX_kilo_pass_issuances_subscription_id", + "columns": [ + { + "expression": "kilo_pass_subscription_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_kilo_pass_issuances_issue_month": { + "name": "IDX_kilo_pass_issuances_issue_month", + "columns": [ + { + "expression": "issue_month", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "kilo_pass_issuances_kilo_pass_subscription_id_kilo_pass_subscriptions_id_fk": { + "name": "kilo_pass_issuances_kilo_pass_subscription_id_kilo_pass_subscriptions_id_fk", + "tableFrom": "kilo_pass_issuances", + "tableTo": "kilo_pass_subscriptions", + "columnsFrom": [ + "kilo_pass_subscription_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "UQ_kilo_pass_issuances_subscription_issue_month": { + "name": "UQ_kilo_pass_issuances_subscription_issue_month", + "nullsNotDistinct": false, + "columns": [ + "kilo_pass_subscription_id", + "issue_month" + ] + } + }, + "policies": {}, + "checkConstraints": { + "kilo_pass_issuances_issue_month_day_one_check": { + "name": "kilo_pass_issuances_issue_month_day_one_check", + "value": "EXTRACT(DAY FROM \"kilo_pass_issuances\".\"issue_month\") = 1" + }, + "kilo_pass_issuances_source_check": { + "name": "kilo_pass_issuances_source_check", + "value": "\"kilo_pass_issuances\".\"source\" IN ('stripe_invoice', 'app_store_transaction', 'google_play_transaction', 'cron')" + }, + "kilo_pass_issuances_initial_welcome_promo_reason_check": { + "name": "kilo_pass_issuances_initial_welcome_promo_reason_check", + "value": "\"kilo_pass_issuances\".\"initial_welcome_promo_eligibility_reason\" IN ('first_payment_fingerprint_claim', 'fingerprint_previously_claimed', 'missing_fingerprint', 'no_supported_fingerprint', 'no_positive_settlement', 'settlement_unresolved')" + } + }, + "isRLSEnabled": false + }, + "public.kilo_pass_org_agreements": { + "name": "kilo_pass_org_agreements", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "pg_catalog.gen_random_uuid()" + }, + "parent_organization_id": { + "name": "parent_organization_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "term_version_id": { + "name": "term_version_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "state": { + "name": "state", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "processing_condition": { + "name": "processing_condition", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'ready'" + }, + "purchase_channel": { + "name": "purchase_channel", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "cadence": { + "name": "cadence", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "purchased_pass_capacity": { + "name": "purchased_pass_capacity", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "next_purchased_pass_capacity": { + "name": "next_purchased_pass_capacity", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "next_capacity_effective_at": { + "name": "next_capacity_effective_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "paid_from": { + "name": "paid_from", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "paid_until": { + "name": "paid_until", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "issuance_anchor_at": { + "name": "issuance_anchor_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "provider_subscription_id": { + "name": "provider_subscription_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "provider_seat_add_on_item_id": { + "name": "provider_seat_add_on_item_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "activation_provider_event_id": { + "name": "activation_provider_event_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "external_contract_id": { + "name": "external_contract_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "payment_review_required_at": { + "name": "payment_review_required_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "cancellation_effective_at": { + "name": "cancellation_effective_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "manually_issued_through": { + "name": "manually_issued_through", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "UQ_kilo_pass_org_agreements_one_non_ended_parent": { + "name": "UQ_kilo_pass_org_agreements_one_non_ended_parent", + "columns": [ + { + "expression": "parent_organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"kilo_pass_org_agreements\".\"state\" <> 'ended'", + "concurrently": false, + "method": "btree", + "with": {} + }, + "UQ_kilo_pass_org_agreements_provider_subscription": { + "name": "UQ_kilo_pass_org_agreements_provider_subscription", + "columns": [ + { + "expression": "provider_subscription_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"kilo_pass_org_agreements\".\"provider_subscription_id\" IS NOT NULL AND \"kilo_pass_org_agreements\".\"state\" <> 'ended'", + "concurrently": false, + "method": "btree", + "with": {} + }, + "UQ_kilo_pass_org_agreements_provider_seat_add_on_item": { + "name": "UQ_kilo_pass_org_agreements_provider_seat_add_on_item", + "columns": [ + { + "expression": "provider_seat_add_on_item_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"kilo_pass_org_agreements\".\"provider_seat_add_on_item_id\" IS NOT NULL AND \"kilo_pass_org_agreements\".\"state\" <> 'ended'", + "concurrently": false, + "method": "btree", + "with": {} + }, + "UQ_kilo_pass_org_agreements_external_contract": { + "name": "UQ_kilo_pass_org_agreements_external_contract", + "columns": [ + { + "expression": "external_contract_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"kilo_pass_org_agreements\".\"external_contract_id\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "UQ_kilo_pass_org_agreements_activation_provider_event": { + "name": "UQ_kilo_pass_org_agreements_activation_provider_event", + "columns": [ + { + "expression": "activation_provider_event_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"kilo_pass_org_agreements\".\"activation_provider_event_id\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_kilo_pass_org_agreements_processing": { + "name": "IDX_kilo_pass_org_agreements_processing", + "columns": [ + { + "expression": "processing_condition", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "kilo_pass_org_agreements_parent_organization_id_organizations_id_fk": { + "name": "kilo_pass_org_agreements_parent_organization_id_organizations_id_fk", + "tableFrom": "kilo_pass_org_agreements", + "tableTo": "organizations", + "columnsFrom": [ + "parent_organization_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "restrict", + "onUpdate": "cascade" + }, + "kilo_pass_org_agreements_term_version_id_kilo_pass_org_term_versions_id_fk": { + "name": "kilo_pass_org_agreements_term_version_id_kilo_pass_org_term_versions_id_fk", + "tableFrom": "kilo_pass_org_agreements", + "tableTo": "kilo_pass_org_term_versions", + "columnsFrom": [ + "term_version_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "restrict", + "onUpdate": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "kilo_pass_org_agreements_purchased_capacity_non_negative_check": { + "name": "kilo_pass_org_agreements_purchased_capacity_non_negative_check", + "value": "\"kilo_pass_org_agreements\".\"purchased_pass_capacity\" >= 0" + }, + "kilo_pass_org_agreements_next_capacity_check": { + "name": "kilo_pass_org_agreements_next_capacity_check", + "value": "(\"kilo_pass_org_agreements\".\"next_purchased_pass_capacity\" IS NULL AND \"kilo_pass_org_agreements\".\"next_capacity_effective_at\" IS NULL) OR (\"kilo_pass_org_agreements\".\"next_purchased_pass_capacity\" >= 0 AND \"kilo_pass_org_agreements\".\"next_capacity_effective_at\" IS NOT NULL)" + }, + "kilo_pass_org_agreements_paid_interval_check": { + "name": "kilo_pass_org_agreements_paid_interval_check", + "value": "(\"kilo_pass_org_agreements\".\"paid_from\" IS NULL AND \"kilo_pass_org_agreements\".\"paid_until\" IS NULL) OR (\"kilo_pass_org_agreements\".\"paid_from\" IS NOT NULL AND \"kilo_pass_org_agreements\".\"paid_until\" IS NOT NULL AND \"kilo_pass_org_agreements\".\"paid_from\" < \"kilo_pass_org_agreements\".\"paid_until\")" + }, + "kilo_pass_org_agreements_state_check": { + "name": "kilo_pass_org_agreements_state_check", + "value": "\"kilo_pass_org_agreements\".\"state\" IN ('pending_payment', 'active', 'cancel_at_period_end', 'ended')" + }, + "kilo_pass_org_agreements_processing_condition_check": { + "name": "kilo_pass_org_agreements_processing_condition_check", + "value": "\"kilo_pass_org_agreements\".\"processing_condition\" IN ('ready', 'manual', 'blocked', 'overallocated', 'failed', 'suspended_for_review')" + }, + "kilo_pass_org_agreements_purchase_channel_check": { + "name": "kilo_pass_org_agreements_purchase_channel_check", + "value": "\"kilo_pass_org_agreements\".\"purchase_channel\" IN ('self_serve', 'manual')" + }, + "kilo_pass_org_agreements_cadence_check": { + "name": "kilo_pass_org_agreements_cadence_check", + "value": "\"kilo_pass_org_agreements\".\"cadence\" IN ('monthly', 'yearly')" + } + }, + "isRLSEnabled": false + }, + "public.kilo_pass_org_allocation_plan_rows": { + "name": "kilo_pass_org_allocation_plan_rows", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "pg_catalog.gen_random_uuid()" + }, + "allocation_plan_id": { + "name": "allocation_plan_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "allocation_container_organization_id": { + "name": "allocation_container_organization_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "pass_capacity": { + "name": "pass_capacity", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "IDX_kilo_pass_org_allocation_plan_rows_positive_container": { + "name": "IDX_kilo_pass_org_allocation_plan_rows_positive_container", + "columns": [ + { + "expression": "allocation_container_organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"kilo_pass_org_allocation_plan_rows\".\"pass_capacity\" > 0", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "kilo_pass_org_allocation_plan_rows_allocation_plan_id_kilo_pass_org_allocation_plans_id_fk": { + "name": "kilo_pass_org_allocation_plan_rows_allocation_plan_id_kilo_pass_org_allocation_plans_id_fk", + "tableFrom": "kilo_pass_org_allocation_plan_rows", + "tableTo": "kilo_pass_org_allocation_plans", + "columnsFrom": [ + "allocation_plan_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "cascade" + }, + "kilo_pass_org_allocation_plan_rows_allocation_container_organization_id_organizations_id_fk": { + "name": "kilo_pass_org_allocation_plan_rows_allocation_container_organization_id_organizations_id_fk", + "tableFrom": "kilo_pass_org_allocation_plan_rows", + "tableTo": "organizations", + "columnsFrom": [ + "allocation_container_organization_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "restrict", + "onUpdate": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "UQ_kilo_pass_org_allocation_plan_rows_plan_container": { + "name": "UQ_kilo_pass_org_allocation_plan_rows_plan_container", + "nullsNotDistinct": false, + "columns": [ + "allocation_plan_id", + "allocation_container_organization_id" + ] + } + }, + "policies": {}, + "checkConstraints": { + "kilo_pass_org_allocation_plan_rows_capacity_non_negative_check": { + "name": "kilo_pass_org_allocation_plan_rows_capacity_non_negative_check", + "value": "\"kilo_pass_org_allocation_plan_rows\".\"pass_capacity\" >= 0" + } + }, + "isRLSEnabled": false + }, + "public.kilo_pass_org_allocation_plans": { + "name": "kilo_pass_org_allocation_plans", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "pg_catalog.gen_random_uuid()" + }, + "agreement_id": { + "name": "agreement_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "effective_window_start": { + "name": "effective_window_start", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "version": { + "name": "version", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "created_by_kilo_user_id": { + "name": "created_by_kilo_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "kilo_pass_org_allocation_plans_agreement_id_kilo_pass_org_agreements_id_fk": { + "name": "kilo_pass_org_allocation_plans_agreement_id_kilo_pass_org_agreements_id_fk", + "tableFrom": "kilo_pass_org_allocation_plans", + "tableTo": "kilo_pass_org_agreements", + "columnsFrom": [ + "agreement_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "cascade" + }, + "kilo_pass_org_allocation_plans_created_by_kilo_user_id_kilocode_users_id_fk": { + "name": "kilo_pass_org_allocation_plans_created_by_kilo_user_id_kilocode_users_id_fk", + "tableFrom": "kilo_pass_org_allocation_plans", + "tableTo": "kilocode_users", + "columnsFrom": [ + "created_by_kilo_user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "UQ_kilo_pass_org_allocation_plans_agreement_window": { + "name": "UQ_kilo_pass_org_allocation_plans_agreement_window", + "nullsNotDistinct": false, + "columns": [ + "agreement_id", + "effective_window_start" + ] + }, + "UQ_kilo_pass_org_allocation_plans_agreement_version": { + "name": "UQ_kilo_pass_org_allocation_plans_agreement_version", + "nullsNotDistinct": false, + "columns": [ + "agreement_id", + "version" + ] + } + }, + "policies": {}, + "checkConstraints": { + "kilo_pass_org_allocation_plans_version_positive_check": { + "name": "kilo_pass_org_allocation_plans_version_positive_check", + "value": "\"kilo_pass_org_allocation_plans\".\"version\" > 0" + } + }, + "isRLSEnabled": false + }, + "public.kilo_pass_org_audit_records": { + "name": "kilo_pass_org_audit_records", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "pg_catalog.gen_random_uuid()" + }, + "agreement_id": { + "name": "agreement_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "actor_kilo_user_id": { + "name": "actor_kilo_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "action": { + "name": "action", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "reason": { + "name": "reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "before_json": { + "name": "before_json", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "after_json": { + "name": "after_json", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "idempotency_key": { + "name": "idempotency_key", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "UQ_kilo_pass_org_audit_records_idempotency": { + "name": "UQ_kilo_pass_org_audit_records_idempotency", + "columns": [ + { + "expression": "idempotency_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"kilo_pass_org_audit_records\".\"idempotency_key\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_kilo_pass_org_audit_records_agreement_created": { + "name": "IDX_kilo_pass_org_audit_records_agreement_created", + "columns": [ + { + "expression": "agreement_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "kilo_pass_org_audit_records_agreement_id_kilo_pass_org_agreements_id_fk": { + "name": "kilo_pass_org_audit_records_agreement_id_kilo_pass_org_agreements_id_fk", + "tableFrom": "kilo_pass_org_audit_records", + "tableTo": "kilo_pass_org_agreements", + "columnsFrom": [ + "agreement_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "cascade" + }, + "kilo_pass_org_audit_records_actor_kilo_user_id_kilocode_users_id_fk": { + "name": "kilo_pass_org_audit_records_actor_kilo_user_id_kilocode_users_id_fk", + "tableFrom": "kilo_pass_org_audit_records", + "tableTo": "kilocode_users", + "columnsFrom": [ + "actor_kilo_user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.kilo_pass_org_issuance_snapshots": { + "name": "kilo_pass_org_issuance_snapshots", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "pg_catalog.gen_random_uuid()" + }, + "agreement_id": { + "name": "agreement_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "processing_run_id": { + "name": "processing_run_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "allocation_plan_id": { + "name": "allocation_plan_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "term_version_id": { + "name": "term_version_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "allocation_container_organization_id": { + "name": "allocation_container_organization_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "window_start": { + "name": "window_start", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "window_end": { + "name": "window_end", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "qualifying_spend_starts_at": { + "name": "qualifying_spend_starts_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "kind": { + "name": "kind", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "tranche_key": { + "name": "tranche_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "allocated_pass_capacity": { + "name": "allocated_pass_capacity", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "base_credit_microdollars": { + "name": "base_credit_microdollars", + "type": "bigint", + "primaryKey": false, + "notNull": true + }, + "bonus_credit_microdollars": { + "name": "bonus_credit_microdollars", + "type": "bigint", + "primaryKey": false, + "notNull": true + }, + "unlock_spend_microdollars": { + "name": "unlock_spend_microdollars", + "type": "bigint", + "primaryKey": false, + "notNull": true + }, + "qualifying_spend_microdollars": { + "name": "qualifying_spend_microdollars", + "type": "bigint", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "bonus_mode": { + "name": "bonus_mode", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "bonus_unlocked_at": { + "name": "bonus_unlocked_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "repair_completed_at": { + "name": "repair_completed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "bonus_credit_transaction_id": { + "name": "bonus_credit_transaction_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "base_credit_transaction_id": { + "name": "base_credit_transaction_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "UQ_kilo_pass_org_issuance_snapshots_base_credit_transaction": { + "name": "UQ_kilo_pass_org_issuance_snapshots_base_credit_transaction", + "columns": [ + { + "expression": "base_credit_transaction_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"kilo_pass_org_issuance_snapshots\".\"base_credit_transaction_id\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "UQ_kilo_pass_org_issuance_snapshots_bonus_credit_transaction": { + "name": "UQ_kilo_pass_org_issuance_snapshots_bonus_credit_transaction", + "columns": [ + { + "expression": "bonus_credit_transaction_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"kilo_pass_org_issuance_snapshots\".\"bonus_credit_transaction_id\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_kilo_pass_org_issuance_snapshots_window": { + "name": "IDX_kilo_pass_org_issuance_snapshots_window", + "columns": [ + { + "expression": "agreement_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "window_start", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "kilo_pass_org_issuance_snapshots_agreement_id_kilo_pass_org_agreements_id_fk": { + "name": "kilo_pass_org_issuance_snapshots_agreement_id_kilo_pass_org_agreements_id_fk", + "tableFrom": "kilo_pass_org_issuance_snapshots", + "tableTo": "kilo_pass_org_agreements", + "columnsFrom": [ + "agreement_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "restrict", + "onUpdate": "cascade" + }, + "kilo_pass_org_issuance_snapshots_processing_run_id_kilo_pass_org_processing_runs_id_fk": { + "name": "kilo_pass_org_issuance_snapshots_processing_run_id_kilo_pass_org_processing_runs_id_fk", + "tableFrom": "kilo_pass_org_issuance_snapshots", + "tableTo": "kilo_pass_org_processing_runs", + "columnsFrom": [ + "processing_run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "cascade" + }, + "kilo_pass_org_issuance_snapshots_allocation_plan_id_kilo_pass_org_allocation_plans_id_fk": { + "name": "kilo_pass_org_issuance_snapshots_allocation_plan_id_kilo_pass_org_allocation_plans_id_fk", + "tableFrom": "kilo_pass_org_issuance_snapshots", + "tableTo": "kilo_pass_org_allocation_plans", + "columnsFrom": [ + "allocation_plan_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "cascade" + }, + "kilo_pass_org_issuance_snapshots_term_version_id_kilo_pass_org_term_versions_id_fk": { + "name": "kilo_pass_org_issuance_snapshots_term_version_id_kilo_pass_org_term_versions_id_fk", + "tableFrom": "kilo_pass_org_issuance_snapshots", + "tableTo": "kilo_pass_org_term_versions", + "columnsFrom": [ + "term_version_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "restrict", + "onUpdate": "cascade" + }, + "kilo_pass_org_issuance_snapshots_allocation_container_organization_id_organizations_id_fk": { + "name": "kilo_pass_org_issuance_snapshots_allocation_container_organization_id_organizations_id_fk", + "tableFrom": "kilo_pass_org_issuance_snapshots", + "tableTo": "organizations", + "columnsFrom": [ + "allocation_container_organization_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "restrict", + "onUpdate": "cascade" + }, + "kilo_pass_org_issuance_snapshots_bonus_credit_transaction_id_credit_transactions_id_fk": { + "name": "kilo_pass_org_issuance_snapshots_bonus_credit_transaction_id_credit_transactions_id_fk", + "tableFrom": "kilo_pass_org_issuance_snapshots", + "tableTo": "credit_transactions", + "columnsFrom": [ + "bonus_credit_transaction_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "restrict", + "onUpdate": "cascade" + }, + "kilo_pass_org_issuance_snapshots_base_credit_transaction_id_credit_transactions_id_fk": { + "name": "kilo_pass_org_issuance_snapshots_base_credit_transaction_id_credit_transactions_id_fk", + "tableFrom": "kilo_pass_org_issuance_snapshots", + "tableTo": "credit_transactions", + "columnsFrom": [ + "base_credit_transaction_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "restrict", + "onUpdate": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "UQ_kilo_pass_org_issuance_snapshots_container_window_tranche": { + "name": "UQ_kilo_pass_org_issuance_snapshots_container_window_tranche", + "nullsNotDistinct": false, + "columns": [ + "agreement_id", + "allocation_container_organization_id", + "window_start", + "tranche_key" + ] + } + }, + "policies": {}, + "checkConstraints": { + "kilo_pass_org_issuance_snapshots_window_check": { + "name": "kilo_pass_org_issuance_snapshots_window_check", + "value": "\"kilo_pass_org_issuance_snapshots\".\"window_start\" < \"kilo_pass_org_issuance_snapshots\".\"window_end\"" + }, + "kilo_pass_org_issuance_snapshots_qualifying_spend_window_check": { + "name": "kilo_pass_org_issuance_snapshots_qualifying_spend_window_check", + "value": "\"kilo_pass_org_issuance_snapshots\".\"window_start\" <= \"kilo_pass_org_issuance_snapshots\".\"qualifying_spend_starts_at\" AND \"kilo_pass_org_issuance_snapshots\".\"qualifying_spend_starts_at\" < \"kilo_pass_org_issuance_snapshots\".\"window_end\"" + }, + "kilo_pass_org_issuance_snapshots_values_non_negative_check": { + "name": "kilo_pass_org_issuance_snapshots_values_non_negative_check", + "value": "\"kilo_pass_org_issuance_snapshots\".\"allocated_pass_capacity\" >= 0 AND \"kilo_pass_org_issuance_snapshots\".\"base_credit_microdollars\" >= 0 AND \"kilo_pass_org_issuance_snapshots\".\"bonus_credit_microdollars\" >= 0 AND \"kilo_pass_org_issuance_snapshots\".\"unlock_spend_microdollars\" >= 0 AND \"kilo_pass_org_issuance_snapshots\".\"qualifying_spend_microdollars\" >= 0" + }, + "kilo_pass_org_issuance_snapshots_kind_check": { + "name": "kilo_pass_org_issuance_snapshots_kind_check", + "value": "\"kilo_pass_org_issuance_snapshots\".\"kind\" IN ('regular', 'bridge', 'supplement')" + }, + "kilo_pass_org_issuance_snapshots_bonus_mode_check": { + "name": "kilo_pass_org_issuance_snapshots_bonus_mode_check", + "value": "\"kilo_pass_org_issuance_snapshots\".\"bonus_mode\" IN ('after_base', 'upfront')" + } + }, + "isRLSEnabled": false + }, + "public.kilo_pass_org_notification_deliveries": { + "name": "kilo_pass_org_notification_deliveries", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "pg_catalog.gen_random_uuid()" + }, + "processing_run_id": { + "name": "processing_run_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "recipient_kilo_user_id": { + "name": "recipient_kilo_user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "attempt_count": { + "name": "attempt_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "lease_expires_at": { + "name": "lease_expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "sent_at": { + "name": "sent_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "IDX_kilo_pass_org_notification_deliveries_status": { + "name": "IDX_kilo_pass_org_notification_deliveries_status", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "kilo_pass_org_notification_deliveries_processing_run_id_kilo_pass_org_processing_runs_id_fk": { + "name": "kilo_pass_org_notification_deliveries_processing_run_id_kilo_pass_org_processing_runs_id_fk", + "tableFrom": "kilo_pass_org_notification_deliveries", + "tableTo": "kilo_pass_org_processing_runs", + "columnsFrom": [ + "processing_run_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "cascade" + }, + "kilo_pass_org_notification_deliveries_recipient_kilo_user_id_kilocode_users_id_fk": { + "name": "kilo_pass_org_notification_deliveries_recipient_kilo_user_id_kilocode_users_id_fk", + "tableFrom": "kilo_pass_org_notification_deliveries", + "tableTo": "kilocode_users", + "columnsFrom": [ + "recipient_kilo_user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "UQ_kilo_pass_org_notification_deliveries_run_recipient": { + "name": "UQ_kilo_pass_org_notification_deliveries_run_recipient", + "nullsNotDistinct": false, + "columns": [ + "processing_run_id", + "recipient_kilo_user_id" + ] + } + }, + "policies": {}, + "checkConstraints": { + "kilo_pass_org_notification_deliveries_status_check": { + "name": "kilo_pass_org_notification_deliveries_status_check", + "value": "\"kilo_pass_org_notification_deliveries\".\"status\" IN ('pending', 'sending', 'sent', 'failed')" + }, + "kilo_pass_org_notification_deliveries_attempt_count_check": { + "name": "kilo_pass_org_notification_deliveries_attempt_count_check", + "value": "\"kilo_pass_org_notification_deliveries\".\"attempt_count\" >= 0" + }, + "kilo_pass_org_notification_deliveries_sent_check": { + "name": "kilo_pass_org_notification_deliveries_sent_check", + "value": "(\"kilo_pass_org_notification_deliveries\".\"status\" = 'sent' AND \"kilo_pass_org_notification_deliveries\".\"sent_at\" IS NOT NULL AND \"kilo_pass_org_notification_deliveries\".\"lease_expires_at\" IS NULL) OR (\"kilo_pass_org_notification_deliveries\".\"status\" = 'sending' AND \"kilo_pass_org_notification_deliveries\".\"sent_at\" IS NULL AND \"kilo_pass_org_notification_deliveries\".\"lease_expires_at\" IS NOT NULL) OR (\"kilo_pass_org_notification_deliveries\".\"status\" IN ('pending', 'failed') AND \"kilo_pass_org_notification_deliveries\".\"sent_at\" IS NULL AND \"kilo_pass_org_notification_deliveries\".\"lease_expires_at\" IS NULL)" + } + }, + "isRLSEnabled": false + }, + "public.kilo_pass_org_processing_runs": { + "name": "kilo_pass_org_processing_runs", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "pg_catalog.gen_random_uuid()" + }, + "agreement_id": { + "name": "agreement_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "window_start": { + "name": "window_start", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "window_end": { + "name": "window_end", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "state": { + "name": "state", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "idempotency_key": { + "name": "idempotency_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "lease_expires_at": { + "name": "lease_expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "attempt_count": { + "name": "attempt_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "failure_code": { + "name": "failure_code", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "IDX_kilo_pass_org_processing_runs_state_lease": { + "name": "IDX_kilo_pass_org_processing_runs_state_lease", + "columns": [ + { + "expression": "state", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "lease_expires_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "kilo_pass_org_processing_runs_agreement_id_kilo_pass_org_agreements_id_fk": { + "name": "kilo_pass_org_processing_runs_agreement_id_kilo_pass_org_agreements_id_fk", + "tableFrom": "kilo_pass_org_processing_runs", + "tableTo": "kilo_pass_org_agreements", + "columnsFrom": [ + "agreement_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "UQ_kilo_pass_org_processing_runs_agreement_window": { + "name": "UQ_kilo_pass_org_processing_runs_agreement_window", + "nullsNotDistinct": false, + "columns": [ + "agreement_id", + "window_start" + ] + }, + "UQ_kilo_pass_org_processing_runs_idempotency": { + "name": "UQ_kilo_pass_org_processing_runs_idempotency", + "nullsNotDistinct": false, + "columns": [ + "idempotency_key" + ] + } + }, + "policies": {}, + "checkConstraints": { + "kilo_pass_org_processing_runs_window_check": { + "name": "kilo_pass_org_processing_runs_window_check", + "value": "\"kilo_pass_org_processing_runs\".\"window_start\" < \"kilo_pass_org_processing_runs\".\"window_end\"" + }, + "kilo_pass_org_processing_runs_attempt_count_non_negative_check": { + "name": "kilo_pass_org_processing_runs_attempt_count_non_negative_check", + "value": "\"kilo_pass_org_processing_runs\".\"attempt_count\" >= 0" + }, + "kilo_pass_org_processing_runs_state_check": { + "name": "kilo_pass_org_processing_runs_state_check", + "value": "\"kilo_pass_org_processing_runs\".\"state\" IN ('pending', 'running', 'succeeded', 'blocked', 'failed')" + } + }, + "isRLSEnabled": false + }, + "public.kilo_pass_org_qualifying_spend_events": { + "name": "kilo_pass_org_qualifying_spend_events", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "pg_catalog.gen_random_uuid()" + }, + "issuance_snapshot_id": { + "name": "issuance_snapshot_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "allocation_container_organization_id": { + "name": "allocation_container_organization_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "credit_transaction_id": { + "name": "credit_transaction_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "spent_microdollars": { + "name": "spent_microdollars", + "type": "bigint", + "primaryKey": false, + "notNull": true + }, + "occurred_at": { + "name": "occurred_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "IDX_kilo_pass_org_qualifying_spend_events_snapshot_occurred": { + "name": "IDX_kilo_pass_org_qualifying_spend_events_snapshot_occurred", + "columns": [ + { + "expression": "issuance_snapshot_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "occurred_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "kilo_pass_org_qualifying_spend_events_issuance_snapshot_id_kilo_pass_org_issuance_snapshots_id_fk": { + "name": "kilo_pass_org_qualifying_spend_events_issuance_snapshot_id_kilo_pass_org_issuance_snapshots_id_fk", + "tableFrom": "kilo_pass_org_qualifying_spend_events", + "tableTo": "kilo_pass_org_issuance_snapshots", + "columnsFrom": [ + "issuance_snapshot_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "cascade" + }, + "kilo_pass_org_qualifying_spend_events_allocation_container_organization_id_organizations_id_fk": { + "name": "kilo_pass_org_qualifying_spend_events_allocation_container_organization_id_organizations_id_fk", + "tableFrom": "kilo_pass_org_qualifying_spend_events", + "tableTo": "organizations", + "columnsFrom": [ + "allocation_container_organization_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "restrict", + "onUpdate": "cascade" + }, + "kilo_pass_org_qualifying_spend_events_credit_transaction_id_credit_transactions_id_fk": { + "name": "kilo_pass_org_qualifying_spend_events_credit_transaction_id_credit_transactions_id_fk", + "tableFrom": "kilo_pass_org_qualifying_spend_events", + "tableTo": "credit_transactions", + "columnsFrom": [ + "credit_transaction_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "restrict", + "onUpdate": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "UQ_kilo_pass_org_qualifying_spend_events_snapshot_credit_transaction": { + "name": "UQ_kilo_pass_org_qualifying_spend_events_snapshot_credit_transaction", + "nullsNotDistinct": false, + "columns": [ + "issuance_snapshot_id", + "credit_transaction_id" + ] + } + }, + "policies": {}, + "checkConstraints": { + "kilo_pass_org_qualifying_spend_events_amount_positive_check": { + "name": "kilo_pass_org_qualifying_spend_events_amount_positive_check", + "value": "\"kilo_pass_org_qualifying_spend_events\".\"spent_microdollars\" > 0" + } + }, + "isRLSEnabled": false + }, + "public.kilo_pass_org_supplements": { + "name": "kilo_pass_org_supplements", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "pg_catalog.gen_random_uuid()" + }, + "issuance_snapshot_id": { + "name": "issuance_snapshot_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "provider_invoice_line_id": { + "name": "provider_invoice_line_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "remaining_service_numerator": { + "name": "remaining_service_numerator", + "type": "bigint", + "primaryKey": false, + "notNull": true + }, + "remaining_service_denominator": { + "name": "remaining_service_denominator", + "type": "bigint", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "kilo_pass_org_supplements_issuance_snapshot_id_kilo_pass_org_issuance_snapshots_id_fk": { + "name": "kilo_pass_org_supplements_issuance_snapshot_id_kilo_pass_org_issuance_snapshots_id_fk", + "tableFrom": "kilo_pass_org_supplements", + "tableTo": "kilo_pass_org_issuance_snapshots", + "columnsFrom": [ + "issuance_snapshot_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "UQ_kilo_pass_org_supplements_provider_invoice_line": { + "name": "UQ_kilo_pass_org_supplements_provider_invoice_line", + "nullsNotDistinct": false, + "columns": [ + "provider_invoice_line_id" + ] + } + }, + "policies": {}, + "checkConstraints": { + "kilo_pass_org_supplements_ratio_check": { + "name": "kilo_pass_org_supplements_ratio_check", + "value": "\"kilo_pass_org_supplements\".\"remaining_service_numerator\" > 0 AND \"kilo_pass_org_supplements\".\"remaining_service_denominator\" > 0 AND \"kilo_pass_org_supplements\".\"remaining_service_numerator\" <= \"kilo_pass_org_supplements\".\"remaining_service_denominator\"" + } + }, + "isRLSEnabled": false + }, + "public.kilo_pass_org_term_transitions": { + "name": "kilo_pass_org_term_transitions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "pg_catalog.gen_random_uuid()" + }, + "agreement_id": { + "name": "agreement_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "from_term_version_id": { + "name": "from_term_version_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "to_term_version_id": { + "name": "to_term_version_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "effective_at": { + "name": "effective_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "created_by_kilo_user_id": { + "name": "created_by_kilo_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "reason": { + "name": "reason", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "kilo_pass_org_term_transitions_agreement_id_kilo_pass_org_agreements_id_fk": { + "name": "kilo_pass_org_term_transitions_agreement_id_kilo_pass_org_agreements_id_fk", + "tableFrom": "kilo_pass_org_term_transitions", + "tableTo": "kilo_pass_org_agreements", + "columnsFrom": [ + "agreement_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "cascade" + }, + "kilo_pass_org_term_transitions_from_term_version_id_kilo_pass_org_term_versions_id_fk": { + "name": "kilo_pass_org_term_transitions_from_term_version_id_kilo_pass_org_term_versions_id_fk", + "tableFrom": "kilo_pass_org_term_transitions", + "tableTo": "kilo_pass_org_term_versions", + "columnsFrom": [ + "from_term_version_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "restrict", + "onUpdate": "cascade" + }, + "kilo_pass_org_term_transitions_to_term_version_id_kilo_pass_org_term_versions_id_fk": { + "name": "kilo_pass_org_term_transitions_to_term_version_id_kilo_pass_org_term_versions_id_fk", + "tableFrom": "kilo_pass_org_term_transitions", + "tableTo": "kilo_pass_org_term_versions", + "columnsFrom": [ + "to_term_version_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "restrict", + "onUpdate": "cascade" + }, + "kilo_pass_org_term_transitions_created_by_kilo_user_id_kilocode_users_id_fk": { + "name": "kilo_pass_org_term_transitions_created_by_kilo_user_id_kilocode_users_id_fk", + "tableFrom": "kilo_pass_org_term_transitions", + "tableTo": "kilocode_users", + "columnsFrom": [ + "created_by_kilo_user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "UQ_kilo_pass_org_term_transitions_agreement_effective": { + "name": "UQ_kilo_pass_org_term_transitions_agreement_effective", + "nullsNotDistinct": false, + "columns": [ + "agreement_id", + "effective_at" + ] + } + }, + "policies": {}, + "checkConstraints": { + "kilo_pass_org_term_transitions_changes_version_check": { + "name": "kilo_pass_org_term_transitions_changes_version_check", + "value": "\"kilo_pass_org_term_transitions\".\"from_term_version_id\" <> \"kilo_pass_org_term_transitions\".\"to_term_version_id\"" + } + }, + "isRLSEnabled": false + }, + "public.kilo_pass_org_term_versions": { + "name": "kilo_pass_org_term_versions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "pg_catalog.gen_random_uuid()" + }, + "version_key": { + "name": "version_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "tier": { + "name": "tier", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "cadence": { + "name": "cadence", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "billing_price_microdollars_per_pass": { + "name": "billing_price_microdollars_per_pass", + "type": "bigint", + "primaryKey": false, + "notNull": true + }, + "base_credit_microdollars_per_pass": { + "name": "base_credit_microdollars_per_pass", + "type": "bigint", + "primaryKey": false, + "notNull": true + }, + "bonus_credit_microdollars_per_pass": { + "name": "bonus_credit_microdollars_per_pass", + "type": "bigint", + "primaryKey": false, + "notNull": true + }, + "unlock_spend_microdollars_per_pass": { + "name": "unlock_spend_microdollars_per_pass", + "type": "bigint", + "primaryKey": false, + "notNull": true + }, + "bonus_mode": { + "name": "bonus_mode", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_by_kilo_user_id": { + "name": "created_by_kilo_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "kilo_pass_org_term_versions_created_by_kilo_user_id_kilocode_users_id_fk": { + "name": "kilo_pass_org_term_versions_created_by_kilo_user_id_kilocode_users_id_fk", + "tableFrom": "kilo_pass_org_term_versions", + "tableTo": "kilocode_users", + "columnsFrom": [ + "created_by_kilo_user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "UQ_kilo_pass_org_term_versions_version_key": { + "name": "UQ_kilo_pass_org_term_versions_version_key", + "nullsNotDistinct": false, + "columns": [ + "version_key" + ] + } + }, + "policies": {}, + "checkConstraints": { + "kilo_pass_org_term_versions_amounts_non_negative_check": { + "name": "kilo_pass_org_term_versions_amounts_non_negative_check", + "value": "\"kilo_pass_org_term_versions\".\"billing_price_microdollars_per_pass\" >= 0 AND \"kilo_pass_org_term_versions\".\"base_credit_microdollars_per_pass\" >= 0 AND \"kilo_pass_org_term_versions\".\"bonus_credit_microdollars_per_pass\" >= 0 AND \"kilo_pass_org_term_versions\".\"unlock_spend_microdollars_per_pass\" >= 0" + }, + "kilo_pass_org_term_versions_tier_check": { + "name": "kilo_pass_org_term_versions_tier_check", + "value": "\"kilo_pass_org_term_versions\".\"tier\" IN ('tier_19', 'tier_49', 'tier_199')" + }, + "kilo_pass_org_term_versions_cadence_check": { + "name": "kilo_pass_org_term_versions_cadence_check", + "value": "\"kilo_pass_org_term_versions\".\"cadence\" IN ('monthly', 'yearly')" + }, + "kilo_pass_org_term_versions_bonus_mode_check": { + "name": "kilo_pass_org_term_versions_bonus_mode_check", + "value": "\"kilo_pass_org_term_versions\".\"bonus_mode\" IN ('after_base', 'upfront')" + } + }, + "isRLSEnabled": false + }, + "public.kilo_pass_pause_events": { + "name": "kilo_pass_pause_events", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "pg_catalog.gen_random_uuid()" + }, + "kilo_pass_subscription_id": { + "name": "kilo_pass_subscription_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "paused_at": { + "name": "paused_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "resumes_at": { + "name": "resumes_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "resumed_at": { + "name": "resumed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "IDX_kilo_pass_pause_events_subscription_id": { + "name": "IDX_kilo_pass_pause_events_subscription_id", + "columns": [ + { + "expression": "kilo_pass_subscription_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "UQ_kilo_pass_pause_events_one_open_per_sub": { + "name": "UQ_kilo_pass_pause_events_one_open_per_sub", + "columns": [ + { + "expression": "kilo_pass_subscription_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"kilo_pass_pause_events\".\"resumed_at\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "kilo_pass_pause_events_kilo_pass_subscription_id_kilo_pass_subscriptions_id_fk": { + "name": "kilo_pass_pause_events_kilo_pass_subscription_id_kilo_pass_subscriptions_id_fk", + "tableFrom": "kilo_pass_pause_events", + "tableTo": "kilo_pass_subscriptions", + "columnsFrom": [ + "kilo_pass_subscription_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "kilo_pass_pause_events_resumed_at_after_paused_at_check": { + "name": "kilo_pass_pause_events_resumed_at_after_paused_at_check", + "value": "\"kilo_pass_pause_events\".\"resumed_at\" IS NULL OR \"kilo_pass_pause_events\".\"resumed_at\" >= \"kilo_pass_pause_events\".\"paused_at\"" + } + }, + "isRLSEnabled": false + }, + "public.kilo_pass_scheduled_changes": { + "name": "kilo_pass_scheduled_changes", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "pg_catalog.gen_random_uuid()" + }, + "kilo_user_id": { + "name": "kilo_user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "stripe_subscription_id": { + "name": "stripe_subscription_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "from_tier": { + "name": "from_tier", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "from_cadence": { + "name": "from_cadence", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "to_tier": { + "name": "to_tier", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "to_cadence": { + "name": "to_cadence", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "stripe_schedule_id": { + "name": "stripe_schedule_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "effective_at": { + "name": "effective_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "deleted_at": { + "name": "deleted_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "IDX_kilo_pass_scheduled_changes_kilo_user_id": { + "name": "IDX_kilo_pass_scheduled_changes_kilo_user_id", + "columns": [ + { + "expression": "kilo_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_kilo_pass_scheduled_changes_status": { + "name": "IDX_kilo_pass_scheduled_changes_status", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_kilo_pass_scheduled_changes_stripe_subscription_id": { + "name": "IDX_kilo_pass_scheduled_changes_stripe_subscription_id", + "columns": [ + { + "expression": "stripe_subscription_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "UQ_kilo_pass_scheduled_changes_active_stripe_subscription_id": { + "name": "UQ_kilo_pass_scheduled_changes_active_stripe_subscription_id", + "columns": [ + { + "expression": "stripe_subscription_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"kilo_pass_scheduled_changes\".\"deleted_at\" is null", + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_kilo_pass_scheduled_changes_effective_at": { + "name": "IDX_kilo_pass_scheduled_changes_effective_at", + "columns": [ + { + "expression": "effective_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_kilo_pass_scheduled_changes_deleted_at": { + "name": "IDX_kilo_pass_scheduled_changes_deleted_at", + "columns": [ + { + "expression": "deleted_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "kilo_pass_scheduled_changes_kilo_user_id_kilocode_users_id_fk": { + "name": "kilo_pass_scheduled_changes_kilo_user_id_kilocode_users_id_fk", + "tableFrom": "kilo_pass_scheduled_changes", + "tableTo": "kilocode_users", + "columnsFrom": [ + "kilo_user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "cascade" + }, + "kilo_pass_scheduled_changes_stripe_subscription_id_kilo_pass_subscriptions_stripe_subscription_id_fk": { + "name": "kilo_pass_scheduled_changes_stripe_subscription_id_kilo_pass_subscriptions_stripe_subscription_id_fk", + "tableFrom": "kilo_pass_scheduled_changes", + "tableTo": "kilo_pass_subscriptions", + "columnsFrom": [ + "stripe_subscription_id" + ], + "columnsTo": [ + "stripe_subscription_id" + ], + "onDelete": "cascade", + "onUpdate": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "kilo_pass_scheduled_changes_from_tier_check": { + "name": "kilo_pass_scheduled_changes_from_tier_check", + "value": "\"kilo_pass_scheduled_changes\".\"from_tier\" IN ('tier_19', 'tier_49', 'tier_199')" + }, + "kilo_pass_scheduled_changes_from_cadence_check": { + "name": "kilo_pass_scheduled_changes_from_cadence_check", + "value": "\"kilo_pass_scheduled_changes\".\"from_cadence\" IN ('monthly', 'yearly')" + }, + "kilo_pass_scheduled_changes_to_tier_check": { + "name": "kilo_pass_scheduled_changes_to_tier_check", + "value": "\"kilo_pass_scheduled_changes\".\"to_tier\" IN ('tier_19', 'tier_49', 'tier_199')" + }, + "kilo_pass_scheduled_changes_to_cadence_check": { + "name": "kilo_pass_scheduled_changes_to_cadence_check", + "value": "\"kilo_pass_scheduled_changes\".\"to_cadence\" IN ('monthly', 'yearly')" + }, + "kilo_pass_scheduled_changes_status_check": { + "name": "kilo_pass_scheduled_changes_status_check", + "value": "\"kilo_pass_scheduled_changes\".\"status\" IN ('not_started', 'active', 'completed', 'released', 'canceled')" + } + }, + "isRLSEnabled": false + }, + "public.kilo_pass_store_events": { + "name": "kilo_pass_store_events", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "pg_catalog.gen_random_uuid()" + }, + "payment_provider": { + "name": "payment_provider", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "event_id": { + "name": "event_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "provider_subscription_id": { + "name": "provider_subscription_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "provider_transaction_id": { + "name": "provider_transaction_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "app_account_token": { + "name": "app_account_token", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "product_id": { + "name": "product_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "environment": { + "name": "environment", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "payload_json": { + "name": "payload_json", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "processing_started_at": { + "name": "processing_started_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "processed_at": { + "name": "processed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "UQ_kilo_pass_store_events_provider_event": { + "name": "UQ_kilo_pass_store_events_provider_event", + "columns": [ + { + "expression": "payment_provider", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "event_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_kilo_pass_store_events_provider_subscription": { + "name": "IDX_kilo_pass_store_events_provider_subscription", + "columns": [ + { + "expression": "payment_provider", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "provider_subscription_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_kilo_pass_store_events_app_account_token": { + "name": "IDX_kilo_pass_store_events_app_account_token", + "columns": [ + { + "expression": "app_account_token", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "kilo_pass_store_events_payment_provider_check": { + "name": "kilo_pass_store_events_payment_provider_check", + "value": "\"kilo_pass_store_events\".\"payment_provider\" IN ('stripe', 'app_store', 'google_play')" + } + }, + "isRLSEnabled": false + }, + "public.kilo_pass_store_purchases": { + "name": "kilo_pass_store_purchases", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "pg_catalog.gen_random_uuid()" + }, + "kilo_pass_subscription_id": { + "name": "kilo_pass_subscription_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "kilo_user_id": { + "name": "kilo_user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "payment_provider": { + "name": "payment_provider", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "product_id": { + "name": "product_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "provider_subscription_id": { + "name": "provider_subscription_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "provider_transaction_id": { + "name": "provider_transaction_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "provider_original_transaction_id": { + "name": "provider_original_transaction_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "app_account_token": { + "name": "app_account_token", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "purchase_token": { + "name": "purchase_token", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "environment": { + "name": "environment", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "purchased_at": { + "name": "purchased_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "amount_charged_minor_units": { + "name": "amount_charged_minor_units", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "currency": { + "name": "currency", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "tax_minor_units": { + "name": "tax_minor_units", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "money_backfill_attempted_at": { + "name": "money_backfill_attempted_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "raw_payload_json": { + "name": "raw_payload_json", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "UQ_kilo_pass_store_purchases_provider_transaction": { + "name": "UQ_kilo_pass_store_purchases_provider_transaction", + "columns": [ + { + "expression": "payment_provider", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "provider_transaction_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_kilo_pass_store_purchases_subscription_id": { + "name": "IDX_kilo_pass_store_purchases_subscription_id", + "columns": [ + { + "expression": "kilo_pass_subscription_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_kilo_pass_store_purchases_user_id": { + "name": "IDX_kilo_pass_store_purchases_user_id", + "columns": [ + { + "expression": "kilo_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_kilo_pass_store_purchases_app_account_token": { + "name": "IDX_kilo_pass_store_purchases_app_account_token", + "columns": [ + { + "expression": "app_account_token", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_kilo_pass_store_purchases_latest_subscription_purchase": { + "name": "IDX_kilo_pass_store_purchases_latest_subscription_purchase", + "columns": [ + { + "expression": "payment_provider", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "provider_subscription_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "purchased_at", + "isExpression": false, + "asc": false, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "kilo_pass_store_purchases_kilo_pass_subscription_id_kilo_pass_subscriptions_id_fk": { + "name": "kilo_pass_store_purchases_kilo_pass_subscription_id_kilo_pass_subscriptions_id_fk", + "tableFrom": "kilo_pass_store_purchases", + "tableTo": "kilo_pass_subscriptions", + "columnsFrom": [ + "kilo_pass_subscription_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "cascade" + }, + "kilo_pass_store_purchases_kilo_user_id_kilocode_users_id_fk": { + "name": "kilo_pass_store_purchases_kilo_user_id_kilocode_users_id_fk", + "tableFrom": "kilo_pass_store_purchases", + "tableTo": "kilocode_users", + "columnsFrom": [ + "kilo_user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "cascade" + }, + "FK_kilo_pass_store_purchases_subscription_owner_provider": { + "name": "FK_kilo_pass_store_purchases_subscription_owner_provider", + "tableFrom": "kilo_pass_store_purchases", + "tableTo": "kilo_pass_subscriptions", + "columnsFrom": [ + "kilo_pass_subscription_id", + "kilo_user_id", + "payment_provider", + "provider_subscription_id" + ], + "columnsTo": [ + "id", + "kilo_user_id", + "payment_provider", + "provider_subscription_id" + ], + "onDelete": "cascade", + "onUpdate": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "kilo_pass_store_purchases_store_provider_check": { + "name": "kilo_pass_store_purchases_store_provider_check", + "value": "\"kilo_pass_store_purchases\".\"payment_provider\" IN ('app_store', 'google_play')" + }, + "kilo_pass_store_purchases_amount_charged_non_negative_check": { + "name": "kilo_pass_store_purchases_amount_charged_non_negative_check", + "value": "\"kilo_pass_store_purchases\".\"amount_charged_minor_units\" IS NULL OR \"kilo_pass_store_purchases\".\"amount_charged_minor_units\" >= 0" + }, + "kilo_pass_store_purchases_tax_non_negative_check": { + "name": "kilo_pass_store_purchases_tax_non_negative_check", + "value": "\"kilo_pass_store_purchases\".\"tax_minor_units\" IS NULL OR \"kilo_pass_store_purchases\".\"tax_minor_units\" >= 0" + }, + "kilo_pass_store_purchases_currency_check": { + "name": "kilo_pass_store_purchases_currency_check", + "value": "\"kilo_pass_store_purchases\".\"currency\" IS NULL OR \"kilo_pass_store_purchases\".\"currency\" ~ '^[A-Z]{3}$'" + }, + "kilo_pass_store_purchases_currency_required_check": { + "name": "kilo_pass_store_purchases_currency_required_check", + "value": "\"kilo_pass_store_purchases\".\"currency\" IS NOT NULL OR (\"kilo_pass_store_purchases\".\"amount_charged_minor_units\" IS NULL AND \"kilo_pass_store_purchases\".\"tax_minor_units\" IS NULL)" + }, + "kilo_pass_store_purchases_payment_provider_check": { + "name": "kilo_pass_store_purchases_payment_provider_check", + "value": "\"kilo_pass_store_purchases\".\"payment_provider\" IN ('stripe', 'app_store', 'google_play')" + } + }, + "isRLSEnabled": false + }, + "public.kilo_pass_subscriptions": { + "name": "kilo_pass_subscriptions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "pg_catalog.gen_random_uuid()" + }, + "kilo_user_id": { + "name": "kilo_user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "payment_provider": { + "name": "payment_provider", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'stripe'" + }, + "provider_subscription_id": { + "name": "provider_subscription_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "stripe_subscription_id": { + "name": "stripe_subscription_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "tier": { + "name": "tier", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "cadence": { + "name": "cadence", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "cancel_at_period_end": { + "name": "cancel_at_period_end", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "started_at": { + "name": "started_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "ended_at": { + "name": "ended_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "current_streak_months": { + "name": "current_streak_months", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "next_yearly_issue_at": { + "name": "next_yearly_issue_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "IDX_kilo_pass_subscriptions_kilo_user_id": { + "name": "IDX_kilo_pass_subscriptions_kilo_user_id", + "columns": [ + { + "expression": "kilo_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_kilo_pass_subscriptions_payment_provider": { + "name": "IDX_kilo_pass_subscriptions_payment_provider", + "columns": [ + { + "expression": "payment_provider", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_kilo_pass_subscriptions_status": { + "name": "IDX_kilo_pass_subscriptions_status", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_kilo_pass_subscriptions_cadence": { + "name": "IDX_kilo_pass_subscriptions_cadence", + "columns": [ + { + "expression": "cadence", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "UQ_kilo_pass_subscriptions_provider_subscription": { + "name": "UQ_kilo_pass_subscriptions_provider_subscription", + "columns": [ + { + "expression": "payment_provider", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "provider_subscription_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"kilo_pass_subscriptions\".\"provider_subscription_id\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "UQ_kilo_pass_subscriptions_store_purchase_reference": { + "name": "UQ_kilo_pass_subscriptions_store_purchase_reference", + "columns": [ + { + "expression": "id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "kilo_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "payment_provider", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "provider_subscription_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "kilo_pass_subscriptions_kilo_user_id_kilocode_users_id_fk": { + "name": "kilo_pass_subscriptions_kilo_user_id_kilocode_users_id_fk", + "tableFrom": "kilo_pass_subscriptions", + "tableTo": "kilocode_users", + "columnsFrom": [ + "kilo_user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "kilo_pass_subscriptions_stripe_subscription_id_unique": { + "name": "kilo_pass_subscriptions_stripe_subscription_id_unique", + "nullsNotDistinct": false, + "columns": [ + "stripe_subscription_id" + ] + } + }, + "policies": {}, + "checkConstraints": { + "kilo_pass_subscriptions_current_streak_months_non_negative_check": { + "name": "kilo_pass_subscriptions_current_streak_months_non_negative_check", + "value": "\"kilo_pass_subscriptions\".\"current_streak_months\" >= 0" + }, + "kilo_pass_subscriptions_provider_ids_check": { + "name": "kilo_pass_subscriptions_provider_ids_check", + "value": "(\n \"kilo_pass_subscriptions\".\"payment_provider\" = 'stripe'\n AND \"kilo_pass_subscriptions\".\"provider_subscription_id\" IS NOT NULL\n AND \"kilo_pass_subscriptions\".\"stripe_subscription_id\" IS NOT NULL\n AND \"kilo_pass_subscriptions\".\"provider_subscription_id\" = \"kilo_pass_subscriptions\".\"stripe_subscription_id\"\n ) OR (\n \"kilo_pass_subscriptions\".\"payment_provider\" IN ('app_store', 'google_play')\n AND \"kilo_pass_subscriptions\".\"provider_subscription_id\" IS NOT NULL\n AND \"kilo_pass_subscriptions\".\"stripe_subscription_id\" IS NULL\n )" + }, + "kilo_pass_subscriptions_payment_provider_check": { + "name": "kilo_pass_subscriptions_payment_provider_check", + "value": "\"kilo_pass_subscriptions\".\"payment_provider\" IN ('stripe', 'app_store', 'google_play')" + }, + "kilo_pass_subscriptions_tier_check": { + "name": "kilo_pass_subscriptions_tier_check", + "value": "\"kilo_pass_subscriptions\".\"tier\" IN ('tier_19', 'tier_49', 'tier_199')" + }, + "kilo_pass_subscriptions_cadence_check": { + "name": "kilo_pass_subscriptions_cadence_check", + "value": "\"kilo_pass_subscriptions\".\"cadence\" IN ('monthly', 'yearly')" + } + }, + "isRLSEnabled": false + }, + "public.kilo_pass_welcome_promo_payment_fingerprint_claims": { + "name": "kilo_pass_welcome_promo_payment_fingerprint_claims", + "schema": "", + "columns": { + "stripe_payment_method_type": { + "name": "stripe_payment_method_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "stripe_fingerprint": { + "name": "stripe_fingerprint", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "source_stripe_invoice_id": { + "name": "source_stripe_invoice_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "claimed_at": { + "name": "claimed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": { + "kilo_pass_welcome_promo_payment_fingerprint_claims_stripe_payment_method_type_stripe_fingerprint_pk": { + "name": "kilo_pass_welcome_promo_payment_fingerprint_claims_stripe_payment_method_type_stripe_fingerprint_pk", + "columns": [ + "stripe_payment_method_type", + "stripe_fingerprint" + ] + } + }, + "uniqueConstraints": { + "UQ_kilo_pass_welcome_promo_payment_fingerprint_claims_source_invoice_id": { + "name": "UQ_kilo_pass_welcome_promo_payment_fingerprint_claims_source_invoice_id", + "nullsNotDistinct": false, + "columns": [ + "source_stripe_invoice_id" + ] + } + }, + "policies": {}, + "checkConstraints": { + "kilo_pass_welcome_promo_payment_fingerprint_claims_type_check": { + "name": "kilo_pass_welcome_promo_payment_fingerprint_claims_type_check", + "value": "\"kilo_pass_welcome_promo_payment_fingerprint_claims\".\"stripe_payment_method_type\" IN ('card', 'sepa_debit', 'us_bank_account', 'bacs_debit', 'au_becs_debit')" + } + }, + "isRLSEnabled": false + }, + "public.kiloclaw_access_codes": { + "name": "kiloclaw_access_codes", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "code": { + "name": "code", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "kilo_user_id": { + "name": "kilo_user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'active'" + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "redeemed_at": { + "name": "redeemed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "UQ_kiloclaw_access_codes_code": { + "name": "UQ_kiloclaw_access_codes_code", + "columns": [ + { + "expression": "code", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_kiloclaw_access_codes_user_status": { + "name": "IDX_kiloclaw_access_codes_user_status", + "columns": [ + { + "expression": "kilo_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "UQ_kiloclaw_access_codes_one_active_per_user": { + "name": "UQ_kiloclaw_access_codes_one_active_per_user", + "columns": [ + { + "expression": "kilo_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "status = 'active'", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "kiloclaw_access_codes_kilo_user_id_kilocode_users_id_fk": { + "name": "kiloclaw_access_codes_kilo_user_id_kilocode_users_id_fk", + "tableFrom": "kiloclaw_access_codes", + "tableTo": "kilocode_users", + "columnsFrom": [ + "kilo_user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.kiloclaw_admin_audit_logs": { + "name": "kiloclaw_admin_audit_logs", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "action": { + "name": "action", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "actor_id": { + "name": "actor_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "actor_email": { + "name": "actor_email", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "actor_name": { + "name": "actor_name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "target_user_id": { + "name": "target_user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "message": { + "name": "message", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "IDX_kiloclaw_admin_audit_logs_target_user_id": { + "name": "IDX_kiloclaw_admin_audit_logs_target_user_id", + "columns": [ + { + "expression": "target_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_kiloclaw_admin_audit_logs_action": { + "name": "IDX_kiloclaw_admin_audit_logs_action", + "columns": [ + { + "expression": "action", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_kiloclaw_admin_audit_logs_created_at": { + "name": "IDX_kiloclaw_admin_audit_logs_created_at", + "columns": [ + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.kiloclaw_cli_runs": { + "name": "kiloclaw_cli_runs", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "instance_id": { + "name": "instance_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "initiated_by_admin_id": { + "name": "initiated_by_admin_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "prompt": { + "name": "prompt", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'running'" + }, + "exit_code": { + "name": "exit_code", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "output": { + "name": "output", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "started_at": { + "name": "started_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "completed_at": { + "name": "completed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "IDX_kiloclaw_cli_runs_user_id": { + "name": "IDX_kiloclaw_cli_runs_user_id", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_kiloclaw_cli_runs_started_at": { + "name": "IDX_kiloclaw_cli_runs_started_at", + "columns": [ + { + "expression": "started_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_kiloclaw_cli_runs_instance_id": { + "name": "IDX_kiloclaw_cli_runs_instance_id", + "columns": [ + { + "expression": "instance_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "kiloclaw_cli_runs_user_id_kilocode_users_id_fk": { + "name": "kiloclaw_cli_runs_user_id_kilocode_users_id_fk", + "tableFrom": "kiloclaw_cli_runs", + "tableTo": "kilocode_users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "kiloclaw_cli_runs_instance_id_kiloclaw_instances_id_fk": { + "name": "kiloclaw_cli_runs_instance_id_kiloclaw_instances_id_fk", + "tableFrom": "kiloclaw_cli_runs", + "tableTo": "kiloclaw_instances", + "columnsFrom": [ + "instance_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "kiloclaw_cli_runs_initiated_by_admin_id_kilocode_users_id_fk": { + "name": "kiloclaw_cli_runs_initiated_by_admin_id_kilocode_users_id_fk", + "tableFrom": "kiloclaw_cli_runs", + "tableTo": "kilocode_users", + "columnsFrom": [ + "initiated_by_admin_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.kiloclaw_earlybird_purchases": { + "name": "kiloclaw_earlybird_purchases", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "stripe_charge_id": { + "name": "stripe_charge_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "manual_payment_id": { + "name": "manual_payment_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "amount_cents": { + "name": "amount_cents", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "kiloclaw_earlybird_purchases_user_id_kilocode_users_id_fk": { + "name": "kiloclaw_earlybird_purchases_user_id_kilocode_users_id_fk", + "tableFrom": "kiloclaw_earlybird_purchases", + "tableTo": "kilocode_users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "kiloclaw_earlybird_purchases_user_id_unique": { + "name": "kiloclaw_earlybird_purchases_user_id_unique", + "nullsNotDistinct": false, + "columns": [ + "user_id" + ] + }, + "kiloclaw_earlybird_purchases_stripe_charge_id_unique": { + "name": "kiloclaw_earlybird_purchases_stripe_charge_id_unique", + "nullsNotDistinct": false, + "columns": [ + "stripe_charge_id" + ] + }, + "kiloclaw_earlybird_purchases_manual_payment_id_unique": { + "name": "kiloclaw_earlybird_purchases_manual_payment_id_unique", + "nullsNotDistinct": false, + "columns": [ + "manual_payment_id" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.kiloclaw_email_log": { + "name": "kiloclaw_email_log", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "instance_id": { + "name": "instance_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "email_type": { + "name": "email_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "period_start": { + "name": "period_start", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "'epoch'" + }, + "sent_at": { + "name": "sent_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "UQ_kiloclaw_email_log_user_type_global": { + "name": "UQ_kiloclaw_email_log_user_type_global", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "email_type", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"kiloclaw_email_log\".\"instance_id\" is null", + "concurrently": false, + "method": "btree", + "with": {} + }, + "UQ_kiloclaw_email_log_user_instance_type_period": { + "name": "UQ_kiloclaw_email_log_user_instance_type_period", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "instance_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "email_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "period_start", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"kiloclaw_email_log\".\"instance_id\" is not null", + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_kiloclaw_email_log_type_sent_instance": { + "name": "IDX_kiloclaw_email_log_type_sent_instance", + "columns": [ + { + "expression": "email_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "sent_at", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "instance_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"kiloclaw_email_log\".\"instance_id\" is not null", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "kiloclaw_email_log_user_id_kilocode_users_id_fk": { + "name": "kiloclaw_email_log_user_id_kilocode_users_id_fk", + "tableFrom": "kiloclaw_email_log", + "tableTo": "kilocode_users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "kiloclaw_email_log_instance_id_kiloclaw_instances_id_fk": { + "name": "kiloclaw_email_log_instance_id_kiloclaw_instances_id_fk", + "tableFrom": "kiloclaw_email_log", + "tableTo": "kiloclaw_instances", + "columnsFrom": [ + "instance_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.kiloclaw_google_oauth_connections": { + "name": "kiloclaw_google_oauth_connections", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "instance_id": { + "name": "instance_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "provider": { + "name": "provider", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'google'" + }, + "account_email": { + "name": "account_email", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "account_subject": { + "name": "account_subject", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "oauth_client_id": { + "name": "oauth_client_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "oauth_client_secret_encrypted": { + "name": "oauth_client_secret_encrypted", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "credential_profile": { + "name": "credential_profile", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'kilo_owned'" + }, + "refresh_token_encrypted": { + "name": "refresh_token_encrypted", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "scopes": { + "name": "scopes", + "type": "text[]", + "primaryKey": false, + "notNull": true, + "default": "'{}'::text[]" + }, + "grants_by_source": { + "name": "grants_by_source", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "capabilities": { + "name": "capabilities", + "type": "text[]", + "primaryKey": false, + "notNull": true, + "default": "'{}'::text[]" + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'active'" + }, + "last_error": { + "name": "last_error", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "last_error_at": { + "name": "last_error_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "connected_at": { + "name": "connected_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "UQ_kiloclaw_google_oauth_connections_instance": { + "name": "UQ_kiloclaw_google_oauth_connections_instance", + "columns": [ + { + "expression": "instance_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_kiloclaw_google_oauth_connections_status": { + "name": "IDX_kiloclaw_google_oauth_connections_status", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_kiloclaw_google_oauth_connections_provider": { + "name": "IDX_kiloclaw_google_oauth_connections_provider", + "columns": [ + { + "expression": "provider", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "kiloclaw_google_oauth_connections_instance_id_kiloclaw_instances_id_fk": { + "name": "kiloclaw_google_oauth_connections_instance_id_kiloclaw_instances_id_fk", + "tableFrom": "kiloclaw_google_oauth_connections", + "tableTo": "kiloclaw_instances", + "columnsFrom": [ + "instance_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "kiloclaw_google_oauth_connections_status_check": { + "name": "kiloclaw_google_oauth_connections_status_check", + "value": "\"kiloclaw_google_oauth_connections\".\"status\" IN ('active', 'action_required', 'disconnected')" + }, + "kiloclaw_google_oauth_connections_credential_profile_check": { + "name": "kiloclaw_google_oauth_connections_credential_profile_check", + "value": "\"kiloclaw_google_oauth_connections\".\"credential_profile\" IN ('legacy', 'kilo_owned')" + } + }, + "isRLSEnabled": false + }, + "public.kiloclaw_image_catalog": { + "name": "kiloclaw_image_catalog", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "openclaw_version": { + "name": "openclaw_version", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "variant": { + "name": "variant", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'default'" + }, + "image_tag": { + "name": "image_tag", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "image_digest": { + "name": "image_digest", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'available'" + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "updated_by": { + "name": "updated_by", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "published_at": { + "name": "published_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "synced_at": { + "name": "synced_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "rollout_percent": { + "name": "rollout_percent", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "is_latest": { + "name": "is_latest", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + } + }, + "indexes": { + "IDX_kiloclaw_image_catalog_status": { + "name": "IDX_kiloclaw_image_catalog_status", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_kiloclaw_image_catalog_variant": { + "name": "IDX_kiloclaw_image_catalog_variant", + "columns": [ + { + "expression": "variant", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "UQ_kiloclaw_image_catalog_one_latest_per_variant": { + "name": "UQ_kiloclaw_image_catalog_one_latest_per_variant", + "columns": [ + { + "expression": "variant", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"kiloclaw_image_catalog\".\"is_latest\" = true", + "concurrently": false, + "method": "btree", + "with": {} + }, + "UQ_kiloclaw_image_catalog_one_candidate_per_variant": { + "name": "UQ_kiloclaw_image_catalog_one_candidate_per_variant", + "columns": [ + { + "expression": "variant", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"kiloclaw_image_catalog\".\"is_latest\" = false AND \"kiloclaw_image_catalog\".\"rollout_percent\" > 0 AND \"kiloclaw_image_catalog\".\"status\" = 'available'", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "kiloclaw_image_catalog_image_tag_unique": { + "name": "kiloclaw_image_catalog_image_tag_unique", + "nullsNotDistinct": false, + "columns": [ + "image_tag" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.kiloclaw_inbound_email_aliases": { + "name": "kiloclaw_inbound_email_aliases", + "schema": "", + "columns": { + "alias": { + "name": "alias", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "instance_id": { + "name": "instance_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "retired_at": { + "name": "retired_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "IDX_kiloclaw_inbound_email_aliases_instance_id": { + "name": "IDX_kiloclaw_inbound_email_aliases_instance_id", + "columns": [ + { + "expression": "instance_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "UQ_kiloclaw_inbound_email_aliases_active_instance": { + "name": "UQ_kiloclaw_inbound_email_aliases_active_instance", + "columns": [ + { + "expression": "instance_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"kiloclaw_inbound_email_aliases\".\"retired_at\" is null", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "kiloclaw_inbound_email_aliases_instance_id_kiloclaw_instances_id_fk": { + "name": "kiloclaw_inbound_email_aliases_instance_id_kiloclaw_instances_id_fk", + "tableFrom": "kiloclaw_inbound_email_aliases", + "tableTo": "kiloclaw_instances", + "columnsFrom": [ + "instance_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.kiloclaw_inbound_email_reserved_aliases": { + "name": "kiloclaw_inbound_email_reserved_aliases", + "schema": "", + "columns": { + "alias": { + "name": "alias", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.kiloclaw_instances": { + "name": "kiloclaw_instances", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "sandbox_id": { + "name": "sandbox_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "provider": { + "name": "provider", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'fly'" + }, + "organization_id": { + "name": "organization_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "inbound_email_enabled": { + "name": "inbound_email_enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "inactive_trial_stopped_at": { + "name": "inactive_trial_stopped_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "destroyed_at": { + "name": "destroyed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "tracked_image_tag": { + "name": "tracked_image_tag", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "instance_type": { + "name": "instance_type", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "admin_size_override": { + "name": "admin_size_override", + "type": "jsonb", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "UQ_kiloclaw_instances_active": { + "name": "UQ_kiloclaw_instances_active", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "sandbox_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"kiloclaw_instances\".\"destroyed_at\" is null", + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_kiloclaw_instances_active_personal_by_user": { + "name": "IDX_kiloclaw_instances_active_personal_by_user", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"kiloclaw_instances\".\"organization_id\" IS NULL AND \"kiloclaw_instances\".\"destroyed_at\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_kiloclaw_instances_active_org_by_user_org": { + "name": "IDX_kiloclaw_instances_active_org_by_user_org", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"kiloclaw_instances\".\"organization_id\" IS NOT NULL AND \"kiloclaw_instances\".\"destroyed_at\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_kiloclaw_instances_active_org_by_org_created": { + "name": "IDX_kiloclaw_instances_active_org_by_org_created", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"kiloclaw_instances\".\"organization_id\" IS NOT NULL AND \"kiloclaw_instances\".\"destroyed_at\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_kiloclaw_instances_user_id_created_at": { + "name": "IDX_kiloclaw_instances_user_id_created_at", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_kiloclaw_instances_tracked_image_tag": { + "name": "IDX_kiloclaw_instances_tracked_image_tag", + "columns": [ + { + "expression": "tracked_image_tag", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"kiloclaw_instances\".\"destroyed_at\" is null", + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_kiloclaw_instances_instance_type": { + "name": "IDX_kiloclaw_instances_instance_type", + "columns": [ + { + "expression": "instance_type", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"kiloclaw_instances\".\"destroyed_at\" is null", + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_kiloclaw_instances_admin_size_override": { + "name": "IDX_kiloclaw_instances_admin_size_override", + "columns": [ + { + "expression": "id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"kiloclaw_instances\".\"admin_size_override\" IS NOT NULL AND \"kiloclaw_instances\".\"destroyed_at\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "kiloclaw_instances_user_id_kilocode_users_id_fk": { + "name": "kiloclaw_instances_user_id_kilocode_users_id_fk", + "tableFrom": "kiloclaw_instances", + "tableTo": "kilocode_users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "kiloclaw_instances_organization_id_organizations_id_fk": { + "name": "kiloclaw_instances_organization_id_organizations_id_fk", + "tableFrom": "kiloclaw_instances", + "tableTo": "organizations", + "columnsFrom": [ + "organization_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "CHK_kiloclaw_instances_instance_type": { + "name": "CHK_kiloclaw_instances_instance_type", + "value": "\"kiloclaw_instances\".\"instance_type\" IS NULL OR \"kiloclaw_instances\".\"instance_type\" IN ('perf-1-3', 'perf-4-8', 'perf-4-16', 'shared-2-3', 'shared-2-4', 'custom')" + } + }, + "isRLSEnabled": false + }, + "public.kiloclaw_morning_briefing_configs": { + "name": "kiloclaw_morning_briefing_configs", + "schema": "", + "columns": { + "instance_id": { + "name": "instance_id", + "type": "uuid", + "primaryKey": true, + "notNull": true + }, + "enabled": { + "name": "enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "cron": { + "name": "cron", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'0 7 * * *'" + }, + "timezone": { + "name": "timezone", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'UTC'" + }, + "interest_topics": { + "name": "interest_topics", + "type": "text[]", + "primaryKey": false, + "notNull": true, + "default": "'{}'::text[]" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "IDX_kiloclaw_morning_briefing_configs_enabled": { + "name": "IDX_kiloclaw_morning_briefing_configs_enabled", + "columns": [ + { + "expression": "instance_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"kiloclaw_morning_briefing_configs\".\"enabled\" = true", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "kiloclaw_morning_briefing_configs_instance_id_kiloclaw_instances_id_fk": { + "name": "kiloclaw_morning_briefing_configs_instance_id_kiloclaw_instances_id_fk", + "tableFrom": "kiloclaw_morning_briefing_configs", + "tableTo": "kiloclaw_instances", + "columnsFrom": [ + "instance_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.kiloclaw_scheduled_action_notifications": { + "name": "kiloclaw_scheduled_action_notifications", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "target_id": { + "name": "target_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "channel": { + "name": "channel", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "kind": { + "name": "kind", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'notice'" + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "claimed_at": { + "name": "claimed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "sent_at": { + "name": "sent_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "error_message": { + "name": "error_message", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "UQ_kiloclaw_scheduled_action_notifications_target_kind_channel": { + "name": "UQ_kiloclaw_scheduled_action_notifications_target_kind_channel", + "columns": [ + { + "expression": "target_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "kind", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "channel", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_kiloclaw_scheduled_action_notifications_pending": { + "name": "IDX_kiloclaw_scheduled_action_notifications_pending", + "columns": [ + { + "expression": "target_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"kiloclaw_scheduled_action_notifications\".\"status\" = 'pending'", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "kiloclaw_scheduled_action_notifications_target_id_kiloclaw_scheduled_action_targets_id_fk": { + "name": "kiloclaw_scheduled_action_notifications_target_id_kiloclaw_scheduled_action_targets_id_fk", + "tableFrom": "kiloclaw_scheduled_action_notifications", + "tableTo": "kiloclaw_scheduled_action_targets", + "columnsFrom": [ + "target_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.kiloclaw_scheduled_action_stages": { + "name": "kiloclaw_scheduled_action_stages", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "scheduled_action_id": { + "name": "scheduled_action_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "stage_index": { + "name": "stage_index", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "scheduled_at": { + "name": "scheduled_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "notice_sent_at": { + "name": "notice_sent_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "started_at": { + "name": "started_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "completed_at": { + "name": "completed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "applied_count": { + "name": "applied_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "skipped_count": { + "name": "skipped_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "failed_count": { + "name": "failed_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + } + }, + "indexes": { + "UQ_kiloclaw_scheduled_action_stages_parent_index": { + "name": "UQ_kiloclaw_scheduled_action_stages_parent_index", + "columns": [ + { + "expression": "scheduled_action_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "stage_index", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_kiloclaw_scheduled_action_stages_notice_due": { + "name": "IDX_kiloclaw_scheduled_action_stages_notice_due", + "columns": [ + { + "expression": "scheduled_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"kiloclaw_scheduled_action_stages\".\"notice_sent_at\" IS NULL AND \"kiloclaw_scheduled_action_stages\".\"status\" = 'pending'", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "kiloclaw_scheduled_action_stages_scheduled_action_id_kiloclaw_scheduled_actions_id_fk": { + "name": "kiloclaw_scheduled_action_stages_scheduled_action_id_kiloclaw_scheduled_actions_id_fk", + "tableFrom": "kiloclaw_scheduled_action_stages", + "tableTo": "kiloclaw_scheduled_actions", + "columnsFrom": [ + "scheduled_action_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.kiloclaw_scheduled_action_targets": { + "name": "kiloclaw_scheduled_action_targets", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "scheduled_action_id": { + "name": "scheduled_action_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "stage_id": { + "name": "stage_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "instance_id": { + "name": "instance_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "source_image_tag": { + "name": "source_image_tag", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "target_image_tag": { + "name": "target_image_tag", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "applied_at": { + "name": "applied_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "skip_reason": { + "name": "skip_reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "error_message": { + "name": "error_message", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "UQ_kiloclaw_scheduled_action_targets_parent_instance": { + "name": "UQ_kiloclaw_scheduled_action_targets_parent_instance", + "columns": [ + { + "expression": "scheduled_action_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "instance_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_kiloclaw_scheduled_action_targets_stage": { + "name": "IDX_kiloclaw_scheduled_action_targets_stage", + "columns": [ + { + "expression": "stage_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_kiloclaw_scheduled_action_targets_pending_by_instance": { + "name": "IDX_kiloclaw_scheduled_action_targets_pending_by_instance", + "columns": [ + { + "expression": "instance_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"kiloclaw_scheduled_action_targets\".\"status\" = 'pending'", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "kiloclaw_scheduled_action_targets_scheduled_action_id_kiloclaw_scheduled_actions_id_fk": { + "name": "kiloclaw_scheduled_action_targets_scheduled_action_id_kiloclaw_scheduled_actions_id_fk", + "tableFrom": "kiloclaw_scheduled_action_targets", + "tableTo": "kiloclaw_scheduled_actions", + "columnsFrom": [ + "scheduled_action_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "kiloclaw_scheduled_action_targets_stage_id_kiloclaw_scheduled_action_stages_id_fk": { + "name": "kiloclaw_scheduled_action_targets_stage_id_kiloclaw_scheduled_action_stages_id_fk", + "tableFrom": "kiloclaw_scheduled_action_targets", + "tableTo": "kiloclaw_scheduled_action_stages", + "columnsFrom": [ + "stage_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "kiloclaw_scheduled_action_targets_instance_id_kiloclaw_instances_id_fk": { + "name": "kiloclaw_scheduled_action_targets_instance_id_kiloclaw_instances_id_fk", + "tableFrom": "kiloclaw_scheduled_action_targets", + "tableTo": "kiloclaw_instances", + "columnsFrom": [ + "instance_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "kiloclaw_scheduled_action_targets_user_id_kilocode_users_id_fk": { + "name": "kiloclaw_scheduled_action_targets_user_id_kilocode_users_id_fk", + "tableFrom": "kiloclaw_scheduled_action_targets", + "tableTo": "kilocode_users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.kiloclaw_scheduled_actions": { + "name": "kiloclaw_scheduled_actions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "action_type": { + "name": "action_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "target_image_tag": { + "name": "target_image_tag", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "override_pins": { + "name": "override_pins", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "notice_lead_hours": { + "name": "notice_lead_hours", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 24 + }, + "notice_subject": { + "name": "notice_subject", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "''" + }, + "notice_body": { + "name": "notice_body", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "''" + }, + "reason": { + "name": "reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'scheduled'" + }, + "created_by": { + "name": "created_by", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "started_at": { + "name": "started_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "completed_at": { + "name": "completed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "cancelled_at": { + "name": "cancelled_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "total_count": { + "name": "total_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "applied_count": { + "name": "applied_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "skipped_count": { + "name": "skipped_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "failed_count": { + "name": "failed_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + } + }, + "indexes": { + "IDX_kiloclaw_scheduled_actions_status": { + "name": "IDX_kiloclaw_scheduled_actions_status", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_kiloclaw_scheduled_actions_action_type": { + "name": "IDX_kiloclaw_scheduled_actions_action_type", + "columns": [ + { + "expression": "action_type", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_kiloclaw_scheduled_actions_created_by": { + "name": "IDX_kiloclaw_scheduled_actions_created_by", + "columns": [ + { + "expression": "created_by", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "kiloclaw_scheduled_actions_target_image_tag_kiloclaw_image_catalog_image_tag_fk": { + "name": "kiloclaw_scheduled_actions_target_image_tag_kiloclaw_image_catalog_image_tag_fk", + "tableFrom": "kiloclaw_scheduled_actions", + "tableTo": "kiloclaw_image_catalog", + "columnsFrom": [ + "target_image_tag" + ], + "columnsTo": [ + "image_tag" + ], + "onDelete": "restrict", + "onUpdate": "no action" + }, + "kiloclaw_scheduled_actions_created_by_kilocode_users_id_fk": { + "name": "kiloclaw_scheduled_actions_created_by_kilocode_users_id_fk", + "tableFrom": "kiloclaw_scheduled_actions", + "tableTo": "kilocode_users", + "columnsFrom": [ + "created_by" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.kiloclaw_subscription_change_log": { + "name": "kiloclaw_subscription_change_log", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "subscription_id": { + "name": "subscription_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "actor_type": { + "name": "actor_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "actor_id": { + "name": "actor_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "action": { + "name": "action", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "reason": { + "name": "reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "before_state": { + "name": "before_state", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "after_state": { + "name": "after_state", + "type": "jsonb", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "IDX_kiloclaw_subscription_change_log_subscription_created_at": { + "name": "IDX_kiloclaw_subscription_change_log_subscription_created_at", + "columns": [ + { + "expression": "subscription_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_kiloclaw_subscription_change_log_created_at": { + "name": "IDX_kiloclaw_subscription_change_log_created_at", + "columns": [ + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "kiloclaw_subscription_change_log_subscription_id_kiloclaw_subscriptions_id_fk": { + "name": "kiloclaw_subscription_change_log_subscription_id_kiloclaw_subscriptions_id_fk", + "tableFrom": "kiloclaw_subscription_change_log", + "tableTo": "kiloclaw_subscriptions", + "columnsFrom": [ + "subscription_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "kiloclaw_subscription_change_log_actor_type_check": { + "name": "kiloclaw_subscription_change_log_actor_type_check", + "value": "\"kiloclaw_subscription_change_log\".\"actor_type\" IN ('user', 'system')" + }, + "kiloclaw_subscription_change_log_action_check": { + "name": "kiloclaw_subscription_change_log_action_check", + "value": "\"kiloclaw_subscription_change_log\".\"action\" IN ('created', 'status_changed', 'plan_switched', 'period_advanced', 'canceled', 'reactivated', 'suspended', 'destruction_scheduled', 'reassigned', 'backfilled', 'payment_source_changed', 'schedule_changed', 'admin_override')" + } + }, + "isRLSEnabled": false + }, + "public.kiloclaw_subscriptions": { + "name": "kiloclaw_subscriptions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "stripe_subscription_id": { + "name": "stripe_subscription_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "stripe_schedule_id": { + "name": "stripe_schedule_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "transferred_to_subscription_id": { + "name": "transferred_to_subscription_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "instance_id": { + "name": "instance_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "access_origin": { + "name": "access_origin", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "payment_source": { + "name": "payment_source", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "kiloclaw_price_version": { + "name": "kiloclaw_price_version", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "plan": { + "name": "plan", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "scheduled_plan": { + "name": "scheduled_plan", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "scheduled_by": { + "name": "scheduled_by", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "cancel_at_period_end": { + "name": "cancel_at_period_end", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "pending_conversion": { + "name": "pending_conversion", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "trial_started_at": { + "name": "trial_started_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "trial_ends_at": { + "name": "trial_ends_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "current_period_start": { + "name": "current_period_start", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "current_period_end": { + "name": "current_period_end", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "credit_renewal_at": { + "name": "credit_renewal_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "commit_ends_at": { + "name": "commit_ends_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "past_due_since": { + "name": "past_due_since", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "suspended_at": { + "name": "suspended_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "destruction_deadline": { + "name": "destruction_deadline", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "auto_resume_requested_at": { + "name": "auto_resume_requested_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "auto_resume_retry_after": { + "name": "auto_resume_retry_after", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "auto_resume_attempt_count": { + "name": "auto_resume_attempt_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "auto_top_up_triggered_for_period": { + "name": "auto_top_up_triggered_for_period", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "IDX_kiloclaw_subscriptions_status": { + "name": "IDX_kiloclaw_subscriptions_status", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_kiloclaw_subscriptions_user_id": { + "name": "IDX_kiloclaw_subscriptions_user_id", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_kiloclaw_subscriptions_user_status": { + "name": "IDX_kiloclaw_subscriptions_user_status", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_kiloclaw_subscriptions_price_version": { + "name": "IDX_kiloclaw_subscriptions_price_version", + "columns": [ + { + "expression": "kiloclaw_price_version", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_kiloclaw_subscriptions_transferred_to": { + "name": "IDX_kiloclaw_subscriptions_transferred_to", + "columns": [ + { + "expression": "transferred_to_subscription_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_kiloclaw_subscriptions_stripe_schedule_id": { + "name": "IDX_kiloclaw_subscriptions_stripe_schedule_id", + "columns": [ + { + "expression": "stripe_schedule_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_kiloclaw_subscriptions_auto_resume_retry_after": { + "name": "IDX_kiloclaw_subscriptions_auto_resume_retry_after", + "columns": [ + { + "expression": "auto_resume_retry_after", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "UQ_kiloclaw_subscriptions_instance": { + "name": "UQ_kiloclaw_subscriptions_instance", + "columns": [ + { + "expression": "instance_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"kiloclaw_subscriptions\".\"instance_id\" is not null", + "concurrently": false, + "method": "btree", + "with": {} + }, + "UQ_kiloclaw_subscriptions_transferred_to": { + "name": "UQ_kiloclaw_subscriptions_transferred_to", + "columns": [ + { + "expression": "transferred_to_subscription_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"kiloclaw_subscriptions\".\"transferred_to_subscription_id\" is not null", + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_kiloclaw_subscriptions_earlybird_origin": { + "name": "IDX_kiloclaw_subscriptions_earlybird_origin", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "access_origin", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"kiloclaw_subscriptions\".\"access_origin\" = 'earlybird'", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "kiloclaw_subscriptions_user_id_kilocode_users_id_fk": { + "name": "kiloclaw_subscriptions_user_id_kilocode_users_id_fk", + "tableFrom": "kiloclaw_subscriptions", + "tableTo": "kilocode_users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "kiloclaw_subscriptions_transferred_to_subscription_id_kiloclaw_subscriptions_id_fk": { + "name": "kiloclaw_subscriptions_transferred_to_subscription_id_kiloclaw_subscriptions_id_fk", + "tableFrom": "kiloclaw_subscriptions", + "tableTo": "kiloclaw_subscriptions", + "columnsFrom": [ + "transferred_to_subscription_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "kiloclaw_subscriptions_instance_id_kiloclaw_instances_id_fk": { + "name": "kiloclaw_subscriptions_instance_id_kiloclaw_instances_id_fk", + "tableFrom": "kiloclaw_subscriptions", + "tableTo": "kiloclaw_instances", + "columnsFrom": [ + "instance_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "kiloclaw_subscriptions_stripe_subscription_id_unique": { + "name": "kiloclaw_subscriptions_stripe_subscription_id_unique", + "nullsNotDistinct": false, + "columns": [ + "stripe_subscription_id" + ] + } + }, + "policies": {}, + "checkConstraints": { + "kiloclaw_subscriptions_price_version_check": { + "name": "kiloclaw_subscriptions_price_version_check", + "value": "\"kiloclaw_subscriptions\".\"kiloclaw_price_version\" IN ('2026-03-19', '2026-05-10')" + }, + "kiloclaw_subscriptions_plan_check": { + "name": "kiloclaw_subscriptions_plan_check", + "value": "\"kiloclaw_subscriptions\".\"plan\" IN ('trial', 'commit', 'standard')" + }, + "kiloclaw_subscriptions_scheduled_plan_check": { + "name": "kiloclaw_subscriptions_scheduled_plan_check", + "value": "\"kiloclaw_subscriptions\".\"scheduled_plan\" IN ('commit', 'standard')" + }, + "kiloclaw_subscriptions_scheduled_by_check": { + "name": "kiloclaw_subscriptions_scheduled_by_check", + "value": "\"kiloclaw_subscriptions\".\"scheduled_by\" IN ('auto', 'user')" + }, + "kiloclaw_subscriptions_status_check": { + "name": "kiloclaw_subscriptions_status_check", + "value": "\"kiloclaw_subscriptions\".\"status\" IN ('trialing', 'active', 'past_due', 'canceled', 'unpaid')" + }, + "kiloclaw_subscriptions_access_origin_check": { + "name": "kiloclaw_subscriptions_access_origin_check", + "value": "\"kiloclaw_subscriptions\".\"access_origin\" IN ('earlybird')" + }, + "kiloclaw_subscriptions_payment_source_check": { + "name": "kiloclaw_subscriptions_payment_source_check", + "value": "\"kiloclaw_subscriptions\".\"payment_source\" IN ('stripe', 'credits')" + } + }, + "isRLSEnabled": false + }, + "public.kiloclaw_terminal_renewal_failures": { + "name": "kiloclaw_terminal_renewal_failures", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "subscription_id": { + "name": "subscription_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "renewal_boundary": { + "name": "renewal_boundary", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'unresolved'" + }, + "attempt_count": { + "name": "attempt_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "first_failure_at": { + "name": "first_failure_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "last_failure_at": { + "name": "last_failure_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "last_failure_code": { + "name": "last_failure_code", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "last_failure_message": { + "name": "last_failure_message", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "resolution_actor_type": { + "name": "resolution_actor_type", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "resolution_actor_id": { + "name": "resolution_actor_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "resolution_at": { + "name": "resolution_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "resolution_reason": { + "name": "resolution_reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "UQ_kiloclaw_terminal_renewal_failures_subscription_boundary": { + "name": "UQ_kiloclaw_terminal_renewal_failures_subscription_boundary", + "columns": [ + { + "expression": "subscription_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "renewal_boundary", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_kiloclaw_terminal_renewal_failures_unresolved": { + "name": "IDX_kiloclaw_terminal_renewal_failures_unresolved", + "columns": [ + { + "expression": "subscription_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "renewal_boundary", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"kiloclaw_terminal_renewal_failures\".\"status\" = 'unresolved'", + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_kiloclaw_terminal_renewal_failures_status_last_failure_at": { + "name": "IDX_kiloclaw_terminal_renewal_failures_status_last_failure_at", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "last_failure_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "kiloclaw_terminal_renewal_failures_subscription_id_kiloclaw_subscriptions_id_fk": { + "name": "kiloclaw_terminal_renewal_failures_subscription_id_kiloclaw_subscriptions_id_fk", + "tableFrom": "kiloclaw_terminal_renewal_failures", + "tableTo": "kiloclaw_subscriptions", + "columnsFrom": [ + "subscription_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "kiloclaw_terminal_renewal_failures_status_check": { + "name": "kiloclaw_terminal_renewal_failures_status_check", + "value": "\"kiloclaw_terminal_renewal_failures\".\"status\" IN ('unresolved', 'resolved', 'waived', 'superseded')" + }, + "kiloclaw_terminal_renewal_failures_last_failure_code_check": { + "name": "kiloclaw_terminal_renewal_failures_last_failure_code_check", + "value": "\"kiloclaw_terminal_renewal_failures\".\"last_failure_code\" IN ('credit_balance_read_failed', 'renewal_transaction_failed', 'auto_top_up_marker_write_failed', 'worker_timeout', 'poison_payload', 'queue_delivery_exhausted')" + }, + "kiloclaw_terminal_renewal_failures_resolution_actor_type_check": { + "name": "kiloclaw_terminal_renewal_failures_resolution_actor_type_check", + "value": "\"kiloclaw_terminal_renewal_failures\".\"resolution_actor_type\" IN ('operator', 'system')" + } + }, + "isRLSEnabled": false + }, + "public.kiloclaw_version_pins": { + "name": "kiloclaw_version_pins", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "instance_id": { + "name": "instance_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "image_tag": { + "name": "image_tag", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "pinned_by": { + "name": "pinned_by", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "reason": { + "name": "reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "kiloclaw_version_pins_instance_id_kiloclaw_instances_id_fk": { + "name": "kiloclaw_version_pins_instance_id_kiloclaw_instances_id_fk", + "tableFrom": "kiloclaw_version_pins", + "tableTo": "kiloclaw_instances", + "columnsFrom": [ + "instance_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "kiloclaw_version_pins_image_tag_kiloclaw_image_catalog_image_tag_fk": { + "name": "kiloclaw_version_pins_image_tag_kiloclaw_image_catalog_image_tag_fk", + "tableFrom": "kiloclaw_version_pins", + "tableTo": "kiloclaw_image_catalog", + "columnsFrom": [ + "image_tag" + ], + "columnsTo": [ + "image_tag" + ], + "onDelete": "restrict", + "onUpdate": "no action" + }, + "kiloclaw_version_pins_pinned_by_kilocode_users_id_fk": { + "name": "kiloclaw_version_pins_pinned_by_kilocode_users_id_fk", + "tableFrom": "kiloclaw_version_pins", + "tableTo": "kilocode_users", + "columnsFrom": [ + "pinned_by" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "kiloclaw_version_pins_instance_id_unique": { + "name": "kiloclaw_version_pins_instance_id_unique", + "nullsNotDistinct": false, + "columns": [ + "instance_id" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.kilocode_users": { + "name": "kilocode_users", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "google_user_email": { + "name": "google_user_email", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "google_user_name": { + "name": "google_user_name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "google_user_image_url": { + "name": "google_user_image_url", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "hosted_domain": { + "name": "hosted_domain", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "microdollars_used": { + "name": "microdollars_used", + "type": "bigint", + "primaryKey": false, + "notNull": true, + "default": "'0'" + }, + "kilo_pass_threshold": { + "name": "kilo_pass_threshold", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "stripe_customer_id": { + "name": "stripe_customer_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "app_store_account_token": { + "name": "app_store_account_token", + "type": "uuid", + "primaryKey": false, + "notNull": true, + "default": "pg_catalog.gen_random_uuid()" + }, + "is_admin": { + "name": "is_admin", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "is_super_admin": { + "name": "is_super_admin", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "can_view_sessions": { + "name": "can_view_sessions", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "can_manage_credits": { + "name": "can_manage_credits", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "total_microdollars_acquired": { + "name": "total_microdollars_acquired", + "type": "bigint", + "primaryKey": false, + "notNull": true, + "default": "'0'" + }, + "next_credit_expiration_at": { + "name": "next_credit_expiration_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "has_validation_stytch": { + "name": "has_validation_stytch", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "has_validation_novel_card_with_hold": { + "name": "has_validation_novel_card_with_hold", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "blocked_reason": { + "name": "blocked_reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "blocked_at": { + "name": "blocked_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "blocked_by_kilo_user_id": { + "name": "blocked_by_kilo_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "api_token_pepper": { + "name": "api_token_pepper", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "web_session_pepper": { + "name": "web_session_pepper", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "auto_top_up_enabled": { + "name": "auto_top_up_enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "is_bot": { + "name": "is_bot", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "kiloclaw_early_access": { + "name": "kiloclaw_early_access", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "default_model": { + "name": "default_model", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "cohorts": { + "name": "cohorts", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "completed_welcome_form": { + "name": "completed_welcome_form", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "linkedin_url": { + "name": "linkedin_url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "github_url": { + "name": "github_url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "discord_server_membership_verified_at": { + "name": "discord_server_membership_verified_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "openrouter_upstream_safety_identifier": { + "name": "openrouter_upstream_safety_identifier", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "openrouter_downstream_safety_identifier": { + "name": "openrouter_downstream_safety_identifier", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "vercel_downstream_safety_identifier": { + "name": "vercel_downstream_safety_identifier", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "customer_source": { + "name": "customer_source", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "signup_ip": { + "name": "signup_ip", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "account_deletion_requested_at": { + "name": "account_deletion_requested_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "normalized_email": { + "name": "normalized_email", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "email_domain": { + "name": "email_domain", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "personal_account_disabled": { + "name": "personal_account_disabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + } + }, + "indexes": { + "IDX_kilocode_users_signup_ip_created_at": { + "name": "IDX_kilocode_users_signup_ip_created_at", + "columns": [ + { + "expression": "signup_ip", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_kilocode_users_blocked_at": { + "name": "IDX_kilocode_users_blocked_at", + "columns": [ + { + "expression": "blocked_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_kilocode_users_blocked_by_kilo_user_id": { + "name": "IDX_kilocode_users_blocked_by_kilo_user_id", + "columns": [ + { + "expression": "blocked_by_kilo_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "UQ_kilocode_users_openrouter_upstream_safety_identifier": { + "name": "UQ_kilocode_users_openrouter_upstream_safety_identifier", + "columns": [ + { + "expression": "openrouter_upstream_safety_identifier", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"kilocode_users\".\"openrouter_upstream_safety_identifier\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "UQ_kilocode_users_openrouter_downstream_safety_identifier": { + "name": "UQ_kilocode_users_openrouter_downstream_safety_identifier", + "columns": [ + { + "expression": "openrouter_downstream_safety_identifier", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"kilocode_users\".\"openrouter_downstream_safety_identifier\" IS NOT NULL", + "concurrently": true, + "method": "btree", + "with": {} + }, + "UQ_kilocode_users_vercel_downstream_safety_identifier": { + "name": "UQ_kilocode_users_vercel_downstream_safety_identifier", + "columns": [ + { + "expression": "vercel_downstream_safety_identifier", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"kilocode_users\".\"vercel_downstream_safety_identifier\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_kilocode_users_normalized_email": { + "name": "IDX_kilocode_users_normalized_email", + "columns": [ + { + "expression": "normalized_email", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_kilocode_users_email_domain": { + "name": "IDX_kilocode_users_email_domain", + "columns": [ + { + "expression": "email_domain", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "kilocode_users_app_store_account_token_unique": { + "name": "kilocode_users_app_store_account_token_unique", + "nullsNotDistinct": false, + "columns": [ + "app_store_account_token" + ] + }, + "UQ_b1afacbcf43f2c7c4cb9f7e7faa": { + "name": "UQ_b1afacbcf43f2c7c4cb9f7e7faa", + "nullsNotDistinct": false, + "columns": [ + "google_user_email" + ] + } + }, + "policies": {}, + "checkConstraints": { + "blocked_reason_not_empty": { + "name": "blocked_reason_not_empty", + "value": "length(blocked_reason) > 0" + }, + "kilocode_users_is_super_admin_requires_admin_check": { + "name": "kilocode_users_is_super_admin_requires_admin_check", + "value": "NOT \"kilocode_users\".\"is_super_admin\" OR \"kilocode_users\".\"is_admin\"" + }, + "kilocode_users_can_view_sessions_requires_admin_check": { + "name": "kilocode_users_can_view_sessions_requires_admin_check", + "value": "NOT \"kilocode_users\".\"can_view_sessions\" OR \"kilocode_users\".\"is_admin\"" + }, + "kilocode_users_can_manage_credits_requires_admin_check": { + "name": "kilocode_users_can_manage_credits_requires_admin_check", + "value": "NOT \"kilocode_users\".\"can_manage_credits\" OR \"kilocode_users\".\"is_admin\"" + } + }, + "isRLSEnabled": false + }, + "public.magic_link_tokens": { + "name": "magic_link_tokens", + "schema": "", + "columns": { + "token_hash": { + "name": "token_hash", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "consumed_at": { + "name": "consumed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "attempts": { + "name": "attempts", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "reserved_until": { + "name": "reserved_until", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "purpose": { + "name": "purpose", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'magic_link'" + }, + "challenge_id": { + "name": "challenge_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "idx_magic_link_tokens_email": { + "name": "idx_magic_link_tokens_email", + "columns": [ + { + "expression": "email", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_magic_link_tokens_expires_at": { + "name": "idx_magic_link_tokens_expires_at", + "columns": [ + { + "expression": "expires_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "UQ_magic_link_tokens_challenge_id": { + "name": "UQ_magic_link_tokens_challenge_id", + "columns": [ + { + "expression": "challenge_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"magic_link_tokens\".\"challenge_id\" IS NOT NULL", + "concurrently": true, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "check_expires_at_future": { + "name": "check_expires_at_future", + "value": "\"magic_link_tokens\".\"expires_at\" > \"magic_link_tokens\".\"created_at\"" + }, + "check_magic_link_tokens_purpose": { + "name": "check_magic_link_tokens_purpose", + "value": "\"magic_link_tokens\".\"purpose\" IN ('magic_link', 'sign_in_code', 'data_export_download')" + } + }, + "isRLSEnabled": false + }, + "public.mcp_gateway_assignments": { + "name": "mcp_gateway_assignments", + "schema": "", + "columns": { + "assignment_id": { + "name": "assignment_id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "pg_catalog.gen_random_uuid()" + }, + "config_id": { + "name": "config_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "kilo_user_id": { + "name": "kilo_user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "assigned_by_kilo_user_id": { + "name": "assigned_by_kilo_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "single_user_slot": { + "name": "single_user_slot", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "revoked_at": { + "name": "revoked_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "UQ_mcp_gateway_assignments_active": { + "name": "UQ_mcp_gateway_assignments_active", + "columns": [ + { + "expression": "config_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "kilo_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"mcp_gateway_assignments\".\"revoked_at\" is null", + "concurrently": false, + "method": "btree", + "with": {} + }, + "UQ_mcp_gateway_assignments_single_user_slot": { + "name": "UQ_mcp_gateway_assignments_single_user_slot", + "columns": [ + { + "expression": "config_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "single_user_slot", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"mcp_gateway_assignments\".\"revoked_at\" is null and \"mcp_gateway_assignments\".\"single_user_slot\" is not null", + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_mcp_gateway_assignments_config": { + "name": "IDX_mcp_gateway_assignments_config", + "columns": [ + { + "expression": "config_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_mcp_gateway_assignments_user": { + "name": "IDX_mcp_gateway_assignments_user", + "columns": [ + { + "expression": "kilo_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "mcp_gateway_assignments_config_id_mcp_gateway_configs_config_id_fk": { + "name": "mcp_gateway_assignments_config_id_mcp_gateway_configs_config_id_fk", + "tableFrom": "mcp_gateway_assignments", + "tableTo": "mcp_gateway_configs", + "columnsFrom": [ + "config_id" + ], + "columnsTo": [ + "config_id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "mcp_gateway_assignments_kilo_user_id_kilocode_users_id_fk": { + "name": "mcp_gateway_assignments_kilo_user_id_kilocode_users_id_fk", + "tableFrom": "mcp_gateway_assignments", + "tableTo": "kilocode_users", + "columnsFrom": [ + "kilo_user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "mcp_gateway_assignments_assigned_by_kilo_user_id_kilocode_users_id_fk": { + "name": "mcp_gateway_assignments_assigned_by_kilo_user_id_kilocode_users_id_fk", + "tableFrom": "mcp_gateway_assignments", + "tableTo": "kilocode_users", + "columnsFrom": [ + "assigned_by_kilo_user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.mcp_gateway_audit_events": { + "name": "mcp_gateway_audit_events", + "schema": "", + "columns": { + "audit_event_id": { + "name": "audit_event_id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "pg_catalog.gen_random_uuid()" + }, + "actor_kilo_user_id": { + "name": "actor_kilo_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "owner_scope": { + "name": "owner_scope", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "owner_id": { + "name": "owner_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "config_id": { + "name": "config_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "connect_resource_id": { + "name": "connect_resource_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "instance_id": { + "name": "instance_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "oauth_grant_id": { + "name": "oauth_grant_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "event_type": { + "name": "event_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "outcome": { + "name": "outcome", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "correlation_metadata": { + "name": "correlation_metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "IDX_mcp_gateway_audit_events_config": { + "name": "IDX_mcp_gateway_audit_events_config", + "columns": [ + { + "expression": "config_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_mcp_gateway_audit_events_grant": { + "name": "IDX_mcp_gateway_audit_events_grant", + "columns": [ + { + "expression": "oauth_grant_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"mcp_gateway_audit_events\".\"oauth_grant_id\" is not null", + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_mcp_gateway_audit_events_owner": { + "name": "IDX_mcp_gateway_audit_events_owner", + "columns": [ + { + "expression": "owner_scope", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "owner_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_mcp_gateway_audit_events_created_at": { + "name": "IDX_mcp_gateway_audit_events_created_at", + "columns": [ + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "mcp_gateway_audit_events_actor_kilo_user_id_kilocode_users_id_fk": { + "name": "mcp_gateway_audit_events_actor_kilo_user_id_kilocode_users_id_fk", + "tableFrom": "mcp_gateway_audit_events", + "tableTo": "kilocode_users", + "columnsFrom": [ + "actor_kilo_user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "mcp_gateway_audit_events_config_id_mcp_gateway_configs_config_id_fk": { + "name": "mcp_gateway_audit_events_config_id_mcp_gateway_configs_config_id_fk", + "tableFrom": "mcp_gateway_audit_events", + "tableTo": "mcp_gateway_configs", + "columnsFrom": [ + "config_id" + ], + "columnsTo": [ + "config_id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "mcp_gateway_audit_events_connect_resource_id_mcp_gateway_connect_resources_connect_resource_id_fk": { + "name": "mcp_gateway_audit_events_connect_resource_id_mcp_gateway_connect_resources_connect_resource_id_fk", + "tableFrom": "mcp_gateway_audit_events", + "tableTo": "mcp_gateway_connect_resources", + "columnsFrom": [ + "connect_resource_id" + ], + "columnsTo": [ + "connect_resource_id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "mcp_gateway_audit_events_instance_id_mcp_gateway_connection_instances_instance_id_fk": { + "name": "mcp_gateway_audit_events_instance_id_mcp_gateway_connection_instances_instance_id_fk", + "tableFrom": "mcp_gateway_audit_events", + "tableTo": "mcp_gateway_connection_instances", + "columnsFrom": [ + "instance_id" + ], + "columnsTo": [ + "instance_id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "mcp_gateway_audit_events_oauth_grant_id_mcp_gateway_oauth_grants_oauth_grant_id_fk": { + "name": "mcp_gateway_audit_events_oauth_grant_id_mcp_gateway_oauth_grants_oauth_grant_id_fk", + "tableFrom": "mcp_gateway_audit_events", + "tableTo": "mcp_gateway_oauth_grants", + "columnsFrom": [ + "oauth_grant_id" + ], + "columnsTo": [ + "oauth_grant_id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "mcp_gateway_audit_events_owner_scope": { + "name": "mcp_gateway_audit_events_owner_scope", + "value": "\"mcp_gateway_audit_events\".\"owner_scope\" IN ('personal', 'organization')" + }, + "mcp_gateway_audit_events_outcome": { + "name": "mcp_gateway_audit_events_outcome", + "value": "\"mcp_gateway_audit_events\".\"outcome\" IN ('success', 'failure', 'blocked')" + } + }, + "isRLSEnabled": false + }, + "public.mcp_gateway_authorization_codes": { + "name": "mcp_gateway_authorization_codes", + "schema": "", + "columns": { + "authorization_code_id": { + "name": "authorization_code_id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "pg_catalog.gen_random_uuid()" + }, + "code_hash": { + "name": "code_hash", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "authorization_request_id": { + "name": "authorization_request_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "oauth_client_id": { + "name": "oauth_client_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "oauth_grant_id": { + "name": "oauth_grant_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "client_id": { + "name": "client_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "owner_scope": { + "name": "owner_scope", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "owner_id": { + "name": "owner_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "config_id": { + "name": "config_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "route_key": { + "name": "route_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "canonical_resource_url": { + "name": "canonical_resource_url", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "redirect_uri": { + "name": "redirect_uri", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "granted_scopes": { + "name": "granted_scopes", + "type": "text[]", + "primaryKey": false, + "notNull": true + }, + "code_challenge": { + "name": "code_challenge", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "code_challenge_method": { + "name": "code_challenge_method", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'S256'" + }, + "execution_context": { + "name": "execution_context", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "kilo_user_id": { + "name": "kilo_user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "instance_id": { + "name": "instance_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "consumed_at": { + "name": "consumed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "UQ_mcp_gateway_authorization_codes_code_hash": { + "name": "UQ_mcp_gateway_authorization_codes_code_hash", + "columns": [ + { + "expression": "code_hash", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_mcp_gateway_authorization_codes_expires_at": { + "name": "IDX_mcp_gateway_authorization_codes_expires_at", + "columns": [ + { + "expression": "expires_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_mcp_gateway_authorization_codes_client": { + "name": "IDX_mcp_gateway_authorization_codes_client", + "columns": [ + { + "expression": "oauth_client_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_mcp_gateway_authorization_codes_grant": { + "name": "IDX_mcp_gateway_authorization_codes_grant", + "columns": [ + { + "expression": "oauth_grant_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"mcp_gateway_authorization_codes\".\"oauth_grant_id\" is not null", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "mcp_gateway_authorization_codes_authorization_request_id_mcp_gateway_authorization_requests_authorization_request_id_fk": { + "name": "mcp_gateway_authorization_codes_authorization_request_id_mcp_gateway_authorization_requests_authorization_request_id_fk", + "tableFrom": "mcp_gateway_authorization_codes", + "tableTo": "mcp_gateway_authorization_requests", + "columnsFrom": [ + "authorization_request_id" + ], + "columnsTo": [ + "authorization_request_id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "mcp_gateway_authorization_codes_oauth_client_id_mcp_gateway_oauth_clients_oauth_client_id_fk": { + "name": "mcp_gateway_authorization_codes_oauth_client_id_mcp_gateway_oauth_clients_oauth_client_id_fk", + "tableFrom": "mcp_gateway_authorization_codes", + "tableTo": "mcp_gateway_oauth_clients", + "columnsFrom": [ + "oauth_client_id" + ], + "columnsTo": [ + "oauth_client_id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "mcp_gateway_authorization_codes_oauth_grant_id_mcp_gateway_oauth_grants_oauth_grant_id_fk": { + "name": "mcp_gateway_authorization_codes_oauth_grant_id_mcp_gateway_oauth_grants_oauth_grant_id_fk", + "tableFrom": "mcp_gateway_authorization_codes", + "tableTo": "mcp_gateway_oauth_grants", + "columnsFrom": [ + "oauth_grant_id" + ], + "columnsTo": [ + "oauth_grant_id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "mcp_gateway_authorization_codes_config_id_mcp_gateway_configs_config_id_fk": { + "name": "mcp_gateway_authorization_codes_config_id_mcp_gateway_configs_config_id_fk", + "tableFrom": "mcp_gateway_authorization_codes", + "tableTo": "mcp_gateway_configs", + "columnsFrom": [ + "config_id" + ], + "columnsTo": [ + "config_id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "mcp_gateway_authorization_codes_kilo_user_id_kilocode_users_id_fk": { + "name": "mcp_gateway_authorization_codes_kilo_user_id_kilocode_users_id_fk", + "tableFrom": "mcp_gateway_authorization_codes", + "tableTo": "kilocode_users", + "columnsFrom": [ + "kilo_user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "mcp_gateway_authorization_codes_instance_id_mcp_gateway_connection_instances_instance_id_fk": { + "name": "mcp_gateway_authorization_codes_instance_id_mcp_gateway_connection_instances_instance_id_fk", + "tableFrom": "mcp_gateway_authorization_codes", + "tableTo": "mcp_gateway_connection_instances", + "columnsFrom": [ + "instance_id" + ], + "columnsTo": [ + "instance_id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "mcp_gateway_authorization_codes_owner_scope": { + "name": "mcp_gateway_authorization_codes_owner_scope", + "value": "\"mcp_gateway_authorization_codes\".\"owner_scope\" IN ('personal', 'organization')" + } + }, + "isRLSEnabled": false + }, + "public.mcp_gateway_authorization_requests": { + "name": "mcp_gateway_authorization_requests", + "schema": "", + "columns": { + "authorization_request_id": { + "name": "authorization_request_id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "pg_catalog.gen_random_uuid()" + }, + "request_state_hash": { + "name": "request_state_hash", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "oauth_client_id": { + "name": "oauth_client_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "oauth_grant_id": { + "name": "oauth_grant_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "client_id": { + "name": "client_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "owner_scope": { + "name": "owner_scope", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "owner_id": { + "name": "owner_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "config_id": { + "name": "config_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "route_key": { + "name": "route_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "canonical_resource_url": { + "name": "canonical_resource_url", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "redirect_uri": { + "name": "redirect_uri", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "requested_scopes": { + "name": "requested_scopes", + "type": "text[]", + "primaryKey": false, + "notNull": true + }, + "granted_scopes": { + "name": "granted_scopes", + "type": "text[]", + "primaryKey": false, + "notNull": true + }, + "oauth_state": { + "name": "oauth_state", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "code_challenge": { + "name": "code_challenge", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "code_challenge_method": { + "name": "code_challenge_method", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'S256'" + }, + "execution_context": { + "name": "execution_context", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "kilo_user_id": { + "name": "kilo_user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "instance_id": { + "name": "instance_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "request_status": { + "name": "request_status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "consumed_at": { + "name": "consumed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "UQ_mcp_gateway_authorization_requests_state_hash": { + "name": "UQ_mcp_gateway_authorization_requests_state_hash", + "columns": [ + { + "expression": "request_state_hash", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_mcp_gateway_authorization_requests_config": { + "name": "IDX_mcp_gateway_authorization_requests_config", + "columns": [ + { + "expression": "config_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_mcp_gateway_authorization_requests_grant": { + "name": "IDX_mcp_gateway_authorization_requests_grant", + "columns": [ + { + "expression": "oauth_grant_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"mcp_gateway_authorization_requests\".\"oauth_grant_id\" is not null", + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_mcp_gateway_authorization_requests_user": { + "name": "IDX_mcp_gateway_authorization_requests_user", + "columns": [ + { + "expression": "kilo_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_mcp_gateway_authorization_requests_expires_at": { + "name": "IDX_mcp_gateway_authorization_requests_expires_at", + "columns": [ + { + "expression": "expires_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "mcp_gateway_authorization_requests_oauth_client_id_mcp_gateway_oauth_clients_oauth_client_id_fk": { + "name": "mcp_gateway_authorization_requests_oauth_client_id_mcp_gateway_oauth_clients_oauth_client_id_fk", + "tableFrom": "mcp_gateway_authorization_requests", + "tableTo": "mcp_gateway_oauth_clients", + "columnsFrom": [ + "oauth_client_id" + ], + "columnsTo": [ + "oauth_client_id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "mcp_gateway_authorization_requests_oauth_grant_id_mcp_gateway_oauth_grants_oauth_grant_id_fk": { + "name": "mcp_gateway_authorization_requests_oauth_grant_id_mcp_gateway_oauth_grants_oauth_grant_id_fk", + "tableFrom": "mcp_gateway_authorization_requests", + "tableTo": "mcp_gateway_oauth_grants", + "columnsFrom": [ + "oauth_grant_id" + ], + "columnsTo": [ + "oauth_grant_id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "mcp_gateway_authorization_requests_config_id_mcp_gateway_configs_config_id_fk": { + "name": "mcp_gateway_authorization_requests_config_id_mcp_gateway_configs_config_id_fk", + "tableFrom": "mcp_gateway_authorization_requests", + "tableTo": "mcp_gateway_configs", + "columnsFrom": [ + "config_id" + ], + "columnsTo": [ + "config_id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "mcp_gateway_authorization_requests_kilo_user_id_kilocode_users_id_fk": { + "name": "mcp_gateway_authorization_requests_kilo_user_id_kilocode_users_id_fk", + "tableFrom": "mcp_gateway_authorization_requests", + "tableTo": "kilocode_users", + "columnsFrom": [ + "kilo_user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "mcp_gateway_authorization_requests_instance_id_mcp_gateway_connection_instances_instance_id_fk": { + "name": "mcp_gateway_authorization_requests_instance_id_mcp_gateway_connection_instances_instance_id_fk", + "tableFrom": "mcp_gateway_authorization_requests", + "tableTo": "mcp_gateway_connection_instances", + "columnsFrom": [ + "instance_id" + ], + "columnsTo": [ + "instance_id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "mcp_gateway_authorization_requests_owner_scope": { + "name": "mcp_gateway_authorization_requests_owner_scope", + "value": "\"mcp_gateway_authorization_requests\".\"owner_scope\" IN ('personal', 'organization')" + }, + "mcp_gateway_authorization_requests_status": { + "name": "mcp_gateway_authorization_requests_status", + "value": "\"mcp_gateway_authorization_requests\".\"request_status\" IN ('pending', 'completed', 'error')" + } + }, + "isRLSEnabled": false + }, + "public.mcp_gateway_config_secrets": { + "name": "mcp_gateway_config_secrets", + "schema": "", + "columns": { + "config_secret_id": { + "name": "config_secret_id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "pg_catalog.gen_random_uuid()" + }, + "config_id": { + "name": "config_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "secret_kind": { + "name": "secret_kind", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "encrypted_secret": { + "name": "encrypted_secret", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "secret_version": { + "name": "secret_version", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 1 + }, + "revoked_at": { + "name": "revoked_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "UQ_mcp_gateway_config_secrets_active_kind": { + "name": "UQ_mcp_gateway_config_secrets_active_kind", + "columns": [ + { + "expression": "config_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "secret_kind", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"mcp_gateway_config_secrets\".\"revoked_at\" is null", + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_mcp_gateway_config_secrets_config": { + "name": "IDX_mcp_gateway_config_secrets_config", + "columns": [ + { + "expression": "config_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "mcp_gateway_config_secrets_config_id_mcp_gateway_configs_config_id_fk": { + "name": "mcp_gateway_config_secrets_config_id_mcp_gateway_configs_config_id_fk", + "tableFrom": "mcp_gateway_config_secrets", + "tableTo": "mcp_gateway_configs", + "columnsFrom": [ + "config_id" + ], + "columnsTo": [ + "config_id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "mcp_gateway_config_secrets_version_positive": { + "name": "mcp_gateway_config_secrets_version_positive", + "value": "\"mcp_gateway_config_secrets\".\"secret_version\" > 0" + }, + "mcp_gateway_config_secrets_kind": { + "name": "mcp_gateway_config_secrets_kind", + "value": "\"mcp_gateway_config_secrets\".\"secret_kind\" IN ('static_provider_credentials', 'dynamic_registration', 'static_headers')" + } + }, + "isRLSEnabled": false + }, + "public.mcp_gateway_configs": { + "name": "mcp_gateway_configs", + "schema": "", + "columns": { + "config_id": { + "name": "config_id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "pg_catalog.gen_random_uuid()" + }, + "owner_scope": { + "name": "owner_scope", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "owner_id": { + "name": "owner_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "remote_url": { + "name": "remote_url", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "auth_mode": { + "name": "auth_mode", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "sharing_mode": { + "name": "sharing_mode", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "provider_scopes": { + "name": "provider_scopes", + "type": "text[]", + "primaryKey": false, + "notNull": false + }, + "provider_scope_source": { + "name": "provider_scope_source", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'none'" + }, + "provider_resource": { + "name": "provider_resource", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "enabled": { + "name": "enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "path_passthrough": { + "name": "path_passthrough", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "config_version": { + "name": "config_version", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 1 + }, + "discovered_provider_metadata": { + "name": "discovered_provider_metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "registry_metadata": { + "name": "registry_metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "auxiliary_headers": { + "name": "auxiliary_headers", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "created_by_kilo_user_id": { + "name": "created_by_kilo_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "deleted_at": { + "name": "deleted_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "IDX_mcp_gateway_configs_owner": { + "name": "IDX_mcp_gateway_configs_owner", + "columns": [ + { + "expression": "owner_scope", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "owner_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_mcp_gateway_configs_enabled": { + "name": "IDX_mcp_gateway_configs_enabled", + "columns": [ + { + "expression": "enabled", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_mcp_gateway_configs_remote_url": { + "name": "IDX_mcp_gateway_configs_remote_url", + "columns": [ + { + "expression": "remote_url", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "mcp_gateway_configs_created_by_kilo_user_id_kilocode_users_id_fk": { + "name": "mcp_gateway_configs_created_by_kilo_user_id_kilocode_users_id_fk", + "tableFrom": "mcp_gateway_configs", + "tableTo": "kilocode_users", + "columnsFrom": [ + "created_by_kilo_user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "mcp_gateway_configs_name_not_empty": { + "name": "mcp_gateway_configs_name_not_empty", + "value": "length(trim(\"mcp_gateway_configs\".\"name\")) > 0" + }, + "mcp_gateway_configs_config_version_positive": { + "name": "mcp_gateway_configs_config_version_positive", + "value": "\"mcp_gateway_configs\".\"config_version\" > 0" + }, + "mcp_gateway_configs_personal_single_user": { + "name": "mcp_gateway_configs_personal_single_user", + "value": "\"mcp_gateway_configs\".\"owner_scope\" <> 'personal' OR \"mcp_gateway_configs\".\"sharing_mode\" = 'single_user'" + }, + "mcp_gateway_configs_owner_scope": { + "name": "mcp_gateway_configs_owner_scope", + "value": "\"mcp_gateway_configs\".\"owner_scope\" IN ('personal', 'organization')" + }, + "mcp_gateway_configs_auth_mode": { + "name": "mcp_gateway_configs_auth_mode", + "value": "\"mcp_gateway_configs\".\"auth_mode\" IN ('none', 'static_headers', 'oauth_dynamic', 'oauth_static')" + }, + "mcp_gateway_configs_sharing_mode": { + "name": "mcp_gateway_configs_sharing_mode", + "value": "\"mcp_gateway_configs\".\"sharing_mode\" IN ('single_user', 'multi_user')" + }, + "mcp_gateway_configs_provider_scope_source": { + "name": "mcp_gateway_configs_provider_scope_source", + "value": "\"mcp_gateway_configs\".\"provider_scope_source\" IN ('none', 'discovered', 'override')" + } + }, + "isRLSEnabled": false + }, + "public.mcp_gateway_connect_resources": { + "name": "mcp_gateway_connect_resources", + "schema": "", + "columns": { + "connect_resource_id": { + "name": "connect_resource_id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "pg_catalog.gen_random_uuid()" + }, + "config_id": { + "name": "config_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "owner_scope": { + "name": "owner_scope", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "owner_id": { + "name": "owner_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "route_key": { + "name": "route_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "canonical_url": { + "name": "canonical_url", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "route_status": { + "name": "route_status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'active'" + }, + "route_version": { + "name": "route_version", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 1 + }, + "rotated_at": { + "name": "rotated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "revoked_at": { + "name": "revoked_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "UQ_mcp_gateway_connect_resources_route_key": { + "name": "UQ_mcp_gateway_connect_resources_route_key", + "columns": [ + { + "expression": "route_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "UQ_mcp_gateway_connect_resources_active_config": { + "name": "UQ_mcp_gateway_connect_resources_active_config", + "columns": [ + { + "expression": "config_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"mcp_gateway_connect_resources\".\"route_status\" = 'active'", + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_mcp_gateway_connect_resources_config": { + "name": "IDX_mcp_gateway_connect_resources_config", + "columns": [ + { + "expression": "config_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_mcp_gateway_connect_resources_canonical_url": { + "name": "IDX_mcp_gateway_connect_resources_canonical_url", + "columns": [ + { + "expression": "canonical_url", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "mcp_gateway_connect_resources_config_id_mcp_gateway_configs_config_id_fk": { + "name": "mcp_gateway_connect_resources_config_id_mcp_gateway_configs_config_id_fk", + "tableFrom": "mcp_gateway_connect_resources", + "tableTo": "mcp_gateway_configs", + "columnsFrom": [ + "config_id" + ], + "columnsTo": [ + "config_id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "mcp_gateway_connect_resources_route_key_format": { + "name": "mcp_gateway_connect_resources_route_key_format", + "value": "\"mcp_gateway_connect_resources\".\"route_key\" ~ '^[A-Za-z0-9_-]{32,}$'" + }, + "mcp_gateway_connect_resources_route_version_positive": { + "name": "mcp_gateway_connect_resources_route_version_positive", + "value": "\"mcp_gateway_connect_resources\".\"route_version\" > 0" + }, + "mcp_gateway_connect_resources_owner_scope": { + "name": "mcp_gateway_connect_resources_owner_scope", + "value": "\"mcp_gateway_connect_resources\".\"owner_scope\" IN ('personal', 'organization')" + }, + "mcp_gateway_connect_resources_route_status": { + "name": "mcp_gateway_connect_resources_route_status", + "value": "\"mcp_gateway_connect_resources\".\"route_status\" IN ('active', 'rotated', 'revoked')" + } + }, + "isRLSEnabled": false + }, + "public.mcp_gateway_connection_instances": { + "name": "mcp_gateway_connection_instances", + "schema": "", + "columns": { + "instance_id": { + "name": "instance_id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "pg_catalog.gen_random_uuid()" + }, + "config_id": { + "name": "config_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "owner_scope": { + "name": "owner_scope", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "owner_id": { + "name": "owner_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "kilo_user_id": { + "name": "kilo_user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "instance_status": { + "name": "instance_status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'active'" + }, + "instance_version": { + "name": "instance_version", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 1 + }, + "last_used_at": { + "name": "last_used_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "revoked_at": { + "name": "revoked_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "removed_at": { + "name": "removed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "UQ_mcp_gateway_connection_instances_non_terminal": { + "name": "UQ_mcp_gateway_connection_instances_non_terminal", + "columns": [ + { + "expression": "owner_scope", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "owner_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "kilo_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "config_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"mcp_gateway_connection_instances\".\"instance_status\" IN ('active', 'needs_reauth')", + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_mcp_gateway_connection_instances_config": { + "name": "IDX_mcp_gateway_connection_instances_config", + "columns": [ + { + "expression": "config_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_mcp_gateway_connection_instances_user": { + "name": "IDX_mcp_gateway_connection_instances_user", + "columns": [ + { + "expression": "kilo_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "mcp_gateway_connection_instances_config_id_mcp_gateway_configs_config_id_fk": { + "name": "mcp_gateway_connection_instances_config_id_mcp_gateway_configs_config_id_fk", + "tableFrom": "mcp_gateway_connection_instances", + "tableTo": "mcp_gateway_configs", + "columnsFrom": [ + "config_id" + ], + "columnsTo": [ + "config_id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "mcp_gateway_connection_instances_kilo_user_id_kilocode_users_id_fk": { + "name": "mcp_gateway_connection_instances_kilo_user_id_kilocode_users_id_fk", + "tableFrom": "mcp_gateway_connection_instances", + "tableTo": "kilocode_users", + "columnsFrom": [ + "kilo_user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "mcp_gateway_connection_instances_version_positive": { + "name": "mcp_gateway_connection_instances_version_positive", + "value": "\"mcp_gateway_connection_instances\".\"instance_version\" > 0" + }, + "mcp_gateway_connection_instances_owner_scope": { + "name": "mcp_gateway_connection_instances_owner_scope", + "value": "\"mcp_gateway_connection_instances\".\"owner_scope\" IN ('personal', 'organization')" + }, + "mcp_gateway_connection_instances_status": { + "name": "mcp_gateway_connection_instances_status", + "value": "\"mcp_gateway_connection_instances\".\"instance_status\" IN ('active', 'needs_reauth', 'revoked', 'removed')" + } + }, + "isRLSEnabled": false + }, + "public.mcp_gateway_oauth_clients": { + "name": "mcp_gateway_oauth_clients", + "schema": "", + "columns": { + "oauth_client_id": { + "name": "oauth_client_id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "pg_catalog.gen_random_uuid()" + }, + "client_id": { + "name": "client_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "client_name": { + "name": "client_name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "registration_token_hash": { + "name": "registration_token_hash", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "client_secret_hash": { + "name": "client_secret_hash", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "token_endpoint_auth_method": { + "name": "token_endpoint_auth_method", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "redirect_uris": { + "name": "redirect_uris", + "type": "text[]", + "primaryKey": false, + "notNull": true + }, + "grant_types": { + "name": "grant_types", + "type": "text[]", + "primaryKey": false, + "notNull": true + }, + "response_types": { + "name": "response_types", + "type": "text[]", + "primaryKey": false, + "notNull": true + }, + "declared_scopes": { + "name": "declared_scopes", + "type": "text[]", + "primaryKey": false, + "notNull": true + }, + "registration_access_token_expires_at": { + "name": "registration_access_token_expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "deleted_at": { + "name": "deleted_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "UQ_mcp_gateway_oauth_clients_client_id": { + "name": "UQ_mcp_gateway_oauth_clients_client_id", + "columns": [ + { + "expression": "client_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "UQ_mcp_gateway_oauth_clients_registration_token_hash": { + "name": "UQ_mcp_gateway_oauth_clients_registration_token_hash", + "columns": [ + { + "expression": "registration_token_hash", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_mcp_gateway_oauth_clients_deleted_at": { + "name": "IDX_mcp_gateway_oauth_clients_deleted_at", + "columns": [ + { + "expression": "deleted_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "mcp_gateway_oauth_clients_client_id_format": { + "name": "mcp_gateway_oauth_clients_client_id_format", + "value": "\"mcp_gateway_oauth_clients\".\"client_id\" ~ '^[A-Za-z0-9._-]+:[A-Za-z0-9._-]+$'" + }, + "mcp_gateway_oauth_clients_auth_method": { + "name": "mcp_gateway_oauth_clients_auth_method", + "value": "\"mcp_gateway_oauth_clients\".\"token_endpoint_auth_method\" IN ('none', 'client_secret_post', 'client_secret_basic')" + } + }, + "isRLSEnabled": false + }, + "public.mcp_gateway_oauth_grants": { + "name": "mcp_gateway_oauth_grants", + "schema": "", + "columns": { + "oauth_grant_id": { + "name": "oauth_grant_id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "pg_catalog.gen_random_uuid()" + }, + "oauth_client_id": { + "name": "oauth_client_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "kilo_user_id": { + "name": "kilo_user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "owner_scope": { + "name": "owner_scope", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "owner_id": { + "name": "owner_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "config_id": { + "name": "config_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "connect_resource_id": { + "name": "connect_resource_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "instance_id": { + "name": "instance_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "redirect_uri": { + "name": "redirect_uri", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "granted_scopes": { + "name": "granted_scopes", + "type": "text[]", + "primaryKey": false, + "notNull": true + }, + "execution_context": { + "name": "execution_context", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "config_version": { + "name": "config_version", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "grant_status": { + "name": "grant_status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'active'" + }, + "approved_at": { + "name": "approved_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "last_used_at": { + "name": "last_used_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "revoked_at": { + "name": "revoked_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "revocation_reason": { + "name": "revocation_reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "UQ_mcp_gateway_oauth_grants_active_binding": { + "name": "UQ_mcp_gateway_oauth_grants_active_binding", + "columns": [ + { + "expression": "oauth_client_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "kilo_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "connect_resource_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "redirect_uri", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"mcp_gateway_oauth_grants\".\"revoked_at\" is null and \"mcp_gateway_oauth_grants\".\"grant_status\" in ('pending', 'active')", + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_mcp_gateway_oauth_grants_client": { + "name": "IDX_mcp_gateway_oauth_grants_client", + "columns": [ + { + "expression": "oauth_client_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_mcp_gateway_oauth_grants_user": { + "name": "IDX_mcp_gateway_oauth_grants_user", + "columns": [ + { + "expression": "kilo_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_mcp_gateway_oauth_grants_config": { + "name": "IDX_mcp_gateway_oauth_grants_config", + "columns": [ + { + "expression": "config_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_mcp_gateway_oauth_grants_owner": { + "name": "IDX_mcp_gateway_oauth_grants_owner", + "columns": [ + { + "expression": "owner_scope", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "owner_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_mcp_gateway_oauth_grants_resource": { + "name": "IDX_mcp_gateway_oauth_grants_resource", + "columns": [ + { + "expression": "connect_resource_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_mcp_gateway_oauth_grants_instance": { + "name": "IDX_mcp_gateway_oauth_grants_instance", + "columns": [ + { + "expression": "instance_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_mcp_gateway_oauth_grants_revoked_at": { + "name": "IDX_mcp_gateway_oauth_grants_revoked_at", + "columns": [ + { + "expression": "revoked_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "mcp_gateway_oauth_grants_oauth_client_id_mcp_gateway_oauth_clients_oauth_client_id_fk": { + "name": "mcp_gateway_oauth_grants_oauth_client_id_mcp_gateway_oauth_clients_oauth_client_id_fk", + "tableFrom": "mcp_gateway_oauth_grants", + "tableTo": "mcp_gateway_oauth_clients", + "columnsFrom": [ + "oauth_client_id" + ], + "columnsTo": [ + "oauth_client_id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "mcp_gateway_oauth_grants_kilo_user_id_kilocode_users_id_fk": { + "name": "mcp_gateway_oauth_grants_kilo_user_id_kilocode_users_id_fk", + "tableFrom": "mcp_gateway_oauth_grants", + "tableTo": "kilocode_users", + "columnsFrom": [ + "kilo_user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "mcp_gateway_oauth_grants_config_id_mcp_gateway_configs_config_id_fk": { + "name": "mcp_gateway_oauth_grants_config_id_mcp_gateway_configs_config_id_fk", + "tableFrom": "mcp_gateway_oauth_grants", + "tableTo": "mcp_gateway_configs", + "columnsFrom": [ + "config_id" + ], + "columnsTo": [ + "config_id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "mcp_gateway_oauth_grants_connect_resource_id_mcp_gateway_connect_resources_connect_resource_id_fk": { + "name": "mcp_gateway_oauth_grants_connect_resource_id_mcp_gateway_connect_resources_connect_resource_id_fk", + "tableFrom": "mcp_gateway_oauth_grants", + "tableTo": "mcp_gateway_connect_resources", + "columnsFrom": [ + "connect_resource_id" + ], + "columnsTo": [ + "connect_resource_id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "mcp_gateway_oauth_grants_instance_id_mcp_gateway_connection_instances_instance_id_fk": { + "name": "mcp_gateway_oauth_grants_instance_id_mcp_gateway_connection_instances_instance_id_fk", + "tableFrom": "mcp_gateway_oauth_grants", + "tableTo": "mcp_gateway_connection_instances", + "columnsFrom": [ + "instance_id" + ], + "columnsTo": [ + "instance_id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "mcp_gateway_oauth_grants_config_version_positive": { + "name": "mcp_gateway_oauth_grants_config_version_positive", + "value": "\"mcp_gateway_oauth_grants\".\"config_version\" > 0" + }, + "mcp_gateway_oauth_grants_owner_scope": { + "name": "mcp_gateway_oauth_grants_owner_scope", + "value": "\"mcp_gateway_oauth_grants\".\"owner_scope\" IN ('personal', 'organization')" + }, + "mcp_gateway_oauth_grants_status": { + "name": "mcp_gateway_oauth_grants_status", + "value": "\"mcp_gateway_oauth_grants\".\"grant_status\" IN ('pending', 'active', 'revoked')" + } + }, + "isRLSEnabled": false + }, + "public.mcp_gateway_pending_provider_authorizations": { + "name": "mcp_gateway_pending_provider_authorizations", + "schema": "", + "columns": { + "pending_provider_authorization_id": { + "name": "pending_provider_authorization_id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "pg_catalog.gen_random_uuid()" + }, + "state_hash": { + "name": "state_hash", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "authorization_request_id": { + "name": "authorization_request_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "oauth_grant_id": { + "name": "oauth_grant_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "config_id": { + "name": "config_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "instance_id": { + "name": "instance_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "owner_scope": { + "name": "owner_scope", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "owner_id": { + "name": "owner_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "kilo_user_id": { + "name": "kilo_user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "route_key": { + "name": "route_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "canonical_resource_url": { + "name": "canonical_resource_url", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "remote_url": { + "name": "remote_url", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "auth_mode": { + "name": "auth_mode", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "provider_authorization_endpoint": { + "name": "provider_authorization_endpoint", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "provider_token_endpoint": { + "name": "provider_token_endpoint", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "encrypted_state": { + "name": "encrypted_state", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "execution_context": { + "name": "execution_context", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "config_version": { + "name": "config_version", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "pending_status": { + "name": "pending_status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "consumed_at": { + "name": "consumed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "UQ_mcp_gateway_pending_provider_authorizations_state_hash": { + "name": "UQ_mcp_gateway_pending_provider_authorizations_state_hash", + "columns": [ + { + "expression": "state_hash", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_mcp_gateway_pending_provider_authorizations_config": { + "name": "IDX_mcp_gateway_pending_provider_authorizations_config", + "columns": [ + { + "expression": "config_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_mcp_gateway_pending_provider_authorizations_grant": { + "name": "IDX_mcp_gateway_pending_provider_authorizations_grant", + "columns": [ + { + "expression": "oauth_grant_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"mcp_gateway_pending_provider_authorizations\".\"oauth_grant_id\" is not null", + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_mcp_gateway_pending_provider_authorizations_expires_at": { + "name": "IDX_mcp_gateway_pending_provider_authorizations_expires_at", + "columns": [ + { + "expression": "expires_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "mcp_gateway_pending_provider_authorizations_authorization_request_id_mcp_gateway_authorization_requests_authorization_request_id_fk": { + "name": "mcp_gateway_pending_provider_authorizations_authorization_request_id_mcp_gateway_authorization_requests_authorization_request_id_fk", + "tableFrom": "mcp_gateway_pending_provider_authorizations", + "tableTo": "mcp_gateway_authorization_requests", + "columnsFrom": [ + "authorization_request_id" + ], + "columnsTo": [ + "authorization_request_id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "mcp_gateway_pending_provider_authorizations_oauth_grant_id_mcp_gateway_oauth_grants_oauth_grant_id_fk": { + "name": "mcp_gateway_pending_provider_authorizations_oauth_grant_id_mcp_gateway_oauth_grants_oauth_grant_id_fk", + "tableFrom": "mcp_gateway_pending_provider_authorizations", + "tableTo": "mcp_gateway_oauth_grants", + "columnsFrom": [ + "oauth_grant_id" + ], + "columnsTo": [ + "oauth_grant_id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "mcp_gateway_pending_provider_authorizations_config_id_mcp_gateway_configs_config_id_fk": { + "name": "mcp_gateway_pending_provider_authorizations_config_id_mcp_gateway_configs_config_id_fk", + "tableFrom": "mcp_gateway_pending_provider_authorizations", + "tableTo": "mcp_gateway_configs", + "columnsFrom": [ + "config_id" + ], + "columnsTo": [ + "config_id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "mcp_gateway_pending_provider_authorizations_instance_id_mcp_gateway_connection_instances_instance_id_fk": { + "name": "mcp_gateway_pending_provider_authorizations_instance_id_mcp_gateway_connection_instances_instance_id_fk", + "tableFrom": "mcp_gateway_pending_provider_authorizations", + "tableTo": "mcp_gateway_connection_instances", + "columnsFrom": [ + "instance_id" + ], + "columnsTo": [ + "instance_id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "mcp_gateway_pending_provider_authorizations_kilo_user_id_kilocode_users_id_fk": { + "name": "mcp_gateway_pending_provider_authorizations_kilo_user_id_kilocode_users_id_fk", + "tableFrom": "mcp_gateway_pending_provider_authorizations", + "tableTo": "kilocode_users", + "columnsFrom": [ + "kilo_user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "mcp_gateway_pending_provider_authorizations_config_version_positive": { + "name": "mcp_gateway_pending_provider_authorizations_config_version_positive", + "value": "\"mcp_gateway_pending_provider_authorizations\".\"config_version\" > 0" + }, + "mcp_gateway_pending_provider_authorizations_owner_scope": { + "name": "mcp_gateway_pending_provider_authorizations_owner_scope", + "value": "\"mcp_gateway_pending_provider_authorizations\".\"owner_scope\" IN ('personal', 'organization')" + }, + "mcp_gateway_pending_provider_authorizations_auth_mode": { + "name": "mcp_gateway_pending_provider_authorizations_auth_mode", + "value": "\"mcp_gateway_pending_provider_authorizations\".\"auth_mode\" IN ('none', 'static_headers', 'oauth_dynamic', 'oauth_static')" + }, + "mcp_gateway_pending_provider_authorizations_status": { + "name": "mcp_gateway_pending_provider_authorizations_status", + "value": "\"mcp_gateway_pending_provider_authorizations\".\"pending_status\" IN ('pending', 'completed', 'error')" + } + }, + "isRLSEnabled": false + }, + "public.mcp_gateway_provider_grants": { + "name": "mcp_gateway_provider_grants", + "schema": "", + "columns": { + "provider_grant_id": { + "name": "provider_grant_id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "pg_catalog.gen_random_uuid()" + }, + "instance_id": { + "name": "instance_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "encrypted_grant": { + "name": "encrypted_grant", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "provider_subject": { + "name": "provider_subject", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "grant_scope": { + "name": "grant_scope", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "grant_status": { + "name": "grant_status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'active'" + }, + "grant_version": { + "name": "grant_version", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 1 + }, + "last_used_at": { + "name": "last_used_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "revoked_at": { + "name": "revoked_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "UQ_mcp_gateway_provider_grants_active_instance": { + "name": "UQ_mcp_gateway_provider_grants_active_instance", + "columns": [ + { + "expression": "instance_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"mcp_gateway_provider_grants\".\"grant_status\" = 'active'", + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_mcp_gateway_provider_grants_instance": { + "name": "IDX_mcp_gateway_provider_grants_instance", + "columns": [ + { + "expression": "instance_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "mcp_gateway_provider_grants_instance_id_mcp_gateway_connection_instances_instance_id_fk": { + "name": "mcp_gateway_provider_grants_instance_id_mcp_gateway_connection_instances_instance_id_fk", + "tableFrom": "mcp_gateway_provider_grants", + "tableTo": "mcp_gateway_connection_instances", + "columnsFrom": [ + "instance_id" + ], + "columnsTo": [ + "instance_id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "mcp_gateway_provider_grants_version_positive": { + "name": "mcp_gateway_provider_grants_version_positive", + "value": "\"mcp_gateway_provider_grants\".\"grant_version\" > 0" + }, + "mcp_gateway_provider_grants_status": { + "name": "mcp_gateway_provider_grants_status", + "value": "\"mcp_gateway_provider_grants\".\"grant_status\" IN ('active', 'revoked')" + } + }, + "isRLSEnabled": false + }, + "public.mcp_gateway_rate_limit_windows": { + "name": "mcp_gateway_rate_limit_windows", + "schema": "", + "columns": { + "rate_limit_window_id": { + "name": "rate_limit_window_id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "pg_catalog.gen_random_uuid()" + }, + "ip_hash": { + "name": "ip_hash", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "window_started_at": { + "name": "window_started_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "attempt_count": { + "name": "attempt_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "UQ_mcp_gateway_rate_limit_windows_ip_window": { + "name": "UQ_mcp_gateway_rate_limit_windows_ip_window", + "columns": [ + { + "expression": "ip_hash", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "window_started_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_mcp_gateway_rate_limit_windows_window": { + "name": "IDX_mcp_gateway_rate_limit_windows_window", + "columns": [ + { + "expression": "window_started_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "mcp_gateway_rate_limit_windows_attempt_count_non_negative": { + "name": "mcp_gateway_rate_limit_windows_attempt_count_non_negative", + "value": "\"mcp_gateway_rate_limit_windows\".\"attempt_count\" >= 0" + } + }, + "isRLSEnabled": false + }, + "public.mcp_gateway_refresh_tokens": { + "name": "mcp_gateway_refresh_tokens", + "schema": "", + "columns": { + "refresh_token_id": { + "name": "refresh_token_id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "pg_catalog.gen_random_uuid()" + }, + "token_hash": { + "name": "token_hash", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "rotated_from_refresh_token_id": { + "name": "rotated_from_refresh_token_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "oauth_client_id": { + "name": "oauth_client_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "oauth_grant_id": { + "name": "oauth_grant_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "client_id": { + "name": "client_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "owner_scope": { + "name": "owner_scope", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "owner_id": { + "name": "owner_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "config_id": { + "name": "config_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "route_key": { + "name": "route_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "canonical_resource_url": { + "name": "canonical_resource_url", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "granted_scopes": { + "name": "granted_scopes", + "type": "text[]", + "primaryKey": false, + "notNull": true + }, + "execution_context": { + "name": "execution_context", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "kilo_user_id": { + "name": "kilo_user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "instance_id": { + "name": "instance_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "consumed_at": { + "name": "consumed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "revoked_at": { + "name": "revoked_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "UQ_mcp_gateway_refresh_tokens_token_hash": { + "name": "UQ_mcp_gateway_refresh_tokens_token_hash", + "columns": [ + { + "expression": "token_hash", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_mcp_gateway_refresh_tokens_user": { + "name": "IDX_mcp_gateway_refresh_tokens_user", + "columns": [ + { + "expression": "kilo_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_mcp_gateway_refresh_tokens_grant": { + "name": "IDX_mcp_gateway_refresh_tokens_grant", + "columns": [ + { + "expression": "oauth_grant_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"mcp_gateway_refresh_tokens\".\"oauth_grant_id\" is not null", + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_mcp_gateway_refresh_tokens_config": { + "name": "IDX_mcp_gateway_refresh_tokens_config", + "columns": [ + { + "expression": "config_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_mcp_gateway_refresh_tokens_consumed_at": { + "name": "IDX_mcp_gateway_refresh_tokens_consumed_at", + "columns": [ + { + "expression": "consumed_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "mcp_gateway_refresh_tokens_oauth_client_id_mcp_gateway_oauth_clients_oauth_client_id_fk": { + "name": "mcp_gateway_refresh_tokens_oauth_client_id_mcp_gateway_oauth_clients_oauth_client_id_fk", + "tableFrom": "mcp_gateway_refresh_tokens", + "tableTo": "mcp_gateway_oauth_clients", + "columnsFrom": [ + "oauth_client_id" + ], + "columnsTo": [ + "oauth_client_id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "mcp_gateway_refresh_tokens_oauth_grant_id_mcp_gateway_oauth_grants_oauth_grant_id_fk": { + "name": "mcp_gateway_refresh_tokens_oauth_grant_id_mcp_gateway_oauth_grants_oauth_grant_id_fk", + "tableFrom": "mcp_gateway_refresh_tokens", + "tableTo": "mcp_gateway_oauth_grants", + "columnsFrom": [ + "oauth_grant_id" + ], + "columnsTo": [ + "oauth_grant_id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "mcp_gateway_refresh_tokens_config_id_mcp_gateway_configs_config_id_fk": { + "name": "mcp_gateway_refresh_tokens_config_id_mcp_gateway_configs_config_id_fk", + "tableFrom": "mcp_gateway_refresh_tokens", + "tableTo": "mcp_gateway_configs", + "columnsFrom": [ + "config_id" + ], + "columnsTo": [ + "config_id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "mcp_gateway_refresh_tokens_kilo_user_id_kilocode_users_id_fk": { + "name": "mcp_gateway_refresh_tokens_kilo_user_id_kilocode_users_id_fk", + "tableFrom": "mcp_gateway_refresh_tokens", + "tableTo": "kilocode_users", + "columnsFrom": [ + "kilo_user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "mcp_gateway_refresh_tokens_instance_id_mcp_gateway_connection_instances_instance_id_fk": { + "name": "mcp_gateway_refresh_tokens_instance_id_mcp_gateway_connection_instances_instance_id_fk", + "tableFrom": "mcp_gateway_refresh_tokens", + "tableTo": "mcp_gateway_connection_instances", + "columnsFrom": [ + "instance_id" + ], + "columnsTo": [ + "instance_id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "mcp_gateway_refresh_tokens_owner_scope": { + "name": "mcp_gateway_refresh_tokens_owner_scope", + "value": "\"mcp_gateway_refresh_tokens\".\"owner_scope\" IN ('personal', 'organization')" + } + }, + "isRLSEnabled": false + }, + "public.microdollar_usage": { + "name": "microdollar_usage", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "pg_catalog.gen_random_uuid()" + }, + "kilo_user_id": { + "name": "kilo_user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "cost": { + "name": "cost", + "type": "bigint", + "primaryKey": false, + "notNull": true + }, + "input_tokens": { + "name": "input_tokens", + "type": "bigint", + "primaryKey": false, + "notNull": true + }, + "output_tokens": { + "name": "output_tokens", + "type": "bigint", + "primaryKey": false, + "notNull": true + }, + "cache_write_tokens": { + "name": "cache_write_tokens", + "type": "bigint", + "primaryKey": false, + "notNull": true + }, + "cache_hit_tokens": { + "name": "cache_hit_tokens", + "type": "bigint", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "provider": { + "name": "provider", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "model": { + "name": "model", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "requested_model": { + "name": "requested_model", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "cache_discount": { + "name": "cache_discount", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "has_error": { + "name": "has_error", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "abuse_classification": { + "name": "abuse_classification", + "type": "smallint", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "organization_id": { + "name": "organization_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "inference_provider": { + "name": "inference_provider", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "project_id": { + "name": "project_id", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "idx_created_at": { + "name": "idx_created_at", + "columns": [ + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_abuse_classification": { + "name": "idx_abuse_classification", + "columns": [ + { + "expression": "abuse_classification", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_kilo_user_id_created_at2": { + "name": "idx_kilo_user_id_created_at2", + "columns": [ + { + "expression": "kilo_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_microdollar_usage_organization_id": { + "name": "idx_microdollar_usage_organization_id", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"microdollar_usage\".\"organization_id\" is not null", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.microdollar_usage_daily": { + "name": "microdollar_usage_daily", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "pg_catalog.gen_random_uuid()" + }, + "kilo_user_id": { + "name": "kilo_user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "organization_id": { + "name": "organization_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "usage_date": { + "name": "usage_date", + "type": "date", + "primaryKey": false, + "notNull": true + }, + "total_cost_microdollars": { + "name": "total_cost_microdollars", + "type": "bigint", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "idx_microdollar_usage_daily_personal": { + "name": "idx_microdollar_usage_daily_personal", + "columns": [ + { + "expression": "kilo_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "usage_date", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"microdollar_usage_daily\".\"organization_id\" is null", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_microdollar_usage_daily_org": { + "name": "idx_microdollar_usage_daily_org", + "columns": [ + { + "expression": "kilo_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "usage_date", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"microdollar_usage_daily\".\"organization_id\" is not null", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.microdollar_usage_daily_repairs": { + "name": "microdollar_usage_daily_repairs", + "schema": "", + "columns": { + "usage_id": { + "name": "usage_id", + "type": "uuid", + "primaryKey": true, + "notNull": true + }, + "kilo_user_id": { + "name": "kilo_user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "organization_id": { + "name": "organization_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "usage_date": { + "name": "usage_date", + "type": "date", + "primaryKey": false, + "notNull": true + }, + "next_attempt_at": { + "name": "next_attempt_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "claimed_at": { + "name": "claimed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "claim_token": { + "name": "claim_token", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "attempt_count": { + "name": "attempt_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "last_error_redacted": { + "name": "last_error_redacted", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "IDX_microdollar_usage_daily_repairs_claim": { + "name": "IDX_microdollar_usage_daily_repairs_claim", + "columns": [ + { + "expression": "attempt_count", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "next_attempt_at", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "claimed_at", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "usage_date", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "usage_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "microdollar_usage_daily_repairs_usage_id_microdollar_usage_id_fk": { + "name": "microdollar_usage_daily_repairs_usage_id_microdollar_usage_id_fk", + "tableFrom": "microdollar_usage_daily_repairs", + "tableTo": "microdollar_usage", + "columnsFrom": [ + "usage_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "microdollar_usage_daily_repairs_attempt_count_check": { + "name": "microdollar_usage_daily_repairs_attempt_count_check", + "value": "\"microdollar_usage_daily_repairs\".\"attempt_count\" >= 0" + }, + "microdollar_usage_daily_repairs_claim_token_check": { + "name": "microdollar_usage_daily_repairs_claim_token_check", + "value": "(\"microdollar_usage_daily_repairs\".\"claimed_at\" IS NULL AND \"microdollar_usage_daily_repairs\".\"claim_token\" IS NULL) OR (\"microdollar_usage_daily_repairs\".\"claimed_at\" IS NOT NULL AND \"microdollar_usage_daily_repairs\".\"claim_token\" IS NOT NULL)" + } + }, + "isRLSEnabled": false + }, + "public.microdollar_usage_metadata": { + "name": "microdollar_usage_metadata", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "message_id": { + "name": "message_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "http_user_agent_id": { + "name": "http_user_agent_id", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "http_ip_id": { + "name": "http_ip_id", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "vercel_ip_city_id": { + "name": "vercel_ip_city_id", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "vercel_ip_country_id": { + "name": "vercel_ip_country_id", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "vercel_ip_latitude": { + "name": "vercel_ip_latitude", + "type": "real", + "primaryKey": false, + "notNull": false + }, + "vercel_ip_longitude": { + "name": "vercel_ip_longitude", + "type": "real", + "primaryKey": false, + "notNull": false + }, + "ja4_digest_id": { + "name": "ja4_digest_id", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "user_prompt_prefix": { + "name": "user_prompt_prefix", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "system_prompt_prefix_id": { + "name": "system_prompt_prefix_id", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "system_prompt_length": { + "name": "system_prompt_length", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "max_tokens": { + "name": "max_tokens", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "has_middle_out_transform": { + "name": "has_middle_out_transform", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "status_code": { + "name": "status_code", + "type": "smallint", + "primaryKey": false, + "notNull": false + }, + "upstream_id": { + "name": "upstream_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "finish_reason_id": { + "name": "finish_reason_id", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "latency": { + "name": "latency", + "type": "real", + "primaryKey": false, + "notNull": false + }, + "moderation_latency": { + "name": "moderation_latency", + "type": "real", + "primaryKey": false, + "notNull": false + }, + "generation_time": { + "name": "generation_time", + "type": "real", + "primaryKey": false, + "notNull": false + }, + "is_byok": { + "name": "is_byok", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "is_user_byok": { + "name": "is_user_byok", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "streamed": { + "name": "streamed", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "cancelled": { + "name": "cancelled", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "editor_name_id": { + "name": "editor_name_id", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "api_kind_id": { + "name": "api_kind_id", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "has_tools": { + "name": "has_tools", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "machine_id": { + "name": "machine_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "feature_id": { + "name": "feature_id", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "session_id": { + "name": "session_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "mode_id": { + "name": "mode_id", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "auto_model_id": { + "name": "auto_model_id", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "market_cost": { + "name": "market_cost", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "is_free": { + "name": "is_free", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "abuse_delay": { + "name": "abuse_delay", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "abuse_downgraded_from": { + "name": "abuse_downgraded_from", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "idx_microdollar_usage_metadata_created_at": { + "name": "idx_microdollar_usage_metadata_created_at", + "columns": [ + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_microdollar_usage_metadata_session_id": { + "name": "idx_microdollar_usage_metadata_session_id", + "columns": [ + { + "expression": "session_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"microdollar_usage_metadata\".\"session_id\" is not null", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "microdollar_usage_metadata_http_user_agent_id_http_user_agent_http_user_agent_id_fk": { + "name": "microdollar_usage_metadata_http_user_agent_id_http_user_agent_http_user_agent_id_fk", + "tableFrom": "microdollar_usage_metadata", + "tableTo": "http_user_agent", + "columnsFrom": [ + "http_user_agent_id" + ], + "columnsTo": [ + "http_user_agent_id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "microdollar_usage_metadata_http_ip_id_http_ip_http_ip_id_fk": { + "name": "microdollar_usage_metadata_http_ip_id_http_ip_http_ip_id_fk", + "tableFrom": "microdollar_usage_metadata", + "tableTo": "http_ip", + "columnsFrom": [ + "http_ip_id" + ], + "columnsTo": [ + "http_ip_id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "microdollar_usage_metadata_vercel_ip_city_id_vercel_ip_city_vercel_ip_city_id_fk": { + "name": "microdollar_usage_metadata_vercel_ip_city_id_vercel_ip_city_vercel_ip_city_id_fk", + "tableFrom": "microdollar_usage_metadata", + "tableTo": "vercel_ip_city", + "columnsFrom": [ + "vercel_ip_city_id" + ], + "columnsTo": [ + "vercel_ip_city_id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "microdollar_usage_metadata_vercel_ip_country_id_vercel_ip_country_vercel_ip_country_id_fk": { + "name": "microdollar_usage_metadata_vercel_ip_country_id_vercel_ip_country_vercel_ip_country_id_fk", + "tableFrom": "microdollar_usage_metadata", + "tableTo": "vercel_ip_country", + "columnsFrom": [ + "vercel_ip_country_id" + ], + "columnsTo": [ + "vercel_ip_country_id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "microdollar_usage_metadata_ja4_digest_id_ja4_digest_ja4_digest_id_fk": { + "name": "microdollar_usage_metadata_ja4_digest_id_ja4_digest_ja4_digest_id_fk", + "tableFrom": "microdollar_usage_metadata", + "tableTo": "ja4_digest", + "columnsFrom": [ + "ja4_digest_id" + ], + "columnsTo": [ + "ja4_digest_id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "microdollar_usage_metadata_system_prompt_prefix_id_system_prompt_prefix_system_prompt_prefix_id_fk": { + "name": "microdollar_usage_metadata_system_prompt_prefix_id_system_prompt_prefix_system_prompt_prefix_id_fk", + "tableFrom": "microdollar_usage_metadata", + "tableTo": "system_prompt_prefix", + "columnsFrom": [ + "system_prompt_prefix_id" + ], + "columnsTo": [ + "system_prompt_prefix_id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.mode": { + "name": "mode", + "schema": "", + "columns": { + "mode_id": { + "name": "mode_id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "mode": { + "name": "mode", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "UQ_mode": { + "name": "UQ_mode", + "columns": [ + { + "expression": "mode", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.model_stats": { + "name": "model_stats", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "is_active": { + "name": "is_active", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": true + }, + "is_featured": { + "name": "is_featured", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "is_stealth": { + "name": "is_stealth", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "is_recommended": { + "name": "is_recommended", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "openrouter_id": { + "name": "openrouter_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "slug": { + "name": "slug", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "aa_slug": { + "name": "aa_slug", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "model_creator": { + "name": "model_creator", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "creator_slug": { + "name": "creator_slug", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "release_date": { + "name": "release_date", + "type": "date", + "primaryKey": false, + "notNull": false + }, + "price_input": { + "name": "price_input", + "type": "numeric(10, 6)", + "primaryKey": false, + "notNull": false + }, + "price_output": { + "name": "price_output", + "type": "numeric(10, 6)", + "primaryKey": false, + "notNull": false + }, + "coding_index": { + "name": "coding_index", + "type": "numeric(5, 2)", + "primaryKey": false, + "notNull": false + }, + "speed_tokens_per_sec": { + "name": "speed_tokens_per_sec", + "type": "numeric(8, 2)", + "primaryKey": false, + "notNull": false + }, + "context_length": { + "name": "context_length", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "max_output_tokens": { + "name": "max_output_tokens", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "input_modalities": { + "name": "input_modalities", + "type": "text[]", + "primaryKey": false, + "notNull": false + }, + "openrouter_data": { + "name": "openrouter_data", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "benchmarks": { + "name": "benchmarks", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "chart_data": { + "name": "chart_data", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "IDX_model_stats_openrouter_id": { + "name": "IDX_model_stats_openrouter_id", + "columns": [ + { + "expression": "openrouter_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_model_stats_slug": { + "name": "IDX_model_stats_slug", + "columns": [ + { + "expression": "slug", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_model_stats_is_active": { + "name": "IDX_model_stats_is_active", + "columns": [ + { + "expression": "is_active", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_model_stats_creator_slug": { + "name": "IDX_model_stats_creator_slug", + "columns": [ + { + "expression": "creator_slug", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_model_stats_price_input": { + "name": "IDX_model_stats_price_input", + "columns": [ + { + "expression": "price_input", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_model_stats_coding_index": { + "name": "IDX_model_stats_coding_index", + "columns": [ + { + "expression": "coding_index", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_model_stats_context_length": { + "name": "IDX_model_stats_context_length", + "columns": [ + { + "expression": "context_length", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "model_stats_openrouter_id_unique": { + "name": "model_stats_openrouter_id_unique", + "nullsNotDistinct": false, + "columns": [ + "openrouter_id" + ] + }, + "model_stats_slug_unique": { + "name": "model_stats_slug_unique", + "nullsNotDistinct": false, + "columns": [ + "slug" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.model_eval_ingestions": { + "name": "model_eval_ingestions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "bench_eval_name": { + "name": "bench_eval_name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "bench_eval_url": { + "name": "bench_eval_url", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "provider": { + "name": "provider", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "model": { + "name": "model", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "model_stats_id": { + "name": "model_stats_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "variant": { + "name": "variant", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "task_source": { + "name": "task_source", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "n_total_trials": { + "name": "n_total_trials", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "n_attempts": { + "name": "n_attempts", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "total_score": { + "name": "total_score", + "type": "numeric(14, 6)", + "primaryKey": false, + "notNull": true + }, + "overall_score": { + "name": "overall_score", + "type": "numeric(12, 8)", + "primaryKey": false, + "notNull": true + }, + "n_errored": { + "name": "n_errored", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "avg_cost_microdollars": { + "name": "avg_cost_microdollars", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "total_cost_microdollars": { + "name": "total_cost_microdollars", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "avg_input_tokens": { + "name": "avg_input_tokens", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "total_input_tokens": { + "name": "total_input_tokens", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "avg_output_tokens": { + "name": "avg_output_tokens", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "total_output_tokens": { + "name": "total_output_tokens", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "avg_cache_read_tokens": { + "name": "avg_cache_read_tokens", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "total_cache_read_tokens": { + "name": "total_cache_read_tokens", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "avg_execution_ms": { + "name": "avg_execution_ms", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "promoted_at": { + "name": "promoted_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "promoted_by_email": { + "name": "promoted_by_email", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "promotion_note": { + "name": "promotion_note", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "IDX_model_eval_ingestions_lookup": { + "name": "IDX_model_eval_ingestions_lookup", + "columns": [ + { + "expression": "provider", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "model", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "variant", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "task_source", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "promoted_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_model_eval_ingestions_model_stats": { + "name": "IDX_model_eval_ingestions_model_stats", + "columns": [ + { + "expression": "model_stats_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_model_eval_ingestions_promoted_by_email_lower": { + "name": "IDX_model_eval_ingestions_promoted_by_email_lower", + "columns": [ + { + "expression": "LOWER(\"promoted_by_email\")", + "asc": true, + "isExpression": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "model_eval_ingestions_model_stats_id_model_stats_id_fk": { + "name": "model_eval_ingestions_model_stats_id_model_stats_id_fk", + "tableFrom": "model_eval_ingestions", + "tableTo": "model_stats", + "columnsFrom": [ + "model_stats_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "model_eval_ingestions_bench_eval_name_unique": { + "name": "model_eval_ingestions_bench_eval_name_unique", + "nullsNotDistinct": false, + "columns": [ + "bench_eval_name" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.model_experiment": { + "name": "model_experiment", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "pg_catalog.gen_random_uuid()" + }, + "public_model_id": { + "name": "public_model_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'draft'" + }, + "is_archived": { + "name": "is_archived", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "created_by_user_id": { + "name": "created_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "started_at": { + "name": "started_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "ended_at": { + "name": "ended_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "UQ_model_experiment_public_model_id_routing": { + "name": "UQ_model_experiment_public_model_id_routing", + "columns": [ + { + "expression": "public_model_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"model_experiment\".\"status\" IN ('active', 'paused')", + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_model_experiment_status": { + "name": "IDX_model_experiment_status", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "model_experiment_created_by_user_id_kilocode_users_id_fk": { + "name": "model_experiment_created_by_user_id_kilocode_users_id_fk", + "tableFrom": "model_experiment", + "tableTo": "kilocode_users", + "columnsFrom": [ + "created_by_user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "model_experiment_status_valid": { + "name": "model_experiment_status_valid", + "value": "\"model_experiment\".\"status\" IN ('draft', 'active', 'paused', 'completed')" + }, + "model_experiment_active_not_archived": { + "name": "model_experiment_active_not_archived", + "value": "\"model_experiment\".\"status\" <> 'active' OR \"model_experiment\".\"is_archived\" = false" + } + }, + "isRLSEnabled": false + }, + "public.model_experiment_request": { + "name": "model_experiment_request", + "schema": "", + "columns": { + "usage_id": { + "name": "usage_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "variant_version_id": { + "name": "variant_version_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "allocation_subject": { + "name": "allocation_subject", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "client_request_id": { + "name": "client_request_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "request_kind": { + "name": "request_kind", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "request_body_sha256": { + "name": "request_body_sha256", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "was_truncated": { + "name": "was_truncated", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "IDX_model_experiment_request_variant_version_created_at": { + "name": "IDX_model_experiment_request_variant_version_created_at", + "columns": [ + { + "expression": "variant_version_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_model_experiment_request_client_request_id": { + "name": "IDX_model_experiment_request_client_request_id", + "columns": [ + { + "expression": "client_request_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"model_experiment_request\".\"client_request_id\" is not null", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "model_experiment_request_usage_id_microdollar_usage_id_fk": { + "name": "model_experiment_request_usage_id_microdollar_usage_id_fk", + "tableFrom": "model_experiment_request", + "tableTo": "microdollar_usage", + "columnsFrom": [ + "usage_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "model_experiment_request_variant_version_id_model_experiment_variant_version_id_fk": { + "name": "model_experiment_request_variant_version_id_model_experiment_variant_version_id_fk", + "tableFrom": "model_experiment_request", + "tableTo": "model_experiment_variant_version", + "columnsFrom": [ + "variant_version_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "model_experiment_request_usage_id_created_at_pk": { + "name": "model_experiment_request_usage_id_created_at_pk", + "columns": [ + "usage_id", + "created_at" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "model_experiment_request_allocation_subject_valid": { + "name": "model_experiment_request_allocation_subject_valid", + "value": "\"model_experiment_request\".\"allocation_subject\" IN ('user', 'machine', 'ip')" + }, + "model_experiment_request_request_kind_valid": { + "name": "model_experiment_request_request_kind_valid", + "value": "\"model_experiment_request\".\"request_kind\" IN ('chat_completions', 'messages', 'responses')" + }, + "model_experiment_request_request_body_sha256_format": { + "name": "model_experiment_request_request_body_sha256_format", + "value": "\"model_experiment_request\".\"request_body_sha256\" ~ '^[0-9a-f]{64}$' OR \"model_experiment_request\".\"request_body_sha256\" IN ('__failed__', '__deleted__')" + } + }, + "isRLSEnabled": false + }, + "public.model_experiment_variant": { + "name": "model_experiment_variant", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "pg_catalog.gen_random_uuid()" + }, + "experiment_id": { + "name": "experiment_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "label": { + "name": "label", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "weight": { + "name": "weight", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "IDX_model_experiment_variant_experiment_id": { + "name": "IDX_model_experiment_variant_experiment_id", + "columns": [ + { + "expression": "experiment_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "model_experiment_variant_experiment_id_model_experiment_id_fk": { + "name": "model_experiment_variant_experiment_id_model_experiment_id_fk", + "tableFrom": "model_experiment_variant", + "tableTo": "model_experiment", + "columnsFrom": [ + "experiment_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "UQ_model_experiment_variant_experiment_label": { + "name": "UQ_model_experiment_variant_experiment_label", + "nullsNotDistinct": false, + "columns": [ + "experiment_id", + "label" + ] + } + }, + "policies": {}, + "checkConstraints": { + "model_experiment_variant_weight_positive": { + "name": "model_experiment_variant_weight_positive", + "value": "\"model_experiment_variant\".\"weight\" > 0" + } + }, + "isRLSEnabled": false + }, + "public.model_experiment_variant_version": { + "name": "model_experiment_variant_version", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "pg_catalog.gen_random_uuid()" + }, + "variant_id": { + "name": "variant_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "upstream": { + "name": "upstream", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "encrypted_api_key": { + "name": "encrypted_api_key", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "effective_at": { + "name": "effective_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "created_by": { + "name": "created_by", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "IDX_model_experiment_variant_version_variant_effective": { + "name": "IDX_model_experiment_variant_version_variant_effective", + "columns": [ + { + "expression": "variant_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "effective_at", + "isExpression": false, + "asc": false, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "model_experiment_variant_version_variant_id_model_experiment_variant_id_fk": { + "name": "model_experiment_variant_version_variant_id_model_experiment_variant_id_fk", + "tableFrom": "model_experiment_variant_version", + "tableTo": "model_experiment_variant", + "columnsFrom": [ + "variant_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "model_experiment_variant_version_created_by_kilocode_users_id_fk": { + "name": "model_experiment_variant_version_created_by_kilocode_users_id_fk", + "tableFrom": "model_experiment_variant_version", + "tableTo": "kilocode_users", + "columnsFrom": [ + "created_by" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.models_by_provider": { + "name": "models_by_provider", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "data": { + "name": "data", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "openrouter": { + "name": "openrouter", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "vercel": { + "name": "vercel", + "type": "jsonb", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.native_admission_challenges": { + "name": "native_admission_challenges", + "schema": "", + "columns": { + "challenge": { + "name": "challenge", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "consumed_at": { + "name": "consumed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "IDX_native_admission_challenges_expires_at": { + "name": "IDX_native_admission_challenges_expires_at", + "columns": [ + { + "expression": "expires_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.native_attested_keys": { + "name": "native_attested_keys", + "schema": "", + "columns": { + "key_id": { + "name": "key_id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "kilo_user_id": { + "name": "kilo_user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "platform": { + "name": "platform", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "public_key": { + "name": "public_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "sign_count": { + "name": "sign_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "last_used_at": { + "name": "last_used_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "attested_at": { + "name": "attested_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "IDX_native_attested_keys_kilo_user_id": { + "name": "IDX_native_attested_keys_kilo_user_id", + "columns": [ + { + "expression": "kilo_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "native_attested_keys_kilo_user_id_kilocode_users_id_fk": { + "name": "native_attested_keys_kilo_user_id_kilocode_users_id_fk", + "tableFrom": "native_attested_keys", + "tableTo": "kilocode_users", + "columnsFrom": [ + "kilo_user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "native_attested_keys_platform_check": { + "name": "native_attested_keys_platform_check", + "value": "\"native_attested_keys\".\"platform\" IN ('ios', 'android')" + } + }, + "isRLSEnabled": false + }, + "public.openai_chatgpt_connections": { + "name": "openai_chatgpt_connections", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "pg_catalog.gen_random_uuid()" + }, + "kilo_user_id": { + "name": "kilo_user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "organization_id": { + "name": "organization_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "encrypted_connection": { + "name": "encrypted_connection", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "is_enabled": { + "name": "is_enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "created_by": { + "name": "created_by", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "UQ_openai_chatgpt_connections_personal": { + "name": "UQ_openai_chatgpt_connections_personal", + "columns": [ + { + "expression": "kilo_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"openai_chatgpt_connections\".\"organization_id\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "UQ_openai_chatgpt_connections_org_member": { + "name": "UQ_openai_chatgpt_connections_org_member", + "columns": [ + { + "expression": "kilo_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"openai_chatgpt_connections\".\"organization_id\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_openai_chatgpt_connections_organization_id": { + "name": "IDX_openai_chatgpt_connections_organization_id", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "openai_chatgpt_connections_kilo_user_id_kilocode_users_id_fk": { + "name": "openai_chatgpt_connections_kilo_user_id_kilocode_users_id_fk", + "tableFrom": "openai_chatgpt_connections", + "tableTo": "kilocode_users", + "columnsFrom": [ + "kilo_user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "openai_chatgpt_connections_organization_id_organizations_id_fk": { + "name": "openai_chatgpt_connections_organization_id_organizations_id_fk", + "tableFrom": "openai_chatgpt_connections", + "tableTo": "organizations", + "columnsFrom": [ + "organization_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.operation_ledgers": { + "name": "operation_ledgers", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "pg_catalog.gen_random_uuid()" + }, + "operation_key": { + "name": "operation_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "domain": { + "name": "domain", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "intent": { + "name": "intent", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "kilo_user_id": { + "name": "kilo_user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "organization_id": { + "name": "organization_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "resource_key": { + "name": "resource_key", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "provider_ref": { + "name": "provider_ref", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "taxonomy": { + "name": "taxonomy", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'admitted'" + }, + "outcome_code": { + "name": "outcome_code", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "canonical_result": { + "name": "canonical_result", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "admitted_at": { + "name": "admitted_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "settled_at": { + "name": "settled_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "lease_expires_at": { + "name": "lease_expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "UQ_operation_ledgers_kilo_user_id_domain_operation_key": { + "name": "UQ_operation_ledgers_kilo_user_id_domain_operation_key", + "columns": [ + { + "expression": "kilo_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "domain", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "operation_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_operation_ledgers_status_expires_at": { + "name": "IDX_operation_ledgers_status_expires_at", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "expires_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_operation_ledgers_provider_ref": { + "name": "IDX_operation_ledgers_provider_ref", + "columns": [ + { + "expression": "provider_ref", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"operation_ledgers\".\"provider_ref\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.organization_audit_logs": { + "name": "organization_audit_logs", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "pg_catalog.gen_random_uuid()" + }, + "action": { + "name": "action", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "actor_id": { + "name": "actor_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "actor_email": { + "name": "actor_email", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "actor_name": { + "name": "actor_name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "organization_id": { + "name": "organization_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "message": { + "name": "message", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "IDX_organization_audit_logs_organization_id": { + "name": "IDX_organization_audit_logs_organization_id", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_organization_audit_logs_action": { + "name": "IDX_organization_audit_logs_action", + "columns": [ + { + "expression": "action", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_organization_audit_logs_actor_id": { + "name": "IDX_organization_audit_logs_actor_id", + "columns": [ + { + "expression": "actor_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_organization_audit_logs_created_at": { + "name": "IDX_organization_audit_logs_created_at", + "columns": [ + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.organization_domain_claims": { + "name": "organization_domain_claims", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "pg_catalog.gen_random_uuid()" + }, + "organization_id": { + "name": "organization_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "domain": { + "name": "domain", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "workos_organization_id": { + "name": "workos_organization_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "workos_domain_id": { + "name": "workos_domain_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "verified_at": { + "name": "verified_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "UQ_organization_domain_claims_verified_domain": { + "name": "UQ_organization_domain_claims_verified_domain", + "columns": [ + { + "expression": "domain", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"organization_domain_claims\".\"status\" = 'verified'", + "concurrently": false, + "method": "btree", + "with": {} + }, + "UQ_organization_domain_claims_workos_domain_id": { + "name": "UQ_organization_domain_claims_workos_domain_id", + "columns": [ + { + "expression": "workos_domain_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"organization_domain_claims\".\"workos_domain_id\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_organization_domain_claims_organization_id": { + "name": "IDX_organization_domain_claims_organization_id", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "organization_domain_claims_organization_id_organizations_id_fk": { + "name": "organization_domain_claims_organization_id_organizations_id_fk", + "tableFrom": "organization_domain_claims", + "tableTo": "organizations", + "columnsFrom": [ + "organization_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "UQ_organization_domain_claims_organization_domain": { + "name": "UQ_organization_domain_claims_organization_domain", + "nullsNotDistinct": false, + "columns": [ + "organization_id", + "domain" + ] + } + }, + "policies": {}, + "checkConstraints": { + "organization_domain_claims_canonical_domain_check": { + "name": "organization_domain_claims_canonical_domain_check", + "value": "length(\"organization_domain_claims\".\"domain\") BETWEEN 1 AND 253 AND \"organization_domain_claims\".\"domain\" = lower(btrim(\"organization_domain_claims\".\"domain\"))" + }, + "organization_domain_claims_status_check": { + "name": "organization_domain_claims_status_check", + "value": "\"organization_domain_claims\".\"status\" IN ('pending', 'verified')" + }, + "organization_domain_claims_verification_shape_check": { + "name": "organization_domain_claims_verification_shape_check", + "value": "(\"organization_domain_claims\".\"status\" = 'pending' AND \"organization_domain_claims\".\"verified_at\" IS NULL)\n OR (\"organization_domain_claims\".\"status\" = 'verified' AND \"organization_domain_claims\".\"verified_at\" IS NOT NULL AND \"organization_domain_claims\".\"workos_organization_id\" IS NOT NULL AND \"organization_domain_claims\".\"workos_domain_id\" IS NOT NULL)" + } + }, + "isRLSEnabled": false + }, + "public.organization_group_memberships": { + "name": "organization_group_memberships", + "schema": "", + "columns": { + "organization_id": { + "name": "organization_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "group_id": { + "name": "group_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "kilo_user_id": { + "name": "kilo_user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "assigned_by_kilo_user_id": { + "name": "assigned_by_kilo_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "IDX_organization_group_memberships_organization_user": { + "name": "IDX_organization_group_memberships_organization_user", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "kilo_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "FK_organization_group_memberships_group": { + "name": "FK_organization_group_memberships_group", + "tableFrom": "organization_group_memberships", + "tableTo": "organization_groups", + "columnsFrom": [ + "organization_id", + "group_id" + ], + "columnsTo": [ + "organization_id", + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "FK_organization_group_memberships_member": { + "name": "FK_organization_group_memberships_member", + "tableFrom": "organization_group_memberships", + "tableTo": "organization_memberships", + "columnsFrom": [ + "organization_id", + "kilo_user_id" + ], + "columnsTo": [ + "organization_id", + "kilo_user_id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "PK_organization_group_memberships": { + "name": "PK_organization_group_memberships", + "columns": [ + "organization_id", + "group_id", + "kilo_user_id" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.organization_group_policy_settings": { + "name": "organization_group_policy_settings", + "schema": "", + "columns": { + "organization_id": { + "name": "organization_id", + "type": "uuid", + "primaryKey": true, + "notNull": true + }, + "default_policies": { + "name": "default_policies", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[{\"type\":\"model_access\",\"data\":{\"mode\":\"all\"}}]'::jsonb" + }, + "policy_revision": { + "name": "policy_revision", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 1 + }, + "updated_by_kilo_user_id": { + "name": "updated_by_kilo_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "organization_group_policy_settings_organization_id_organizations_id_fk": { + "name": "organization_group_policy_settings_organization_id_organizations_id_fk", + "tableFrom": "organization_group_policy_settings", + "tableTo": "organizations", + "columnsFrom": [ + "organization_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "organization_group_policy_settings_revision_check": { + "name": "organization_group_policy_settings_revision_check", + "value": "\"organization_group_policy_settings\".\"policy_revision\" >= 1" + } + }, + "isRLSEnabled": false + }, + "public.organization_groups": { + "name": "organization_groups", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "pg_catalog.gen_random_uuid()" + }, + "organization_id": { + "name": "organization_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "policies": { + "name": "policies", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "created_by_kilo_user_id": { + "name": "created_by_kilo_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "UQ_organization_groups_organization_id_canonical_name": { + "name": "UQ_organization_groups_organization_id_canonical_name", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "lower(btrim(\"name\"))", + "asc": true, + "isExpression": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_organization_groups_organization_id": { + "name": "IDX_organization_groups_organization_id", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "organization_groups_organization_id_organizations_id_fk": { + "name": "organization_groups_organization_id_organizations_id_fk", + "tableFrom": "organization_groups", + "tableTo": "organizations", + "columnsFrom": [ + "organization_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "UQ_organization_groups_organization_id_id": { + "name": "UQ_organization_groups_organization_id_id", + "nullsNotDistinct": false, + "columns": [ + "organization_id", + "id" + ] + } + }, + "policies": {}, + "checkConstraints": { + "organization_groups_name_check": { + "name": "organization_groups_name_check", + "value": "char_length(btrim(\"organization_groups\".\"name\")) BETWEEN 1 AND 80" + }, + "organization_groups_description_check": { + "name": "organization_groups_description_check", + "value": "\"organization_groups\".\"description\" IS NULL OR char_length(\"organization_groups\".\"description\") <= 500" + } + }, + "isRLSEnabled": false + }, + "public.organization_invitations": { + "name": "organization_invitations", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "pg_catalog.gen_random_uuid()" + }, + "organization_id": { + "name": "organization_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "role": { + "name": "role", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "invited_by": { + "name": "invited_by", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "token": { + "name": "token", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "accepted_at": { + "name": "accepted_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "authentication_requirement": { + "name": "authentication_requirement", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'default'" + }, + "sso_source_organization_id": { + "name": "sso_source_organization_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "UQ_organization_invitations_token": { + "name": "UQ_organization_invitations_token", + "columns": [ + { + "expression": "token", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_organization_invitations_org_id": { + "name": "IDX_organization_invitations_org_id", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_organization_invitations_email": { + "name": "IDX_organization_invitations_email", + "columns": [ + { + "expression": "email", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_organization_invitations_expires_at": { + "name": "IDX_organization_invitations_expires_at", + "columns": [ + { + "expression": "expires_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "organization_invitations_sso_source_organization_id_organizations_id_fk": { + "name": "organization_invitations_sso_source_organization_id_organizations_id_fk", + "tableFrom": "organization_invitations", + "tableTo": "organizations", + "columnsFrom": [ + "sso_source_organization_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "restrict", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.organization_membership_removals": { + "name": "organization_membership_removals", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "pg_catalog.gen_random_uuid()" + }, + "organization_id": { + "name": "organization_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "kilo_user_id": { + "name": "kilo_user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "removed_at": { + "name": "removed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "removed_by": { + "name": "removed_by", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "previous_role": { + "name": "previous_role", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "IDX_org_membership_removals_org_id": { + "name": "IDX_org_membership_removals_org_id", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_org_membership_removals_user_id": { + "name": "IDX_org_membership_removals_user_id", + "columns": [ + { + "expression": "kilo_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "UQ_org_membership_removals_org_user": { + "name": "UQ_org_membership_removals_org_user", + "nullsNotDistinct": false, + "columns": [ + "organization_id", + "kilo_user_id" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.organization_memberships": { + "name": "organization_memberships", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "pg_catalog.gen_random_uuid()" + }, + "organization_id": { + "name": "organization_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "kilo_user_id": { + "name": "kilo_user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "role": { + "name": "role", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "joined_at": { + "name": "joined_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "invited_by": { + "name": "invited_by", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "IDX_organization_memberships_org_id": { + "name": "IDX_organization_memberships_org_id", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_organization_memberships_user_id": { + "name": "IDX_organization_memberships_user_id", + "columns": [ + { + "expression": "kilo_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "UQ_organization_memberships_org_user": { + "name": "UQ_organization_memberships_org_user", + "nullsNotDistinct": false, + "columns": [ + "organization_id", + "kilo_user_id" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.organization_recommendation_dismissals": { + "name": "organization_recommendation_dismissals", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "pg_catalog.gen_random_uuid()" + }, + "owned_by_organization_id": { + "name": "owned_by_organization_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "recommendation_key": { + "name": "recommendation_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "dismissed_by_user_id": { + "name": "dismissed_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "dismissed_at": { + "name": "dismissed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "organization_recommendation_dismissals_owned_by_organization_id_organizations_id_fk": { + "name": "organization_recommendation_dismissals_owned_by_organization_id_organizations_id_fk", + "tableFrom": "organization_recommendation_dismissals", + "tableTo": "organizations", + "columnsFrom": [ + "owned_by_organization_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "organization_recommendation_dismissals_dismissed_by_user_id_kilocode_users_id_fk": { + "name": "organization_recommendation_dismissals_dismissed_by_user_id_kilocode_users_id_fk", + "tableFrom": "organization_recommendation_dismissals", + "tableTo": "kilocode_users", + "columnsFrom": [ + "dismissed_by_user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "UQ_org_recommendation_dismissals_org_key": { + "name": "UQ_org_recommendation_dismissals_org_key", + "nullsNotDistinct": false, + "columns": [ + "owned_by_organization_id", + "recommendation_key" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.organization_seats_purchases": { + "name": "organization_seats_purchases", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "pg_catalog.gen_random_uuid()" + }, + "organization_id": { + "name": "organization_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "subscription_stripe_id": { + "name": "subscription_stripe_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "seat_count": { + "name": "seat_count", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "amount_usd": { + "name": "amount_usd", + "type": "numeric", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "subscription_status": { + "name": "subscription_status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'active'" + }, + "idempotency_key": { + "name": "idempotency_key", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "pg_catalog.gen_random_uuid()" + }, + "starts_at": { + "name": "starts_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "billing_cycle": { + "name": "billing_cycle", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'monthly'" + } + }, + "indexes": { + "IDX_organization_seats_org_id": { + "name": "IDX_organization_seats_org_id", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_organization_seats_expires_at": { + "name": "IDX_organization_seats_expires_at", + "columns": [ + { + "expression": "expires_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_organization_seats_created_at": { + "name": "IDX_organization_seats_created_at", + "columns": [ + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_organization_seats_updated_at": { + "name": "IDX_organization_seats_updated_at", + "columns": [ + { + "expression": "updated_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_organization_seats_starts_at": { + "name": "IDX_organization_seats_starts_at", + "columns": [ + { + "expression": "starts_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "UQ_organization_seats_idempotency_key": { + "name": "UQ_organization_seats_idempotency_key", + "nullsNotDistinct": false, + "columns": [ + "idempotency_key" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.organization_service_fee_exemptions": { + "name": "organization_service_fee_exemptions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "pg_catalog.gen_random_uuid()" + }, + "organization_id": { + "name": "organization_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "is_exempt": { + "name": "is_exempt", + "type": "boolean", + "primaryKey": false, + "notNull": true + }, + "reason": { + "name": "reason", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "changed_by_kilo_user_id": { + "name": "changed_by_kilo_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "IDX_org_service_fee_exemptions_org_created_at": { + "name": "IDX_org_service_fee_exemptions_org_created_at", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": false, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "FK_org_svc_fee_exemptions_organization": { + "name": "FK_org_svc_fee_exemptions_organization", + "tableFrom": "organization_service_fee_exemptions", + "tableTo": "organizations", + "columnsFrom": [ + "organization_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "restrict", + "onUpdate": "cascade" + }, + "FK_org_svc_fee_exemptions_changed_by": { + "name": "FK_org_svc_fee_exemptions_changed_by", + "tableFrom": "organization_service_fee_exemptions", + "tableTo": "kilocode_users", + "columnsFrom": [ + "changed_by_kilo_user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "organization_service_fee_exemptions_reason_check": { + "name": "organization_service_fee_exemptions_reason_check", + "value": "length(trim(\"organization_service_fee_exemptions\".\"reason\")) > 0" + } + }, + "isRLSEnabled": false + }, + "public.organization_user_limits": { + "name": "organization_user_limits", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "pg_catalog.gen_random_uuid()" + }, + "organization_id": { + "name": "organization_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "kilo_user_id": { + "name": "kilo_user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "limit_type": { + "name": "limit_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "microdollar_limit": { + "name": "microdollar_limit", + "type": "bigint", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "IDX_organization_user_limits_org_id": { + "name": "IDX_organization_user_limits_org_id", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_organization_user_limits_user_id": { + "name": "IDX_organization_user_limits_user_id", + "columns": [ + { + "expression": "kilo_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "UQ_organization_user_limits_org_user": { + "name": "UQ_organization_user_limits_org_user", + "nullsNotDistinct": false, + "columns": [ + "organization_id", + "kilo_user_id", + "limit_type" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.organization_user_usage": { + "name": "organization_user_usage", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "pg_catalog.gen_random_uuid()" + }, + "organization_id": { + "name": "organization_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "kilo_user_id": { + "name": "kilo_user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "usage_date": { + "name": "usage_date", + "type": "date", + "primaryKey": false, + "notNull": true + }, + "limit_type": { + "name": "limit_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "microdollar_usage": { + "name": "microdollar_usage", + "type": "bigint", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "IDX_organization_user_daily_usage_org_id": { + "name": "IDX_organization_user_daily_usage_org_id", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_organization_user_daily_usage_user_id": { + "name": "IDX_organization_user_daily_usage_user_id", + "columns": [ + { + "expression": "kilo_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "UQ_organization_user_daily_usage_org_user_date": { + "name": "UQ_organization_user_daily_usage_org_user_date", + "nullsNotDistinct": false, + "columns": [ + "organization_id", + "kilo_user_id", + "limit_type", + "usage_date" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.organizations": { + "name": "organizations", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "pg_catalog.gen_random_uuid()" + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "microdollars_used": { + "name": "microdollars_used", + "type": "bigint", + "primaryKey": false, + "notNull": true, + "default": "'0'" + }, + "microdollars_balance": { + "name": "microdollars_balance", + "type": "bigint", + "primaryKey": false, + "notNull": true, + "default": "'0'" + }, + "total_microdollars_acquired": { + "name": "total_microdollars_acquired", + "type": "bigint", + "primaryKey": false, + "notNull": true, + "default": "'0'" + }, + "next_credit_expiration_at": { + "name": "next_credit_expiration_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "stripe_customer_id": { + "name": "stripe_customer_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "auto_top_up_enabled": { + "name": "auto_top_up_enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "settings": { + "name": "settings", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "seat_count": { + "name": "seat_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "require_seats": { + "name": "require_seats", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "created_by_kilo_user_id": { + "name": "created_by_kilo_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "deleted_at": { + "name": "deleted_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "sso_domain": { + "name": "sso_domain", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "parent_organization_id": { + "name": "parent_organization_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "plan": { + "name": "plan", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'teams'" + }, + "free_trial_end_at": { + "name": "free_trial_end_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "company_domain": { + "name": "company_domain", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "IDX_organizations_sso_domain": { + "name": "IDX_organizations_sso_domain", + "columns": [ + { + "expression": "sso_domain", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_organizations_parent_organization_id": { + "name": "IDX_organizations_parent_organization_id", + "columns": [ + { + "expression": "parent_organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "UQ_organizations_live_sales_demo_per_owner": { + "name": "UQ_organizations_live_sales_demo_per_owner", + "columns": [ + { + "expression": "created_by_kilo_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "(\"organizations\".\"settings\"->>'is_sales_demo')::boolean = true AND \"organizations\".\"deleted_at\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "organizations_parent_organization_id_organizations_id_fk": { + "name": "organizations_parent_organization_id_organizations_id_fk", + "tableFrom": "organizations", + "tableTo": "organizations", + "columnsFrom": [ + "parent_organization_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "restrict", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "organizations_name_not_empty_check": { + "name": "organizations_name_not_empty_check", + "value": "length(trim(\"organizations\".\"name\")) > 0" + }, + "organizations_not_parented_by_self_check": { + "name": "organizations_not_parented_by_self_check", + "value": "\"organizations\".\"parent_organization_id\" IS NULL OR \"organizations\".\"parent_organization_id\" <> \"organizations\".\"id\"" + } + }, + "isRLSEnabled": false + }, + "public.organization_modes": { + "name": "organization_modes", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "pg_catalog.gen_random_uuid()" + }, + "organization_id": { + "name": "organization_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "slug": { + "name": "slug", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_by": { + "name": "created_by", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "config": { + "name": "config", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + } + }, + "indexes": { + "IDX_organization_modes_organization_id": { + "name": "IDX_organization_modes_organization_id", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "UQ_organization_modes_org_id_slug": { + "name": "UQ_organization_modes_org_id_slug", + "nullsNotDistinct": false, + "columns": [ + "organization_id", + "slug" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.passkey_challenges": { + "name": "passkey_challenges", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true + }, + "challenge": { + "name": "challenge", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "kind": { + "name": "kind", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "kilo_user_id": { + "name": "kilo_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "consumed_at": { + "name": "consumed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "idx_passkey_challenges_expires_at": { + "name": "idx_passkey_challenges_expires_at", + "columns": [ + { + "expression": "expires_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_passkey_challenges_kilo_user_id": { + "name": "idx_passkey_challenges_kilo_user_id", + "columns": [ + { + "expression": "kilo_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "check_passkey_challenges_kind": { + "name": "check_passkey_challenges_kind", + "value": "\"passkey_challenges\".\"kind\" IN ('registration', 'authentication')" + } + }, + "isRLSEnabled": false + }, + "public.passkey_credentials": { + "name": "passkey_credentials", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "kilo_user_id": { + "name": "kilo_user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "credential_id": { + "name": "credential_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "public_key": { + "name": "public_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "sign_count": { + "name": "sign_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "transports": { + "name": "transports", + "type": "text[]", + "primaryKey": false, + "notNull": false + }, + "device_type": { + "name": "device_type", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "backed_up": { + "name": "backed_up", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "aaguid": { + "name": "aaguid", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "last_used_at": { + "name": "last_used_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "UQ_passkey_credentials_credential_id": { + "name": "UQ_passkey_credentials_credential_id", + "columns": [ + { + "expression": "credential_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_passkey_credentials_kilo_user_id": { + "name": "idx_passkey_credentials_kilo_user_id", + "columns": [ + { + "expression": "kilo_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.passkey_sign_in_tickets": { + "name": "passkey_sign_in_tickets", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "ticket_hash": { + "name": "ticket_hash", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "kilo_user_id": { + "name": "kilo_user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "consumed_at": { + "name": "consumed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "UQ_passkey_sign_in_tickets_ticket_hash": { + "name": "UQ_passkey_sign_in_tickets_ticket_hash", + "columns": [ + { + "expression": "ticket_hash", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_passkey_sign_in_tickets_expires_at": { + "name": "idx_passkey_sign_in_tickets_expires_at", + "columns": [ + { + "expression": "expires_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_passkey_sign_in_tickets_kilo_user_id": { + "name": "idx_passkey_sign_in_tickets_kilo_user_id", + "columns": [ + { + "expression": "kilo_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.payment_methods": { + "name": "payment_methods", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "pg_catalog.gen_random_uuid()" + }, + "stripe_fingerprint": { + "name": "stripe_fingerprint", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "stripe_id": { + "name": "stripe_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "last4": { + "name": "last4", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "brand": { + "name": "brand", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "address_line1": { + "name": "address_line1", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "address_line2": { + "name": "address_line2", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "address_city": { + "name": "address_city", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "address_state": { + "name": "address_state", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "address_zip": { + "name": "address_zip", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "address_country": { + "name": "address_country", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "three_d_secure_supported": { + "name": "three_d_secure_supported", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "funding": { + "name": "funding", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "regulated_status": { + "name": "regulated_status", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "address_line1_check_status": { + "name": "address_line1_check_status", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "postal_code_check_status": { + "name": "postal_code_check_status", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "http_x_forwarded_for": { + "name": "http_x_forwarded_for", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "http_x_vercel_ip_city": { + "name": "http_x_vercel_ip_city", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "http_x_vercel_ip_country": { + "name": "http_x_vercel_ip_country", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "http_x_vercel_ip_latitude": { + "name": "http_x_vercel_ip_latitude", + "type": "real", + "primaryKey": false, + "notNull": false + }, + "http_x_vercel_ip_longitude": { + "name": "http_x_vercel_ip_longitude", + "type": "real", + "primaryKey": false, + "notNull": false + }, + "http_x_vercel_ja4_digest": { + "name": "http_x_vercel_ja4_digest", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "eligible_for_free_credits": { + "name": "eligible_for_free_credits", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "deleted_at": { + "name": "deleted_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "stripe_data": { + "name": "stripe_data", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "organization_id": { + "name": "organization_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "IDX_d7d7fb15569674aaadcfbc0428": { + "name": "IDX_d7d7fb15569674aaadcfbc0428", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_e1feb919d0ab8a36381d5d5138": { + "name": "IDX_e1feb919d0ab8a36381d5d5138", + "columns": [ + { + "expression": "stripe_fingerprint", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_payment_methods_organization_id": { + "name": "IDX_payment_methods_organization_id", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "UQ_29df1b0403df5792c96bbbfdbe6": { + "name": "UQ_29df1b0403df5792c96bbbfdbe6", + "nullsNotDistinct": false, + "columns": [ + "user_id", + "stripe_id" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.pending_impact_sale_reversals": { + "name": "pending_impact_sale_reversals", + "schema": "", + "columns": { + "stripe_charge_id": { + "name": "stripe_charge_id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "dispute_id": { + "name": "dispute_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "amount": { + "name": "amount", + "type": "real", + "primaryKey": false, + "notNull": true + }, + "currency": { + "name": "currency", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "event_date": { + "name": "event_date", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "attempt_count": { + "name": "attempt_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "last_attempt_at": { + "name": "last_attempt_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "pending_impact_sale_reversals_attempt_count_non_negative_check": { + "name": "pending_impact_sale_reversals_attempt_count_non_negative_check", + "value": "\"pending_impact_sale_reversals\".\"attempt_count\" >= 0" + } + }, + "isRLSEnabled": false + }, + "public.platform_access_token_credentials": { + "name": "platform_access_token_credentials", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "pg_catalog.gen_random_uuid()" + }, + "platform_integration_id": { + "name": "platform_integration_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "owned_by_organization_id": { + "name": "owned_by_organization_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "platform": { + "name": "platform", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "integration_type": { + "name": "integration_type", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "token_encrypted": { + "name": "token_encrypted", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "provider_credential_type": { + "name": "provider_credential_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "provider_resource_id": { + "name": "provider_resource_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "provider_base_url": { + "name": "provider_base_url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "authorized_by_user_id": { + "name": "authorized_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "provider_metadata": { + "name": "provider_metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "provider_scopes": { + "name": "provider_scopes", + "type": "text[]", + "primaryKey": false, + "notNull": false + }, + "provider_verified_at": { + "name": "provider_verified_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "credential_version": { + "name": "credential_version", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 1 + }, + "last_validated_at": { + "name": "last_validated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "last_used_at": { + "name": "last_used_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "UQ_platform_access_token_credentials_integration_level": { + "name": "UQ_platform_access_token_credentials_integration_level", + "columns": [ + { + "expression": "platform_integration_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"platform_access_token_credentials\".\"provider_resource_id\" is null", + "concurrently": false, + "method": "btree", + "with": {} + }, + "UQ_platform_access_token_credentials_resource": { + "name": "UQ_platform_access_token_credentials_resource", + "columns": [ + { + "expression": "platform_integration_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "provider_credential_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "provider_resource_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"platform_access_token_credentials\".\"provider_resource_id\" is not null", + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_platform_access_token_credentials_authorized_by_user_id": { + "name": "IDX_platform_access_token_credentials_authorized_by_user_id", + "columns": [ + { + "expression": "authorized_by_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "platform_access_token_credentials_authorized_by_user_id_kilocode_users_id_fk": { + "name": "platform_access_token_credentials_authorized_by_user_id_kilocode_users_id_fk", + "tableFrom": "platform_access_token_credentials", + "tableTo": "kilocode_users", + "columnsFrom": [ + "authorized_by_user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "FK_platform_access_token_credentials_parent": { + "name": "FK_platform_access_token_credentials_parent", + "tableFrom": "platform_access_token_credentials", + "tableTo": "platform_integrations", + "columnsFrom": [ + "platform_integration_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "platform_access_token_credentials_credential_version_check": { + "name": "platform_access_token_credentials_credential_version_check", + "value": "\"platform_access_token_credentials\".\"credential_version\" > 0" + }, + "platform_access_token_credentials_resource_id_check": { + "name": "platform_access_token_credentials_resource_id_check", + "value": "\"platform_access_token_credentials\".\"provider_resource_id\" IS NULL OR \"platform_access_token_credentials\".\"provider_resource_id\" <> ''" + } + }, + "isRLSEnabled": false + }, + "public.platform_integrations": { + "name": "platform_integrations", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "pg_catalog.gen_random_uuid()" + }, + "owned_by_organization_id": { + "name": "owned_by_organization_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "owned_by_user_id": { + "name": "owned_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_by_user_id": { + "name": "created_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "platform": { + "name": "platform", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "integration_type": { + "name": "integration_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "platform_installation_id": { + "name": "platform_installation_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "platform_account_id": { + "name": "platform_account_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "platform_account_login": { + "name": "platform_account_login", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "permissions": { + "name": "permissions", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "scopes": { + "name": "scopes", + "type": "text[]", + "primaryKey": false, + "notNull": false + }, + "repository_access": { + "name": "repository_access", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "repositories": { + "name": "repositories", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "repositories_synced_at": { + "name": "repositories_synced_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "auth_invalid_at": { + "name": "auth_invalid_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "auth_invalid_reason": { + "name": "auth_invalid_reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "kilo_requester_user_id": { + "name": "kilo_requester_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "platform_requester_account_id": { + "name": "platform_requester_account_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "integration_status": { + "name": "integration_status", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "suspended_at": { + "name": "suspended_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "suspended_by": { + "name": "suspended_by", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "github_app_type": { + "name": "github_app_type", + "type": "text", + "primaryKey": false, + "notNull": false, + "default": "'standard'" + }, + "github_installation_id": { + "name": "github_installation_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "github_disconnected_at": { + "name": "github_disconnected_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "github_connection_role": { + "name": "github_connection_role", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "github_authorized_by_user_id": { + "name": "github_authorized_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "github_authorized_user_id": { + "name": "github_authorized_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "github_authorized_at": { + "name": "github_authorized_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "installed_at": { + "name": "installed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "UQ_platform_integrations_owned_by_org_platform_inst": { + "name": "UQ_platform_integrations_owned_by_org_platform_inst", + "columns": [ + { + "expression": "owned_by_organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "platform", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "platform_installation_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"platform_integrations\".\"owned_by_organization_id\" is not null", + "concurrently": false, + "method": "btree", + "with": {} + }, + "UQ_platform_integrations_owned_by_user_platform_inst": { + "name": "UQ_platform_integrations_owned_by_user_platform_inst", + "columns": [ + { + "expression": "owned_by_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "platform", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "platform_installation_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"platform_integrations\".\"owned_by_user_id\" is not null", + "concurrently": false, + "method": "btree", + "with": {} + }, + "UQ_platform_integrations_slack_platform_inst": { + "name": "UQ_platform_integrations_slack_platform_inst", + "columns": [ + { + "expression": "platform", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "platform_installation_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"platform_integrations\".\"platform\" = 'slack' AND \"platform_integrations\".\"platform_installation_id\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "UQ_platform_integrations_linear_platform_inst": { + "name": "UQ_platform_integrations_linear_platform_inst", + "columns": [ + { + "expression": "platform", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "platform_installation_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"platform_integrations\".\"platform\" = 'linear' AND \"platform_integrations\".\"platform_installation_id\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "UQ_platform_integrations_github_org_canonical": { + "name": "UQ_platform_integrations_github_org_canonical", + "columns": [ + { + "expression": "owned_by_organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "github_installation_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"platform_integrations\".\"platform\" = 'github' AND \"platform_integrations\".\"owned_by_organization_id\" IS NOT NULL AND \"platform_integrations\".\"github_installation_id\" IS NOT NULL", + "concurrently": true, + "method": "btree", + "with": {} + }, + "UQ_platform_integrations_github_workflow_canonical": { + "name": "UQ_platform_integrations_github_workflow_canonical", + "columns": [ + { + "expression": "github_installation_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"platform_integrations\".\"github_connection_role\" = 'workflow'", + "concurrently": true, + "method": "btree", + "with": {} + }, + "UQ_platform_integrations_github_workflow_identity": { + "name": "UQ_platform_integrations_github_workflow_identity", + "columns": [ + { + "expression": "COALESCE(\"github_app_type\", 'standard')", + "asc": true, + "isExpression": true, + "nulls": "last" + }, + { + "expression": "platform_installation_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"platform_integrations\".\"github_connection_role\" = 'workflow'", + "concurrently": true, + "method": "btree", + "with": {} + }, + "UQ_platform_integrations_github_user_canonical": { + "name": "UQ_platform_integrations_github_user_canonical", + "columns": [ + { + "expression": "owned_by_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "github_installation_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"platform_integrations\".\"platform\" = 'github' AND \"platform_integrations\".\"owned_by_user_id\" IS NOT NULL AND \"platform_integrations\".\"github_installation_id\" IS NOT NULL", + "concurrently": true, + "method": "btree", + "with": {} + }, + "UQ_platform_integrations_github_org_pending_target": { + "name": "UQ_platform_integrations_github_org_pending_target", + "columns": [ + { + "expression": "owned_by_organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "platform", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "github_app_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "platform_account_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"platform_integrations\".\"platform\" = 'github' AND \"platform_integrations\".\"owned_by_organization_id\" IS NOT NULL AND \"platform_integrations\".\"integration_status\" = 'pending' AND \"platform_integrations\".\"platform_installation_id\" IS NULL AND \"platform_integrations\".\"platform_account_id\" IS NOT NULL", + "concurrently": true, + "method": "btree", + "with": {} + }, + "UQ_platform_integrations_github_user_pending_target": { + "name": "UQ_platform_integrations_github_user_pending_target", + "columns": [ + { + "expression": "owned_by_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "platform", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "github_app_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "platform_account_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"platform_integrations\".\"platform\" = 'github' AND \"platform_integrations\".\"owned_by_user_id\" IS NOT NULL AND \"platform_integrations\".\"integration_status\" = 'pending' AND \"platform_integrations\".\"platform_installation_id\" IS NULL AND \"platform_integrations\".\"platform_account_id\" IS NOT NULL", + "concurrently": true, + "method": "btree", + "with": {} + }, + "UQ_platform_integrations_user_bitbucket": { + "name": "UQ_platform_integrations_user_bitbucket", + "columns": [ + { + "expression": "owned_by_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"platform_integrations\".\"platform\" = 'bitbucket' AND \"platform_integrations\".\"owned_by_user_id\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "UQ_platform_integrations_org_bitbucket": { + "name": "UQ_platform_integrations_org_bitbucket", + "columns": [ + { + "expression": "owned_by_organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"platform_integrations\".\"platform\" = 'bitbucket' AND \"platform_integrations\".\"owned_by_organization_id\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_platform_integrations_owned_by_org_id": { + "name": "IDX_platform_integrations_owned_by_org_id", + "columns": [ + { + "expression": "owned_by_organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_platform_integrations_owned_by_user_id": { + "name": "IDX_platform_integrations_owned_by_user_id", + "columns": [ + { + "expression": "owned_by_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_platform_integrations_platform_inst_id": { + "name": "IDX_platform_integrations_platform_inst_id", + "columns": [ + { + "expression": "platform_installation_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_platform_integrations_platform": { + "name": "IDX_platform_integrations_platform", + "columns": [ + { + "expression": "platform", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_platform_integrations_owned_by_org_platform": { + "name": "IDX_platform_integrations_owned_by_org_platform", + "columns": [ + { + "expression": "owned_by_organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "platform", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_platform_integrations_owned_by_user_platform": { + "name": "IDX_platform_integrations_owned_by_user_platform", + "columns": [ + { + "expression": "owned_by_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "platform", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_platform_integrations_integration_status": { + "name": "IDX_platform_integrations_integration_status", + "columns": [ + { + "expression": "integration_status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_platform_integrations_kilo_requester": { + "name": "IDX_platform_integrations_kilo_requester", + "columns": [ + { + "expression": "platform", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "kilo_requester_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "integration_status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_platform_integrations_platform_requester": { + "name": "IDX_platform_integrations_platform_requester", + "columns": [ + { + "expression": "platform", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "platform_requester_account_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "integration_status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "platform_integrations_owned_by_organization_id_organizations_id_fk": { + "name": "platform_integrations_owned_by_organization_id_organizations_id_fk", + "tableFrom": "platform_integrations", + "tableTo": "organizations", + "columnsFrom": [ + "owned_by_organization_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "platform_integrations_owned_by_user_id_kilocode_users_id_fk": { + "name": "platform_integrations_owned_by_user_id_kilocode_users_id_fk", + "tableFrom": "platform_integrations", + "tableTo": "kilocode_users", + "columnsFrom": [ + "owned_by_user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "platform_integrations_github_installation_id_github_app_installations_id_fk": { + "name": "platform_integrations_github_installation_id_github_app_installations_id_fk", + "tableFrom": "platform_integrations", + "tableTo": "github_app_installations", + "columnsFrom": [ + "github_installation_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "restrict", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "platform_integrations_github_connection_role_check": { + "name": "platform_integrations_github_connection_role_check", + "value": "\"platform_integrations\".\"github_connection_role\" IS NULL OR (\n \"platform_integrations\".\"platform\" = 'github' AND \"platform_integrations\".\"integration_type\" = 'app'\n AND \"platform_integrations\".\"platform_installation_id\" IS NOT NULL\n AND \"platform_integrations\".\"github_connection_role\" IN ('workflow', 'agent_only')\n AND (\"platform_integrations\".\"github_connection_role\" <> 'agent_only' OR \"platform_integrations\".\"github_installation_id\" IS NOT NULL)\n )" + }, + "platform_integrations_owner_check": { + "name": "platform_integrations_owner_check", + "value": "(\n (\"platform_integrations\".\"owned_by_user_id\" IS NOT NULL AND \"platform_integrations\".\"owned_by_organization_id\" IS NULL) OR\n (\"platform_integrations\".\"owned_by_user_id\" IS NULL AND \"platform_integrations\".\"owned_by_organization_id\" IS NOT NULL)\n )" + } + }, + "isRLSEnabled": false + }, + "public.platform_oauth_credentials": { + "name": "platform_oauth_credentials", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "pg_catalog.gen_random_uuid()" + }, + "platform_integration_id": { + "name": "platform_integration_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "platform": { + "name": "platform", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "authorized_by_user_id": { + "name": "authorized_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "provider_subject_id": { + "name": "provider_subject_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "provider_subject_login": { + "name": "provider_subject_login", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "provider_base_url": { + "name": "provider_base_url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "access_token_encrypted": { + "name": "access_token_encrypted", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "access_token_expires_at": { + "name": "access_token_expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "refresh_token_encrypted": { + "name": "refresh_token_encrypted", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "refresh_token_expires_at": { + "name": "refresh_token_expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "oauth_client_secret_encrypted": { + "name": "oauth_client_secret_encrypted", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "credential_version": { + "name": "credential_version", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 1 + }, + "revoked_at": { + "name": "revoked_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "revocation_reason": { + "name": "revocation_reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "last_used_at": { + "name": "last_used_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "UQ_platform_oauth_credentials_platform_integration_id": { + "name": "UQ_platform_oauth_credentials_platform_integration_id", + "columns": [ + { + "expression": "platform_integration_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_platform_oauth_credentials_authorized_by_user_id": { + "name": "IDX_platform_oauth_credentials_authorized_by_user_id", + "columns": [ + { + "expression": "authorized_by_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "platform_oauth_credentials_platform_integration_id_platform_integrations_id_fk": { + "name": "platform_oauth_credentials_platform_integration_id_platform_integrations_id_fk", + "tableFrom": "platform_oauth_credentials", + "tableTo": "platform_integrations", + "columnsFrom": [ + "platform_integration_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "platform_oauth_credentials_authorized_by_user_id_kilocode_users_id_fk": { + "name": "platform_oauth_credentials_authorized_by_user_id_kilocode_users_id_fk", + "tableFrom": "platform_oauth_credentials", + "tableTo": "kilocode_users", + "columnsFrom": [ + "authorized_by_user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "platform_oauth_credentials_credential_version_check": { + "name": "platform_oauth_credentials_credential_version_check", + "value": "\"platform_oauth_credentials\".\"credential_version\" > 0" + } + }, + "isRLSEnabled": false + }, + "public.provider_oauth_attempts": { + "name": "provider_oauth_attempts", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "pg_catalog.gen_random_uuid()" + }, + "provider": { + "name": "provider", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "purpose": { + "name": "purpose", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'provider_install'" + }, + "state_hash": { + "name": "state_hash", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "initiated_by_user_id": { + "name": "initiated_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "owned_by_user_id": { + "name": "owned_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "owned_by_organization_id": { + "name": "owned_by_organization_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "consumed_at": { + "name": "consumed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "UQ_provider_oauth_attempts_user_pending": { + "name": "UQ_provider_oauth_attempts_user_pending", + "columns": [ + { + "expression": "owned_by_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "provider", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"provider_oauth_attempts\".\"status\" = 'pending' AND \"provider_oauth_attempts\".\"owned_by_user_id\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "UQ_provider_oauth_attempts_org_pending": { + "name": "UQ_provider_oauth_attempts_org_pending", + "columns": [ + { + "expression": "owned_by_organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "provider", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"provider_oauth_attempts\".\"status\" = 'pending' AND \"provider_oauth_attempts\".\"owned_by_organization_id\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_provider_oauth_attempts_expires_at": { + "name": "IDX_provider_oauth_attempts_expires_at", + "columns": [ + { + "expression": "expires_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "provider_oauth_attempts_initiated_by_user_id_kilocode_users_id_fk": { + "name": "provider_oauth_attempts_initiated_by_user_id_kilocode_users_id_fk", + "tableFrom": "provider_oauth_attempts", + "tableTo": "kilocode_users", + "columnsFrom": [ + "initiated_by_user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "provider_oauth_attempts_owned_by_user_id_kilocode_users_id_fk": { + "name": "provider_oauth_attempts_owned_by_user_id_kilocode_users_id_fk", + "tableFrom": "provider_oauth_attempts", + "tableTo": "kilocode_users", + "columnsFrom": [ + "owned_by_user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "provider_oauth_attempts_owned_by_organization_id_organizations_id_fk": { + "name": "provider_oauth_attempts_owned_by_organization_id_organizations_id_fk", + "tableFrom": "provider_oauth_attempts", + "tableTo": "organizations", + "columnsFrom": [ + "owned_by_organization_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "provider_oauth_attempts_state_hash_unique": { + "name": "provider_oauth_attempts_state_hash_unique", + "nullsNotDistinct": false, + "columns": [ + "state_hash" + ] + } + }, + "policies": {}, + "checkConstraints": { + "provider_oauth_attempts_provider_check": { + "name": "provider_oauth_attempts_provider_check", + "value": "\"provider_oauth_attempts\".\"provider\" IN ('slack', 'linear', 'discord')" + }, + "provider_oauth_attempts_status_check": { + "name": "provider_oauth_attempts_status_check", + "value": "\"provider_oauth_attempts\".\"status\" IN ('pending', 'consumed', 'expired')" + }, + "provider_oauth_attempts_purpose_check": { + "name": "provider_oauth_attempts_purpose_check", + "value": "\"provider_oauth_attempts\".\"purpose\" = 'provider_install'" + }, + "provider_oauth_attempts_owner_check": { + "name": "provider_oauth_attempts_owner_check", + "value": "num_nonnulls(\"provider_oauth_attempts\".\"owned_by_user_id\", \"provider_oauth_attempts\".\"owned_by_organization_id\") = 1" + } + }, + "isRLSEnabled": false + }, + "public.quick_chat_messages": { + "name": "quick_chat_messages", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "pg_catalog.gen_random_uuid()" + }, + "thread_id": { + "name": "thread_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "role": { + "name": "role", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "content": { + "name": "content", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "client_id": { + "name": "client_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "IDX_quick_chat_messages_thread_created_at": { + "name": "IDX_quick_chat_messages_thread_created_at", + "columns": [ + { + "expression": "thread_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "quick_chat_messages_thread_id_quick_chat_threads_id_fk": { + "name": "quick_chat_messages_thread_id_quick_chat_threads_id_fk", + "tableFrom": "quick_chat_messages", + "tableTo": "quick_chat_threads", + "columnsFrom": [ + "thread_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.quick_chat_threads": { + "name": "quick_chat_threads", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "pg_catalog.gen_random_uuid()" + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "organization_id": { + "name": "organization_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "quick_chat_threads_user_personal_uidx": { + "name": "quick_chat_threads_user_personal_uidx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"quick_chat_threads\".\"organization_id\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "quick_chat_threads_user_org_uidx": { + "name": "quick_chat_threads_user_org_uidx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"quick_chat_threads\".\"organization_id\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "quick_chat_threads_user_id_kilocode_users_id_fk": { + "name": "quick_chat_threads_user_id_kilocode_users_id_fk", + "tableFrom": "quick_chat_threads", + "tableTo": "kilocode_users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "restrict", + "onUpdate": "no action" + }, + "quick_chat_threads_organization_id_organizations_id_fk": { + "name": "quick_chat_threads_organization_id_organizations_id_fk", + "tableFrom": "quick_chat_threads", + "tableTo": "organizations", + "columnsFrom": [ + "organization_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.referral_code_usages": { + "name": "referral_code_usages", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "pg_catalog.gen_random_uuid()" + }, + "referring_kilo_user_id": { + "name": "referring_kilo_user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "redeeming_kilo_user_id": { + "name": "redeeming_kilo_user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "code": { + "name": "code", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "amount_usd": { + "name": "amount_usd", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "paid_at": { + "name": "paid_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "IDX_referral_code_usages_redeeming_kilo_user_id": { + "name": "IDX_referral_code_usages_redeeming_kilo_user_id", + "columns": [ + { + "expression": "redeeming_kilo_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "UQ_referral_code_usages_redeeming_user_id_code": { + "name": "UQ_referral_code_usages_redeeming_user_id_code", + "nullsNotDistinct": false, + "columns": [ + "redeeming_kilo_user_id", + "referring_kilo_user_id" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.referral_codes": { + "name": "referral_codes", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "kilo_user_id": { + "name": "kilo_user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "code": { + "name": "code", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "max_redemptions": { + "name": "max_redemptions", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 10 + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "UQ_referral_codes_kilo_user_id": { + "name": "UQ_referral_codes_kilo_user_id", + "columns": [ + { + "expression": "kilo_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_referral_codes_code": { + "name": "IDX_referral_codes_code", + "columns": [ + { + "expression": "code", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.sales_demo_spend_ledger": { + "name": "sales_demo_spend_ledger", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "pg_catalog.gen_random_uuid()" + }, + "organization_id": { + "name": "organization_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "owner_kilo_user_id": { + "name": "owner_kilo_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "period_start": { + "name": "period_start", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "period_end": { + "name": "period_end", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "microdollars_used": { + "name": "microdollars_used", + "type": "bigint", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "sales_demo_spend_ledger_organization_id_organizations_id_fk": { + "name": "sales_demo_spend_ledger_organization_id_organizations_id_fk", + "tableFrom": "sales_demo_spend_ledger", + "tableTo": "organizations", + "columnsFrom": [ + "organization_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "restrict", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "sales_demo_spend_ledger_spend_positive": { + "name": "sales_demo_spend_ledger_spend_positive", + "value": "\"sales_demo_spend_ledger\".\"microdollars_used\" > 0" + } + }, + "isRLSEnabled": false + }, + "public.security_advisor_check_catalog": { + "name": "security_advisor_check_catalog", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "pg_catalog.gen_random_uuid()" + }, + "check_id": { + "name": "check_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "severity": { + "name": "severity", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "explanation": { + "name": "explanation", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "risk": { + "name": "risk", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "is_active": { + "name": "is_active", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "security_advisor_check_catalog_check_id_unique": { + "name": "security_advisor_check_catalog_check_id_unique", + "nullsNotDistinct": false, + "columns": [ + "check_id" + ] + } + }, + "policies": {}, + "checkConstraints": { + "security_advisor_check_catalog_severity_check": { + "name": "security_advisor_check_catalog_severity_check", + "value": "\"security_advisor_check_catalog\".\"severity\" in ('critical', 'warn', 'info')" + } + }, + "isRLSEnabled": false + }, + "public.security_advisor_content": { + "name": "security_advisor_content", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "pg_catalog.gen_random_uuid()" + }, + "key": { + "name": "key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "value": { + "name": "value", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "''" + }, + "is_active": { + "name": "is_active", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "security_advisor_content_key_unique": { + "name": "security_advisor_content_key_unique", + "nullsNotDistinct": false, + "columns": [ + "key" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.security_advisor_kiloclaw_coverage": { + "name": "security_advisor_kiloclaw_coverage", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "pg_catalog.gen_random_uuid()" + }, + "area": { + "name": "area", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "summary": { + "name": "summary", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "detail": { + "name": "detail", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "match_check_ids": { + "name": "match_check_ids", + "type": "text[]", + "primaryKey": false, + "notNull": true, + "default": "'{}'::text[]" + }, + "is_active": { + "name": "is_active", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "security_advisor_kiloclaw_coverage_area_unique": { + "name": "security_advisor_kiloclaw_coverage_area_unique", + "nullsNotDistinct": false, + "columns": [ + "area" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.security_advisor_scans": { + "name": "security_advisor_scans", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "pg_catalog.gen_random_uuid()" + }, + "kilo_user_id": { + "name": "kilo_user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "organization_id": { + "name": "organization_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "source_platform": { + "name": "source_platform", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "source_method": { + "name": "source_method", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "plugin_version": { + "name": "plugin_version", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "openclaw_version": { + "name": "openclaw_version", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "public_ip": { + "name": "public_ip", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "findings_critical": { + "name": "findings_critical", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "findings_warn": { + "name": "findings_warn", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "findings_info": { + "name": "findings_info", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "idx_security_advisor_scans_user_created_at": { + "name": "idx_security_advisor_scans_user_created_at", + "columns": [ + { + "expression": "kilo_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_security_advisor_scans_created_at": { + "name": "idx_security_advisor_scans_created_at", + "columns": [ + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_security_advisor_scans_platform": { + "name": "idx_security_advisor_scans_platform", + "columns": [ + { + "expression": "source_platform", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.security_agent_commands": { + "name": "security_agent_commands", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "pg_catalog.gen_random_uuid()" + }, + "command_type": { + "name": "command_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "origin": { + "name": "origin", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "owned_by_organization_id": { + "name": "owned_by_organization_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "owned_by_user_id": { + "name": "owned_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "finding_id": { + "name": "finding_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "repo_full_name": { + "name": "repo_full_name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "operation_key": { + "name": "operation_key", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'accepted'" + }, + "result_code": { + "name": "result_code", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "result_metadata": { + "name": "result_metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "last_error_redacted": { + "name": "last_error_redacted", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "accepted_at": { + "name": "accepted_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "started_at": { + "name": "started_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "completed_at": { + "name": "completed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "idx_security_agent_commands_org_created": { + "name": "idx_security_agent_commands_org_created", + "columns": [ + { + "expression": "owned_by_organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": false, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_security_agent_commands_user_created": { + "name": "idx_security_agent_commands_user_created", + "columns": [ + { + "expression": "owned_by_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": false, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_security_agent_commands_status_updated": { + "name": "idx_security_agent_commands_status_updated", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "updated_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_security_agent_commands_finding_created": { + "name": "idx_security_agent_commands_finding_created", + "columns": [ + { + "expression": "finding_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": false, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "UQ_security_agent_commands_org_operation_key": { + "name": "UQ_security_agent_commands_org_operation_key", + "columns": [ + { + "expression": "owned_by_organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "operation_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"security_agent_commands\".\"owned_by_organization_id\" IS NOT NULL AND \"security_agent_commands\".\"operation_key\" IS NOT NULL", + "concurrently": true, + "method": "btree", + "with": {} + }, + "UQ_security_agent_commands_user_operation_key": { + "name": "UQ_security_agent_commands_user_operation_key", + "columns": [ + { + "expression": "owned_by_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "operation_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"security_agent_commands\".\"owned_by_user_id\" IS NOT NULL AND \"security_agent_commands\".\"operation_key\" IS NOT NULL", + "concurrently": true, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "security_agent_commands_owned_by_organization_id_organizations_id_fk": { + "name": "security_agent_commands_owned_by_organization_id_organizations_id_fk", + "tableFrom": "security_agent_commands", + "tableTo": "organizations", + "columnsFrom": [ + "owned_by_organization_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "security_agent_commands_owned_by_user_id_kilocode_users_id_fk": { + "name": "security_agent_commands_owned_by_user_id_kilocode_users_id_fk", + "tableFrom": "security_agent_commands", + "tableTo": "kilocode_users", + "columnsFrom": [ + "owned_by_user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "security_agent_commands_finding_id_security_findings_id_fk": { + "name": "security_agent_commands_finding_id_security_findings_id_fk", + "tableFrom": "security_agent_commands", + "tableTo": "security_findings", + "columnsFrom": [ + "finding_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "security_agent_commands_owner_check": { + "name": "security_agent_commands_owner_check", + "value": "(\n (\"security_agent_commands\".\"owned_by_user_id\" IS NOT NULL AND \"security_agent_commands\".\"owned_by_organization_id\" IS NULL) OR\n (\"security_agent_commands\".\"owned_by_user_id\" IS NULL AND \"security_agent_commands\".\"owned_by_organization_id\" IS NOT NULL)\n )" + }, + "security_agent_commands_type_check": { + "name": "security_agent_commands_type_check", + "value": "\"security_agent_commands\".\"command_type\" IN ('sync', 'dismiss_finding', 'start_analysis', 'apply_auto_remediation')" + }, + "security_agent_commands_origin_check": { + "name": "security_agent_commands_origin_check", + "value": "\"security_agent_commands\".\"origin\" IN ('manual', 'dashboard_refresh', 'enable_initial_sync', 'settings_include_existing')" + }, + "security_agent_commands_status_check": { + "name": "security_agent_commands_status_check", + "value": "\"security_agent_commands\".\"status\" IN ('accepted', 'running', 'succeeded', 'failed', 'no_op')" + } + }, + "isRLSEnabled": false + }, + "public.security_agent_repository_sync_state": { + "name": "security_agent_repository_sync_state", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "pg_catalog.gen_random_uuid()" + }, + "owned_by_organization_id": { + "name": "owned_by_organization_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "owned_by_user_id": { + "name": "owned_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "repo_full_name": { + "name": "repo_full_name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "last_attempted_at": { + "name": "last_attempted_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "last_succeeded_at": { + "name": "last_succeeded_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "last_failure_code": { + "name": "last_failure_code", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "UQ_security_agent_repository_sync_state_org_repo": { + "name": "UQ_security_agent_repository_sync_state_org_repo", + "columns": [ + { + "expression": "owned_by_organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "repo_full_name", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"security_agent_repository_sync_state\".\"owned_by_organization_id\" is not null", + "concurrently": false, + "method": "btree", + "with": {} + }, + "UQ_security_agent_repository_sync_state_user_repo": { + "name": "UQ_security_agent_repository_sync_state_user_repo", + "columns": [ + { + "expression": "owned_by_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "repo_full_name", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"security_agent_repository_sync_state\".\"owned_by_user_id\" is not null", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "security_agent_repository_sync_state_owned_by_organization_id_organizations_id_fk": { + "name": "security_agent_repository_sync_state_owned_by_organization_id_organizations_id_fk", + "tableFrom": "security_agent_repository_sync_state", + "tableTo": "organizations", + "columnsFrom": [ + "owned_by_organization_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "security_agent_repository_sync_state_owned_by_user_id_kilocode_users_id_fk": { + "name": "security_agent_repository_sync_state_owned_by_user_id_kilocode_users_id_fk", + "tableFrom": "security_agent_repository_sync_state", + "tableTo": "kilocode_users", + "columnsFrom": [ + "owned_by_user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "security_agent_repository_sync_state_owner_check": { + "name": "security_agent_repository_sync_state_owner_check", + "value": "(\n (\"security_agent_repository_sync_state\".\"owned_by_user_id\" IS NOT NULL AND \"security_agent_repository_sync_state\".\"owned_by_organization_id\" IS NULL) OR\n (\"security_agent_repository_sync_state\".\"owned_by_user_id\" IS NULL AND \"security_agent_repository_sync_state\".\"owned_by_organization_id\" IS NOT NULL)\n )" + } + }, + "isRLSEnabled": false + }, + "public.security_analysis_owner_state": { + "name": "security_analysis_owner_state", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "pg_catalog.gen_random_uuid()" + }, + "owned_by_organization_id": { + "name": "owned_by_organization_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "owned_by_user_id": { + "name": "owned_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "auto_analysis_enabled_at": { + "name": "auto_analysis_enabled_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "blocked_until": { + "name": "blocked_until", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "block_reason": { + "name": "block_reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "consecutive_actor_resolution_failures": { + "name": "consecutive_actor_resolution_failures", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "last_actor_resolution_failure_at": { + "name": "last_actor_resolution_failure_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "UQ_security_analysis_owner_state_org_owner": { + "name": "UQ_security_analysis_owner_state_org_owner", + "columns": [ + { + "expression": "owned_by_organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"security_analysis_owner_state\".\"owned_by_organization_id\" is not null", + "concurrently": false, + "method": "btree", + "with": {} + }, + "UQ_security_analysis_owner_state_user_owner": { + "name": "UQ_security_analysis_owner_state_user_owner", + "columns": [ + { + "expression": "owned_by_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"security_analysis_owner_state\".\"owned_by_user_id\" is not null", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "security_analysis_owner_state_owned_by_organization_id_organizations_id_fk": { + "name": "security_analysis_owner_state_owned_by_organization_id_organizations_id_fk", + "tableFrom": "security_analysis_owner_state", + "tableTo": "organizations", + "columnsFrom": [ + "owned_by_organization_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "security_analysis_owner_state_owned_by_user_id_kilocode_users_id_fk": { + "name": "security_analysis_owner_state_owned_by_user_id_kilocode_users_id_fk", + "tableFrom": "security_analysis_owner_state", + "tableTo": "kilocode_users", + "columnsFrom": [ + "owned_by_user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "security_analysis_owner_state_owner_check": { + "name": "security_analysis_owner_state_owner_check", + "value": "(\n (\"security_analysis_owner_state\".\"owned_by_user_id\" IS NOT NULL AND \"security_analysis_owner_state\".\"owned_by_organization_id\" IS NULL) OR\n (\"security_analysis_owner_state\".\"owned_by_user_id\" IS NULL AND \"security_analysis_owner_state\".\"owned_by_organization_id\" IS NOT NULL)\n )" + }, + "security_analysis_owner_state_block_reason_check": { + "name": "security_analysis_owner_state_block_reason_check", + "value": "\"security_analysis_owner_state\".\"block_reason\" IS NULL OR \"security_analysis_owner_state\".\"block_reason\" IN ('INSUFFICIENT_CREDITS', 'ACTOR_RESOLUTION_FAILED', 'OPERATOR_PAUSE')" + } + }, + "isRLSEnabled": false + }, + "public.security_analysis_queue": { + "name": "security_analysis_queue", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "pg_catalog.gen_random_uuid()" + }, + "finding_id": { + "name": "finding_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "owned_by_organization_id": { + "name": "owned_by_organization_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "owned_by_user_id": { + "name": "owned_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "queue_status": { + "name": "queue_status", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "severity_rank": { + "name": "severity_rank", + "type": "smallint", + "primaryKey": false, + "notNull": true + }, + "admitted_config_revision": { + "name": "admitted_config_revision", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "queued_at": { + "name": "queued_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "claimed_at": { + "name": "claimed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "claimed_by_job_id": { + "name": "claimed_by_job_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "claim_token": { + "name": "claim_token", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "attempt_count": { + "name": "attempt_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "reopen_requeue_count": { + "name": "reopen_requeue_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "next_retry_at": { + "name": "next_retry_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "failure_code": { + "name": "failure_code", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "last_error_redacted": { + "name": "last_error_redacted", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "UQ_security_analysis_queue_finding_id": { + "name": "UQ_security_analysis_queue_finding_id", + "columns": [ + { + "expression": "finding_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_security_analysis_queue_claim_path_org": { + "name": "idx_security_analysis_queue_claim_path_org", + "columns": [ + { + "expression": "owned_by_organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "coalesce(\"next_retry_at\", '-infinity'::timestamptz)", + "asc": true, + "isExpression": true, + "nulls": "last" + }, + { + "expression": "severity_rank", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "queued_at", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"security_analysis_queue\".\"queue_status\" = 'queued'", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_security_analysis_queue_claim_path_user": { + "name": "idx_security_analysis_queue_claim_path_user", + "columns": [ + { + "expression": "owned_by_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "coalesce(\"next_retry_at\", '-infinity'::timestamptz)", + "asc": true, + "isExpression": true, + "nulls": "last" + }, + { + "expression": "severity_rank", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "queued_at", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"security_analysis_queue\".\"queue_status\" = 'queued'", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_security_analysis_queue_in_flight_org": { + "name": "idx_security_analysis_queue_in_flight_org", + "columns": [ + { + "expression": "owned_by_organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "queue_status", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "claimed_at", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"security_analysis_queue\".\"queue_status\" IN ('pending', 'running')", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_security_analysis_queue_in_flight_user": { + "name": "idx_security_analysis_queue_in_flight_user", + "columns": [ + { + "expression": "owned_by_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "queue_status", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "claimed_at", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"security_analysis_queue\".\"queue_status\" IN ('pending', 'running')", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_security_analysis_queue_lag_dashboards": { + "name": "idx_security_analysis_queue_lag_dashboards", + "columns": [ + { + "expression": "queued_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"security_analysis_queue\".\"queue_status\" = 'queued'", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_security_analysis_queue_pending_reconciliation": { + "name": "idx_security_analysis_queue_pending_reconciliation", + "columns": [ + { + "expression": "claimed_at", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"security_analysis_queue\".\"queue_status\" = 'pending'", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_security_analysis_queue_running_reconciliation": { + "name": "idx_security_analysis_queue_running_reconciliation", + "columns": [ + { + "expression": "updated_at", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"security_analysis_queue\".\"queue_status\" = 'running'", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_security_analysis_queue_failure_trend": { + "name": "idx_security_analysis_queue_failure_trend", + "columns": [ + { + "expression": "failure_code", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "updated_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"security_analysis_queue\".\"failure_code\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "security_analysis_queue_finding_id_security_findings_id_fk": { + "name": "security_analysis_queue_finding_id_security_findings_id_fk", + "tableFrom": "security_analysis_queue", + "tableTo": "security_findings", + "columnsFrom": [ + "finding_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "security_analysis_queue_owned_by_organization_id_organizations_id_fk": { + "name": "security_analysis_queue_owned_by_organization_id_organizations_id_fk", + "tableFrom": "security_analysis_queue", + "tableTo": "organizations", + "columnsFrom": [ + "owned_by_organization_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "security_analysis_queue_owned_by_user_id_kilocode_users_id_fk": { + "name": "security_analysis_queue_owned_by_user_id_kilocode_users_id_fk", + "tableFrom": "security_analysis_queue", + "tableTo": "kilocode_users", + "columnsFrom": [ + "owned_by_user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "security_analysis_queue_owner_check": { + "name": "security_analysis_queue_owner_check", + "value": "(\n (\"security_analysis_queue\".\"owned_by_user_id\" IS NOT NULL AND \"security_analysis_queue\".\"owned_by_organization_id\" IS NULL) OR\n (\"security_analysis_queue\".\"owned_by_user_id\" IS NULL AND \"security_analysis_queue\".\"owned_by_organization_id\" IS NOT NULL)\n )" + }, + "security_analysis_queue_status_check": { + "name": "security_analysis_queue_status_check", + "value": "\"security_analysis_queue\".\"queue_status\" IN ('queued', 'pending', 'running', 'failed', 'completed')" + }, + "security_analysis_queue_claim_token_required_check": { + "name": "security_analysis_queue_claim_token_required_check", + "value": "\"security_analysis_queue\".\"queue_status\" NOT IN ('pending', 'running') OR \"security_analysis_queue\".\"claim_token\" IS NOT NULL" + }, + "security_analysis_queue_attempt_count_non_negative_check": { + "name": "security_analysis_queue_attempt_count_non_negative_check", + "value": "\"security_analysis_queue\".\"attempt_count\" >= 0" + }, + "security_analysis_queue_reopen_requeue_count_non_negative_check": { + "name": "security_analysis_queue_reopen_requeue_count_non_negative_check", + "value": "\"security_analysis_queue\".\"reopen_requeue_count\" >= 0" + }, + "security_analysis_queue_severity_rank_check": { + "name": "security_analysis_queue_severity_rank_check", + "value": "\"security_analysis_queue\".\"severity_rank\" IN (0, 1, 2, 3)" + }, + "security_analysis_queue_failure_code_check": { + "name": "security_analysis_queue_failure_code_check", + "value": "\"security_analysis_queue\".\"failure_code\" IS NULL OR \"security_analysis_queue\".\"failure_code\" IN (\n 'NETWORK_TIMEOUT',\n 'UPSTREAM_5XX',\n 'TEMP_TOKEN_FAILURE',\n 'START_CALL_AMBIGUOUS',\n 'REQUEUE_TEMPORARY_PRECONDITION',\n 'ACTOR_RESOLUTION_FAILED',\n 'GITHUB_TOKEN_UNAVAILABLE',\n 'INVALID_CONFIG',\n 'MISSING_OWNERSHIP',\n 'PERMISSION_DENIED_PERMANENT',\n 'UNSUPPORTED_SEVERITY',\n 'INSUFFICIENT_CREDITS',\n 'STATE_GUARD_REJECTED',\n 'SKIPPED_ALREADY_IN_PROGRESS',\n 'SKIPPED_NO_LONGER_ELIGIBLE',\n 'REOPEN_LOOP_GUARD',\n 'RUN_LOST'\n )" + } + }, + "isRLSEnabled": false + }, + "public.security_audit_log": { + "name": "security_audit_log", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "pg_catalog.gen_random_uuid()" + }, + "owned_by_organization_id": { + "name": "owned_by_organization_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "owned_by_user_id": { + "name": "owned_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "actor_id": { + "name": "actor_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "actor_email": { + "name": "actor_email", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "actor_name": { + "name": "actor_name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "actor_type": { + "name": "actor_type", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "action": { + "name": "action", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "resource_type": { + "name": "resource_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "resource_id": { + "name": "resource_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "before_state": { + "name": "before_state", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "after_state": { + "name": "after_state", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "finding_id": { + "name": "finding_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "occurred_at": { + "name": "occurred_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "source_occurred_at": { + "name": "source_occurred_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "event_key": { + "name": "event_key", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "schema_version": { + "name": "schema_version", + "type": "smallint", + "primaryKey": false, + "notNull": false + }, + "finding_snapshot": { + "name": "finding_snapshot", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "source_context": { + "name": "source_context", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "IDX_security_audit_log_org_created": { + "name": "IDX_security_audit_log_org_created", + "columns": [ + { + "expression": "owned_by_organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_security_audit_log_user_created": { + "name": "IDX_security_audit_log_user_created", + "columns": [ + { + "expression": "owned_by_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_security_audit_log_resource": { + "name": "IDX_security_audit_log_resource", + "columns": [ + { + "expression": "resource_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "resource_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_security_audit_log_actor": { + "name": "IDX_security_audit_log_actor", + "columns": [ + { + "expression": "actor_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_security_audit_log_action": { + "name": "IDX_security_audit_log_action", + "columns": [ + { + "expression": "action", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "UQ_security_audit_log_org_event_key": { + "name": "UQ_security_audit_log_org_event_key", + "columns": [ + { + "expression": "owned_by_organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "event_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"security_audit_log\".\"owned_by_organization_id\" IS NOT NULL AND \"security_audit_log\".\"event_key\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "UQ_security_audit_log_user_event_key": { + "name": "UQ_security_audit_log_user_event_key", + "columns": [ + { + "expression": "owned_by_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "event_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"security_audit_log\".\"owned_by_user_id\" IS NOT NULL AND \"security_audit_log\".\"event_key\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_security_audit_log_org_occurred": { + "name": "IDX_security_audit_log_org_occurred", + "columns": [ + { + "expression": "owned_by_organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "occurred_at", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"security_audit_log\".\"owned_by_organization_id\" IS NOT NULL AND \"security_audit_log\".\"occurred_at\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_security_audit_log_user_occurred": { + "name": "IDX_security_audit_log_user_occurred", + "columns": [ + { + "expression": "owned_by_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "occurred_at", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"security_audit_log\".\"owned_by_user_id\" IS NOT NULL AND \"security_audit_log\".\"occurred_at\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "security_audit_log_owned_by_organization_id_organizations_id_fk": { + "name": "security_audit_log_owned_by_organization_id_organizations_id_fk", + "tableFrom": "security_audit_log", + "tableTo": "organizations", + "columnsFrom": [ + "owned_by_organization_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "security_audit_log_owned_by_user_id_kilocode_users_id_fk": { + "name": "security_audit_log_owned_by_user_id_kilocode_users_id_fk", + "tableFrom": "security_audit_log", + "tableTo": "kilocode_users", + "columnsFrom": [ + "owned_by_user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "security_audit_log_owner_check": { + "name": "security_audit_log_owner_check", + "value": "(\"security_audit_log\".\"owned_by_user_id\" IS NOT NULL AND \"security_audit_log\".\"owned_by_organization_id\" IS NULL) OR (\"security_audit_log\".\"owned_by_user_id\" IS NULL AND \"security_audit_log\".\"owned_by_organization_id\" IS NOT NULL)" + }, + "security_audit_log_action_check": { + "name": "security_audit_log_action_check", + "value": "\"security_audit_log\".\"action\" IN ('security.finding.created', 'security.finding.severity_changed', 'security.finding.status_change', 'security.finding.dismissed', 'security.finding.auto_dismissed', 'security.finding.superseded', 'security.finding.analysis_started', 'security.finding.analysis_completed', 'security.finding.analysis_failed', 'security.remediation.queued', 'security.remediation.started', 'security.remediation.pr_opened', 'security.remediation.failed', 'security.remediation.blocked', 'security.remediation.no_changes_needed', 'security.remediation.cancelled', 'security.remediation.retried', 'security.finding.deleted', 'security.config.enabled', 'security.config.disabled', 'security.config.updated', 'security.sync.triggered', 'security.sync.completed', 'security.audit_log.exported', 'security.audit_report.generated')" + }, + "security_audit_log_actor_type_check": { + "name": "security_audit_log_actor_type_check", + "value": "\"security_audit_log\".\"actor_type\" IN ('customer_user', 'kilo_admin', 'system')" + }, + "security_audit_log_source_context_check": { + "name": "security_audit_log_source_context_check", + "value": "\"security_audit_log\".\"source_context\" IN ('security_sync', 'web', 'analysis_worker', 'remediation_callback', 'rollout_baseline')" + } + }, + "isRLSEnabled": false + }, + "public.security_finding_notifications": { + "name": "security_finding_notifications", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "finding_id": { + "name": "finding_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "recipient_user_id": { + "name": "recipient_user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "kind": { + "name": "kind", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'staged'" + }, + "attempt_count": { + "name": "attempt_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "next_attempt_at": { + "name": "next_attempt_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "claimed_at": { + "name": "claimed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "sent_at": { + "name": "sent_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "error_message": { + "name": "error_message", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "uq_security_finding_notifications_finding_recipient_kind": { + "name": "uq_security_finding_notifications_finding_recipient_kind", + "columns": [ + { + "expression": "finding_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "recipient_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "kind", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_security_finding_notifications_pending": { + "name": "idx_security_finding_notifications_pending", + "columns": [ + { + "expression": "next_attempt_at", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"security_finding_notifications\".\"status\" = 'pending'", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_security_finding_notifications_staged": { + "name": "idx_security_finding_notifications_staged", + "columns": [ + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"security_finding_notifications\".\"status\" = 'staged'", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_security_finding_notifications_finding_id": { + "name": "idx_security_finding_notifications_finding_id", + "columns": [ + { + "expression": "finding_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_security_finding_notifications_recipient_user_id": { + "name": "idx_security_finding_notifications_recipient_user_id", + "columns": [ + { + "expression": "recipient_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "security_finding_notifications_finding_fk": { + "name": "security_finding_notifications_finding_fk", + "tableFrom": "security_finding_notifications", + "tableTo": "security_findings", + "columnsFrom": [ + "finding_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "security_finding_notifications_recipient_fk": { + "name": "security_finding_notifications_recipient_fk", + "tableFrom": "security_finding_notifications", + "tableTo": "kilocode_users", + "columnsFrom": [ + "recipient_user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "security_finding_notifications_kind_check": { + "name": "security_finding_notifications_kind_check", + "value": "\"security_finding_notifications\".\"kind\" IN ('new_finding', 'sla_warning', 'sla_breach')" + }, + "security_finding_notifications_status_check": { + "name": "security_finding_notifications_status_check", + "value": "\"security_finding_notifications\".\"status\" IN ('staged', 'pending', 'sending', 'sent', 'failed', 'cancelled')" + }, + "security_finding_notifications_attempt_count_check": { + "name": "security_finding_notifications_attempt_count_check", + "value": "\"security_finding_notifications\".\"attempt_count\" >= 0" + }, + "security_finding_notifications_claimed_at_check": { + "name": "security_finding_notifications_claimed_at_check", + "value": "(\n (\"security_finding_notifications\".\"status\" = 'sending' AND \"security_finding_notifications\".\"claimed_at\" IS NOT NULL) OR\n (\"security_finding_notifications\".\"status\" <> 'sending' AND \"security_finding_notifications\".\"claimed_at\" IS NULL)\n )" + }, + "security_finding_notifications_sent_at_check": { + "name": "security_finding_notifications_sent_at_check", + "value": "(\n (\"security_finding_notifications\".\"status\" = 'sent' AND \"security_finding_notifications\".\"sent_at\" IS NOT NULL) OR\n (\"security_finding_notifications\".\"status\" <> 'sent' AND \"security_finding_notifications\".\"sent_at\" IS NULL)\n )" + }, + "security_finding_notifications_error_message_length_check": { + "name": "security_finding_notifications_error_message_length_check", + "value": "\"security_finding_notifications\".\"error_message\" IS NULL OR length(\"security_finding_notifications\".\"error_message\") <= 500" + } + }, + "isRLSEnabled": false + }, + "public.security_findings": { + "name": "security_findings", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "pg_catalog.gen_random_uuid()" + }, + "owned_by_organization_id": { + "name": "owned_by_organization_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "owned_by_user_id": { + "name": "owned_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "platform_integration_id": { + "name": "platform_integration_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "repo_full_name": { + "name": "repo_full_name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "source": { + "name": "source", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "source_id": { + "name": "source_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "severity": { + "name": "severity", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "ghsa_id": { + "name": "ghsa_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "cve_id": { + "name": "cve_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "package_name": { + "name": "package_name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "package_ecosystem": { + "name": "package_ecosystem", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "vulnerable_version_range": { + "name": "vulnerable_version_range", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "patched_version": { + "name": "patched_version", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "manifest_path": { + "name": "manifest_path", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'open'" + }, + "ignored_reason": { + "name": "ignored_reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "ignored_by": { + "name": "ignored_by", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "fixed_at": { + "name": "fixed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "sla_due_at": { + "name": "sla_due_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "dependabot_html_url": { + "name": "dependabot_html_url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "cwe_ids": { + "name": "cwe_ids", + "type": "text[]", + "primaryKey": false, + "notNull": false + }, + "cvss_score": { + "name": "cvss_score", + "type": "numeric(3, 1)", + "primaryKey": false, + "notNull": false + }, + "dependency_scope": { + "name": "dependency_scope", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "session_id": { + "name": "session_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "cli_session_id": { + "name": "cli_session_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "analysis_status": { + "name": "analysis_status", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "analysis_started_at": { + "name": "analysis_started_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "analysis_completed_at": { + "name": "analysis_completed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "analysis_error": { + "name": "analysis_error", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "analysis": { + "name": "analysis", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "raw_data": { + "name": "raw_data", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "first_detected_at": { + "name": "first_detected_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "last_synced_at": { + "name": "last_synced_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "uq_security_findings_user_source": { + "name": "uq_security_findings_user_source", + "columns": [ + { + "expression": "owned_by_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "repo_full_name", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "source", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "source_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"security_findings\".\"owned_by_user_id\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "uq_security_findings_org_source": { + "name": "uq_security_findings_org_source", + "columns": [ + { + "expression": "owned_by_organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "repo_full_name", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "source", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "source_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"security_findings\".\"owned_by_organization_id\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_security_findings_org_id": { + "name": "idx_security_findings_org_id", + "columns": [ + { + "expression": "owned_by_organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_security_findings_user_id": { + "name": "idx_security_findings_user_id", + "columns": [ + { + "expression": "owned_by_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_security_findings_repo": { + "name": "idx_security_findings_repo", + "columns": [ + { + "expression": "repo_full_name", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_security_findings_severity": { + "name": "idx_security_findings_severity", + "columns": [ + { + "expression": "severity", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_security_findings_status": { + "name": "idx_security_findings_status", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_security_findings_package": { + "name": "idx_security_findings_package", + "columns": [ + { + "expression": "package_name", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_security_findings_sla_due_at": { + "name": "idx_security_findings_sla_due_at", + "columns": [ + { + "expression": "sla_due_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_security_findings_session_id": { + "name": "idx_security_findings_session_id", + "columns": [ + { + "expression": "session_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_security_findings_cli_session_id": { + "name": "idx_security_findings_cli_session_id", + "columns": [ + { + "expression": "cli_session_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_security_findings_analysis_status": { + "name": "idx_security_findings_analysis_status", + "columns": [ + { + "expression": "analysis_status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_security_findings_org_analysis_in_flight": { + "name": "idx_security_findings_org_analysis_in_flight", + "columns": [ + { + "expression": "owned_by_organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "analysis_status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"security_findings\".\"analysis_status\" IN ('pending', 'running')", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_security_findings_user_analysis_in_flight": { + "name": "idx_security_findings_user_analysis_in_flight", + "columns": [ + { + "expression": "owned_by_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "analysis_status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"security_findings\".\"analysis_status\" IN ('pending', 'running')", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "security_findings_owned_by_organization_id_organizations_id_fk": { + "name": "security_findings_owned_by_organization_id_organizations_id_fk", + "tableFrom": "security_findings", + "tableTo": "organizations", + "columnsFrom": [ + "owned_by_organization_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "security_findings_owned_by_user_id_kilocode_users_id_fk": { + "name": "security_findings_owned_by_user_id_kilocode_users_id_fk", + "tableFrom": "security_findings", + "tableTo": "kilocode_users", + "columnsFrom": [ + "owned_by_user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "security_findings_platform_integration_id_platform_integrations_id_fk": { + "name": "security_findings_platform_integration_id_platform_integrations_id_fk", + "tableFrom": "security_findings", + "tableTo": "platform_integrations", + "columnsFrom": [ + "platform_integration_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "security_findings_owner_check": { + "name": "security_findings_owner_check", + "value": "(\n (\"security_findings\".\"owned_by_user_id\" IS NOT NULL AND \"security_findings\".\"owned_by_organization_id\" IS NULL) OR\n (\"security_findings\".\"owned_by_user_id\" IS NULL AND \"security_findings\".\"owned_by_organization_id\" IS NOT NULL)\n )" + } + }, + "isRLSEnabled": false + }, + "public.security_remediation_attempts": { + "name": "security_remediation_attempts", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "pg_catalog.gen_random_uuid()" + }, + "remediation_id": { + "name": "remediation_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "finding_id": { + "name": "finding_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "owned_by_organization_id": { + "name": "owned_by_organization_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "owned_by_user_id": { + "name": "owned_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "repo_full_name": { + "name": "repo_full_name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "origin": { + "name": "origin", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'queued'" + }, + "attempt_number": { + "name": "attempt_number", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "retry_of_attempt_id": { + "name": "retry_of_attempt_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "requested_by_user_id": { + "name": "requested_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "analysis_fingerprint": { + "name": "analysis_fingerprint", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "analysis_completed_at": { + "name": "analysis_completed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "remediation_model_slug": { + "name": "remediation_model_slug", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "branch_name": { + "name": "branch_name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "cloud_agent_session_id": { + "name": "cloud_agent_session_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "kilo_session_id": { + "name": "kilo_session_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "execution_id": { + "name": "execution_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "priority": { + "name": "priority", + "type": "smallint", + "primaryKey": false, + "notNull": true, + "default": 50 + }, + "claim_token": { + "name": "claim_token", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "claimed_at": { + "name": "claimed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "claimed_by_job_id": { + "name": "claimed_by_job_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "launch_attempt_count": { + "name": "launch_attempt_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "next_retry_at": { + "name": "next_retry_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "callback_attempt_token_hash": { + "name": "callback_attempt_token_hash", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "failure_code": { + "name": "failure_code", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "blocked_reason": { + "name": "blocked_reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "last_error_redacted": { + "name": "last_error_redacted", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "structured_result": { + "name": "structured_result", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "final_assistant_message": { + "name": "final_assistant_message", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "validation_evidence": { + "name": "validation_evidence", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "risk_notes": { + "name": "risk_notes", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "draft_reason": { + "name": "draft_reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "pr_url": { + "name": "pr_url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "pr_number": { + "name": "pr_number", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "pr_draft": { + "name": "pr_draft", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "pr_head_branch": { + "name": "pr_head_branch", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "pr_base_branch": { + "name": "pr_base_branch", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "cancellation_requested_at": { + "name": "cancellation_requested_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "cancellation_requested_by_user_id": { + "name": "cancellation_requested_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "queued_at": { + "name": "queued_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "launched_at": { + "name": "launched_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "completed_at": { + "name": "completed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "UQ_security_remediation_attempts_number": { + "name": "UQ_security_remediation_attempts_number", + "columns": [ + { + "expression": "remediation_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "attempt_number", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "UQ_security_remediation_attempts_active_finding": { + "name": "UQ_security_remediation_attempts_active_finding", + "columns": [ + { + "expression": "finding_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"security_remediation_attempts\".\"status\" IN ('queued', 'launching', 'running')", + "concurrently": false, + "method": "btree", + "with": {} + }, + "UQ_security_remediation_attempts_active_remediation": { + "name": "UQ_security_remediation_attempts_active_remediation", + "columns": [ + { + "expression": "remediation_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"security_remediation_attempts\".\"status\" IN ('queued', 'launching', 'running')", + "concurrently": false, + "method": "btree", + "with": {} + }, + "UQ_security_remediation_attempts_finding_fingerprint_terminal": { + "name": "UQ_security_remediation_attempts_finding_fingerprint_terminal", + "columns": [ + { + "expression": "finding_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "analysis_fingerprint", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"security_remediation_attempts\".\"status\" IN ('queued', 'launching', 'running', 'pr_opened')", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_security_remediation_attempts_org_claim": { + "name": "idx_security_remediation_attempts_org_claim", + "columns": [ + { + "expression": "owned_by_organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "coalesce(\"next_retry_at\", '-infinity'::timestamptz)", + "asc": true, + "isExpression": true, + "nulls": "last" + }, + { + "expression": "priority", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "queued_at", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"security_remediation_attempts\".\"status\" = 'queued'", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_security_remediation_attempts_user_claim": { + "name": "idx_security_remediation_attempts_user_claim", + "columns": [ + { + "expression": "owned_by_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "coalesce(\"next_retry_at\", '-infinity'::timestamptz)", + "asc": true, + "isExpression": true, + "nulls": "last" + }, + { + "expression": "priority", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "queued_at", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"security_remediation_attempts\".\"status\" = 'queued'", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_security_remediation_attempts_repo_claim": { + "name": "idx_security_remediation_attempts_repo_claim", + "columns": [ + { + "expression": "repo_full_name", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "coalesce(\"next_retry_at\", '-infinity'::timestamptz)", + "asc": true, + "isExpression": true, + "nulls": "last" + }, + { + "expression": "priority", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "queued_at", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"security_remediation_attempts\".\"status\" = 'queued'", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_security_remediation_attempts_org_inflight": { + "name": "idx_security_remediation_attempts_org_inflight", + "columns": [ + { + "expression": "owned_by_organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "claimed_at", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"security_remediation_attempts\".\"status\" IN ('launching', 'running')", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_security_remediation_attempts_user_inflight": { + "name": "idx_security_remediation_attempts_user_inflight", + "columns": [ + { + "expression": "owned_by_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "claimed_at", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"security_remediation_attempts\".\"status\" IN ('launching', 'running')", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_security_remediation_attempts_repo_inflight": { + "name": "idx_security_remediation_attempts_repo_inflight", + "columns": [ + { + "expression": "repo_full_name", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "claimed_at", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"security_remediation_attempts\".\"status\" IN ('launching', 'running')", + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_security_remediation_attempts_cloud_agent_session": { + "name": "idx_security_remediation_attempts_cloud_agent_session", + "columns": [ + { + "expression": "cloud_agent_session_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_security_remediation_attempts_finding_fingerprint": { + "name": "idx_security_remediation_attempts_finding_fingerprint", + "columns": [ + { + "expression": "finding_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "analysis_fingerprint", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "security_remediation_attempts_remediation_id_security_remediations_id_fk": { + "name": "security_remediation_attempts_remediation_id_security_remediations_id_fk", + "tableFrom": "security_remediation_attempts", + "tableTo": "security_remediations", + "columnsFrom": [ + "remediation_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "security_remediation_attempts_finding_id_security_findings_id_fk": { + "name": "security_remediation_attempts_finding_id_security_findings_id_fk", + "tableFrom": "security_remediation_attempts", + "tableTo": "security_findings", + "columnsFrom": [ + "finding_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "security_remediation_attempts_owned_by_organization_id_organizations_id_fk": { + "name": "security_remediation_attempts_owned_by_organization_id_organizations_id_fk", + "tableFrom": "security_remediation_attempts", + "tableTo": "organizations", + "columnsFrom": [ + "owned_by_organization_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "security_remediation_attempts_owned_by_user_id_kilocode_users_id_fk": { + "name": "security_remediation_attempts_owned_by_user_id_kilocode_users_id_fk", + "tableFrom": "security_remediation_attempts", + "tableTo": "kilocode_users", + "columnsFrom": [ + "owned_by_user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "security_remediation_attempts_requested_by_user_id_kilocode_users_id_fk": { + "name": "security_remediation_attempts_requested_by_user_id_kilocode_users_id_fk", + "tableFrom": "security_remediation_attempts", + "tableTo": "kilocode_users", + "columnsFrom": [ + "requested_by_user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "security_remediation_attempts_cancellation_requested_by_user_id_kilocode_users_id_fk": { + "name": "security_remediation_attempts_cancellation_requested_by_user_id_kilocode_users_id_fk", + "tableFrom": "security_remediation_attempts", + "tableTo": "kilocode_users", + "columnsFrom": [ + "cancellation_requested_by_user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "security_remediation_attempts_owner_check": { + "name": "security_remediation_attempts_owner_check", + "value": "(\n (\"security_remediation_attempts\".\"owned_by_user_id\" IS NOT NULL AND \"security_remediation_attempts\".\"owned_by_organization_id\" IS NULL) OR\n (\"security_remediation_attempts\".\"owned_by_user_id\" IS NULL AND \"security_remediation_attempts\".\"owned_by_organization_id\" IS NOT NULL)\n )" + }, + "security_remediation_attempts_status_check": { + "name": "security_remediation_attempts_status_check", + "value": "\"security_remediation_attempts\".\"status\" IN ('queued', 'launching', 'running', 'pr_opened', 'failed', 'blocked', 'no_changes_needed', 'cancelled')" + }, + "security_remediation_attempts_origin_check": { + "name": "security_remediation_attempts_origin_check", + "value": "\"security_remediation_attempts\".\"origin\" IN ('auto_policy', 'bulk_existing', 'manual')" + }, + "security_remediation_attempts_attempt_number_check": { + "name": "security_remediation_attempts_attempt_number_check", + "value": "\"security_remediation_attempts\".\"attempt_number\" >= 1" + }, + "security_remediation_attempts_launch_attempt_count_check": { + "name": "security_remediation_attempts_launch_attempt_count_check", + "value": "\"security_remediation_attempts\".\"launch_attempt_count\" >= 0" + } + }, + "isRLSEnabled": false + }, + "public.security_remediations": { + "name": "security_remediations", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "pg_catalog.gen_random_uuid()" + }, + "owned_by_organization_id": { + "name": "owned_by_organization_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "owned_by_user_id": { + "name": "owned_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "finding_id": { + "name": "finding_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "repo_full_name": { + "name": "repo_full_name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'queued'" + }, + "latest_attempt_id": { + "name": "latest_attempt_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "latest_analysis_fingerprint": { + "name": "latest_analysis_fingerprint", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "latest_analysis_completed_at": { + "name": "latest_analysis_completed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "pr_url": { + "name": "pr_url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "pr_number": { + "name": "pr_number", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "pr_draft": { + "name": "pr_draft", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "pr_head_branch": { + "name": "pr_head_branch", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "pr_base_branch": { + "name": "pr_base_branch", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "failure_code": { + "name": "failure_code", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "blocked_reason": { + "name": "blocked_reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "outcome_summary": { + "name": "outcome_summary", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "completed_at": { + "name": "completed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "UQ_security_remediations_finding_id": { + "name": "UQ_security_remediations_finding_id", + "columns": [ + { + "expression": "finding_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_security_remediations_org_status": { + "name": "idx_security_remediations_org_status", + "columns": [ + { + "expression": "owned_by_organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_security_remediations_user_status": { + "name": "idx_security_remediations_user_status", + "columns": [ + { + "expression": "owned_by_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_security_remediations_repo_status": { + "name": "idx_security_remediations_repo_status", + "columns": [ + { + "expression": "repo_full_name", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_security_remediations_latest_attempt": { + "name": "idx_security_remediations_latest_attempt", + "columns": [ + { + "expression": "latest_attempt_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "security_remediations_owned_by_organization_id_organizations_id_fk": { + "name": "security_remediations_owned_by_organization_id_organizations_id_fk", + "tableFrom": "security_remediations", + "tableTo": "organizations", + "columnsFrom": [ + "owned_by_organization_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "security_remediations_owned_by_user_id_kilocode_users_id_fk": { + "name": "security_remediations_owned_by_user_id_kilocode_users_id_fk", + "tableFrom": "security_remediations", + "tableTo": "kilocode_users", + "columnsFrom": [ + "owned_by_user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "security_remediations_finding_id_security_findings_id_fk": { + "name": "security_remediations_finding_id_security_findings_id_fk", + "tableFrom": "security_remediations", + "tableTo": "security_findings", + "columnsFrom": [ + "finding_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "security_remediations_owner_check": { + "name": "security_remediations_owner_check", + "value": "(\n (\"security_remediations\".\"owned_by_user_id\" IS NOT NULL AND \"security_remediations\".\"owned_by_organization_id\" IS NULL) OR\n (\"security_remediations\".\"owned_by_user_id\" IS NULL AND \"security_remediations\".\"owned_by_organization_id\" IS NOT NULL)\n )" + }, + "security_remediations_status_check": { + "name": "security_remediations_status_check", + "value": "\"security_remediations\".\"status\" IN ('queued', 'running', 'pr_opened', 'failed', 'blocked', 'no_changes_needed', 'cancelled')" + } + }, + "isRLSEnabled": false + }, + "public.shared_cli_sessions": { + "name": "shared_cli_sessions", + "schema": "", + "columns": { + "share_id": { + "name": "share_id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "pg_catalog.gen_random_uuid()" + }, + "session_id": { + "name": "session_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "kilo_user_id": { + "name": "kilo_user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "shared_state": { + "name": "shared_state", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'public'" + }, + "api_conversation_history_blob_url": { + "name": "api_conversation_history_blob_url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "task_metadata_blob_url": { + "name": "task_metadata_blob_url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "ui_messages_blob_url": { + "name": "ui_messages_blob_url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "git_state_blob_url": { + "name": "git_state_blob_url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "IDX_shared_cli_sessions_session_id": { + "name": "IDX_shared_cli_sessions_session_id", + "columns": [ + { + "expression": "session_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_shared_cli_sessions_created_at": { + "name": "IDX_shared_cli_sessions_created_at", + "columns": [ + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "shared_cli_sessions_session_id_cli_sessions_session_id_fk": { + "name": "shared_cli_sessions_session_id_cli_sessions_session_id_fk", + "tableFrom": "shared_cli_sessions", + "tableTo": "cli_sessions", + "columnsFrom": [ + "session_id" + ], + "columnsTo": [ + "session_id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "shared_cli_sessions_kilo_user_id_kilocode_users_id_fk": { + "name": "shared_cli_sessions_kilo_user_id_kilocode_users_id_fk", + "tableFrom": "shared_cli_sessions", + "tableTo": "kilocode_users", + "columnsFrom": [ + "kilo_user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "restrict", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "shared_cli_sessions_shared_state_check": { + "name": "shared_cli_sessions_shared_state_check", + "value": "\"shared_cli_sessions\".\"shared_state\" IN ('public', 'organization')" + } + }, + "isRLSEnabled": false + }, + "public.slack_bot_requests": { + "name": "slack_bot_requests", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "pg_catalog.gen_random_uuid()" + }, + "owned_by_organization_id": { + "name": "owned_by_organization_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "owned_by_user_id": { + "name": "owned_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "platform_integration_id": { + "name": "platform_integration_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "slack_team_id": { + "name": "slack_team_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "slack_team_name": { + "name": "slack_team_name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "slack_channel_id": { + "name": "slack_channel_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "slack_user_id": { + "name": "slack_user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "slack_thread_ts": { + "name": "slack_thread_ts", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "event_type": { + "name": "event_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "user_message": { + "name": "user_message", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "user_message_truncated": { + "name": "user_message_truncated", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "error_message": { + "name": "error_message", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "response_time_ms": { + "name": "response_time_ms", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "model_used": { + "name": "model_used", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "tool_calls_made": { + "name": "tool_calls_made", + "type": "text[]", + "primaryKey": false, + "notNull": false + }, + "cloud_agent_session_id": { + "name": "cloud_agent_session_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "idx_slack_bot_requests_created_at": { + "name": "idx_slack_bot_requests_created_at", + "columns": [ + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_slack_bot_requests_slack_team_id": { + "name": "idx_slack_bot_requests_slack_team_id", + "columns": [ + { + "expression": "slack_team_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_slack_bot_requests_owned_by_org_id": { + "name": "idx_slack_bot_requests_owned_by_org_id", + "columns": [ + { + "expression": "owned_by_organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_slack_bot_requests_owned_by_user_id": { + "name": "idx_slack_bot_requests_owned_by_user_id", + "columns": [ + { + "expression": "owned_by_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_slack_bot_requests_status": { + "name": "idx_slack_bot_requests_status", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_slack_bot_requests_event_type": { + "name": "idx_slack_bot_requests_event_type", + "columns": [ + { + "expression": "event_type", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_slack_bot_requests_team_created": { + "name": "idx_slack_bot_requests_team_created", + "columns": [ + { + "expression": "slack_team_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "slack_bot_requests_owned_by_organization_id_organizations_id_fk": { + "name": "slack_bot_requests_owned_by_organization_id_organizations_id_fk", + "tableFrom": "slack_bot_requests", + "tableTo": "organizations", + "columnsFrom": [ + "owned_by_organization_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "slack_bot_requests_owned_by_user_id_kilocode_users_id_fk": { + "name": "slack_bot_requests_owned_by_user_id_kilocode_users_id_fk", + "tableFrom": "slack_bot_requests", + "tableTo": "kilocode_users", + "columnsFrom": [ + "owned_by_user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "slack_bot_requests_platform_integration_id_platform_integrations_id_fk": { + "name": "slack_bot_requests_platform_integration_id_platform_integrations_id_fk", + "tableFrom": "slack_bot_requests", + "tableTo": "platform_integrations", + "columnsFrom": [ + "platform_integration_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "slack_bot_requests_owner_check": { + "name": "slack_bot_requests_owner_check", + "value": "(\n (\"slack_bot_requests\".\"owned_by_user_id\" IS NOT NULL AND \"slack_bot_requests\".\"owned_by_organization_id\" IS NULL) OR\n (\"slack_bot_requests\".\"owned_by_user_id\" IS NULL AND \"slack_bot_requests\".\"owned_by_organization_id\" IS NOT NULL) OR\n (\"slack_bot_requests\".\"owned_by_user_id\" IS NULL AND \"slack_bot_requests\".\"owned_by_organization_id\" IS NULL)\n )" + } + }, + "isRLSEnabled": false + }, + "public.slack_oauth_credentials": { + "name": "slack_oauth_credentials", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "pg_catalog.gen_random_uuid()" + }, + "platform_integration_id": { + "name": "platform_integration_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "slack_team_id": { + "name": "slack_team_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "slack_enterprise_id": { + "name": "slack_enterprise_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "is_enterprise_install": { + "name": "is_enterprise_install", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "bot_user_id": { + "name": "bot_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "access_token_encrypted": { + "name": "access_token_encrypted", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "access_token_expires_at": { + "name": "access_token_expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "refresh_token_encrypted": { + "name": "refresh_token_encrypted", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "granted_scopes": { + "name": "granted_scopes", + "type": "text[]", + "primaryKey": false, + "notNull": false + }, + "credential_version": { + "name": "credential_version", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 1 + }, + "refresh_claimed_at": { + "name": "refresh_claimed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "refresh_attempt_count": { + "name": "refresh_attempt_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "next_refresh_attempt_at": { + "name": "next_refresh_attempt_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "last_refreshed_at": { + "name": "last_refreshed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "revoked_at": { + "name": "revoked_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "revocation_reason": { + "name": "revocation_reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "last_used_at": { + "name": "last_used_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "UQ_slack_oauth_credentials_platform_integration_id": { + "name": "UQ_slack_oauth_credentials_platform_integration_id", + "columns": [ + { + "expression": "platform_integration_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_slack_oauth_credentials_slack_team_id": { + "name": "IDX_slack_oauth_credentials_slack_team_id", + "columns": [ + { + "expression": "slack_team_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_slack_oauth_credentials_refresh_due": { + "name": "IDX_slack_oauth_credentials_refresh_due", + "columns": [ + { + "expression": "access_token_expires_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"slack_oauth_credentials\".\"revoked_at\" is null", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "slack_oauth_credentials_platform_integration_id_platform_integrations_id_fk": { + "name": "slack_oauth_credentials_platform_integration_id_platform_integrations_id_fk", + "tableFrom": "slack_oauth_credentials", + "tableTo": "platform_integrations", + "columnsFrom": [ + "platform_integration_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "slack_oauth_credentials_credential_version_check": { + "name": "slack_oauth_credentials_credential_version_check", + "value": "\"slack_oauth_credentials\".\"credential_version\" > 0" + }, + "slack_oauth_credentials_refresh_attempt_count_check": { + "name": "slack_oauth_credentials_refresh_attempt_count_check", + "value": "\"slack_oauth_credentials\".\"refresh_attempt_count\" >= 0" + }, + "slack_oauth_credentials_slack_team_id_check": { + "name": "slack_oauth_credentials_slack_team_id_check", + "value": "\"slack_oauth_credentials\".\"slack_team_id\" <> ''" + } + }, + "isRLSEnabled": false + }, + "public.source_embeddings": { + "name": "source_embeddings", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "pg_catalog.gen_random_uuid()" + }, + "organization_id": { + "name": "organization_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "kilo_user_id": { + "name": "kilo_user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "project_id": { + "name": "project_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "embedding": { + "name": "embedding", + "type": "vector(1536)", + "primaryKey": false, + "notNull": true + }, + "file_path": { + "name": "file_path", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "file_hash": { + "name": "file_hash", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "start_line": { + "name": "start_line", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "end_line": { + "name": "end_line", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "git_branch": { + "name": "git_branch", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'main'" + }, + "is_base_branch": { + "name": "is_base_branch", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "IDX_source_embeddings_organization_id": { + "name": "IDX_source_embeddings_organization_id", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_source_embeddings_kilo_user_id": { + "name": "IDX_source_embeddings_kilo_user_id", + "columns": [ + { + "expression": "kilo_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_source_embeddings_project_id": { + "name": "IDX_source_embeddings_project_id", + "columns": [ + { + "expression": "project_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_source_embeddings_created_at": { + "name": "IDX_source_embeddings_created_at", + "columns": [ + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_source_embeddings_updated_at": { + "name": "IDX_source_embeddings_updated_at", + "columns": [ + { + "expression": "updated_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_source_embeddings_file_path_lower": { + "name": "IDX_source_embeddings_file_path_lower", + "columns": [ + { + "expression": "LOWER(\"file_path\")", + "asc": true, + "isExpression": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_source_embeddings_git_branch": { + "name": "IDX_source_embeddings_git_branch", + "columns": [ + { + "expression": "git_branch", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_source_embeddings_org_project_branch": { + "name": "IDX_source_embeddings_org_project_branch", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "project_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "git_branch", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "source_embeddings_organization_id_organizations_id_fk": { + "name": "source_embeddings_organization_id_organizations_id_fk", + "tableFrom": "source_embeddings", + "tableTo": "organizations", + "columnsFrom": [ + "organization_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "source_embeddings_kilo_user_id_kilocode_users_id_fk": { + "name": "source_embeddings_kilo_user_id_kilocode_users_id_fk", + "tableFrom": "source_embeddings", + "tableTo": "kilocode_users", + "columnsFrom": [ + "kilo_user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "UQ_source_embeddings_org_project_branch_file_lines": { + "name": "UQ_source_embeddings_org_project_branch_file_lines", + "nullsNotDistinct": false, + "columns": [ + "organization_id", + "project_id", + "git_branch", + "file_path", + "start_line", + "end_line" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.spend_alert_deliveries": { + "name": "spend_alert_deliveries", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "pg_catalog.gen_random_uuid()" + }, + "dedupe_key": { + "name": "dedupe_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "scope_key": { + "name": "scope_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "rule_id": { + "name": "rule_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "kind": { + "name": "kind", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "channel": { + "name": "channel", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "fired_at": { + "name": "fired_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "recipients": { + "name": "recipients", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "payload": { + "name": "payload", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "attempt_count": { + "name": "attempt_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "next_attempt_at": { + "name": "next_attempt_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "last_error_redacted": { + "name": "last_error_redacted", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "IDX_spend_alert_deliveries_pending": { + "name": "IDX_spend_alert_deliveries_pending", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "next_attempt_at", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "attempt_count", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "spend_alert_deliveries_rule_id_spend_alert_rules_id_fk": { + "name": "spend_alert_deliveries_rule_id_spend_alert_rules_id_fk", + "tableFrom": "spend_alert_deliveries", + "tableTo": "spend_alert_rules", + "columnsFrom": [ + "rule_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "UQ_spend_alert_deliveries_dedupe_key": { + "name": "UQ_spend_alert_deliveries_dedupe_key", + "nullsNotDistinct": false, + "columns": [ + "dedupe_key" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.spend_alert_hourly": { + "name": "spend_alert_hourly", + "schema": "", + "columns": { + "scope_key": { + "name": "scope_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "hour_start": { + "name": "hour_start", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "cost_microdollars": { + "name": "cost_microdollars", + "type": "bigint", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "uq_spend_alert_hourly_scope_hour": { + "name": "uq_spend_alert_hourly_scope_hour", + "columns": [ + { + "expression": "scope_key", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "hour_start", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_spend_alert_hourly_hour_start": { + "name": "IDX_spend_alert_hourly_hour_start", + "columns": [ + { + "expression": "hour_start", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.spend_alert_rule_state": { + "name": "spend_alert_rule_state", + "schema": "", + "columns": { + "rule_id": { + "name": "rule_id", + "type": "uuid", + "primaryKey": true, + "notNull": true + }, + "firing": { + "name": "firing", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "condition_started_at": { + "name": "condition_started_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "last_value_microdollars": { + "name": "last_value_microdollars", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "spend_alert_rule_state_rule_id_spend_alert_rules_id_fk": { + "name": "spend_alert_rule_state_rule_id_spend_alert_rules_id_fk", + "tableFrom": "spend_alert_rule_state", + "tableTo": "spend_alert_rules", + "columnsFrom": [ + "rule_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.spend_alert_rules": { + "name": "spend_alert_rules", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "pg_catalog.gen_random_uuid()" + }, + "settings_id": { + "name": "settings_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "kind": { + "name": "kind", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "enabled": { + "name": "enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "threshold_microdollars": { + "name": "threshold_microdollars", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "window_hours": { + "name": "window_hours", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "multiplier_basis_points": { + "name": "multiplier_basis_points", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "email_enabled": { + "name": "email_enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "push_enabled": { + "name": "push_enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "uq_spend_alert_rules_kind": { + "name": "uq_spend_alert_rules_kind", + "columns": [ + { + "expression": "settings_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "kind", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "spend_alert_rules_settings_id_spend_alert_settings_id_fk": { + "name": "spend_alert_rules_settings_id_spend_alert_settings_id_fk", + "tableFrom": "spend_alert_rules", + "tableTo": "spend_alert_settings", + "columnsFrom": [ + "settings_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.spend_alert_settings": { + "name": "spend_alert_settings", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "pg_catalog.gen_random_uuid()" + }, + "scope_key": { + "name": "scope_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "kilo_user_id": { + "name": "kilo_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "organization_id": { + "name": "organization_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "enabled": { + "name": "enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "uq_spend_alert_settings_scope": { + "name": "uq_spend_alert_settings_scope", + "columns": [ + { + "expression": "scope_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "spend_alert_settings_kilo_user_id_kilocode_users_id_fk": { + "name": "spend_alert_settings_kilo_user_id_kilocode_users_id_fk", + "tableFrom": "spend_alert_settings", + "tableTo": "kilocode_users", + "columnsFrom": [ + "kilo_user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "spend_alert_settings_organization_id_organizations_id_fk": { + "name": "spend_alert_settings_organization_id_organizations_id_fk", + "tableFrom": "spend_alert_settings", + "tableTo": "organizations", + "columnsFrom": [ + "organization_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "spend_alert_settings_scope_check": { + "name": "spend_alert_settings_scope_check", + "value": "(\"spend_alert_settings\".\"kilo_user_id\" IS NOT NULL) <> (\"spend_alert_settings\".\"organization_id\" IS NOT NULL)" + } + }, + "isRLSEnabled": false + }, + "public.stripe_dispute_actions": { + "name": "stripe_dispute_actions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "pg_catalog.gen_random_uuid()" + }, + "case_id": { + "name": "case_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "action_type": { + "name": "action_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "target_key": { + "name": "target_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'queued'" + }, + "attempt_count": { + "name": "attempt_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "next_retry_at": { + "name": "next_retry_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "claimed_at": { + "name": "claimed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "last_attempt_at": { + "name": "last_attempt_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "completed_at": { + "name": "completed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "terminal_at": { + "name": "terminal_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "result_code": { + "name": "result_code", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "result_reference_id": { + "name": "result_reference_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "failure_context": { + "name": "failure_context", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "IDX_stripe_dispute_actions_case_id": { + "name": "IDX_stripe_dispute_actions_case_id", + "columns": [ + { + "expression": "case_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_stripe_dispute_actions_claim_path": { + "name": "IDX_stripe_dispute_actions_claim_path", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "coalesce(\"next_retry_at\", '-infinity'::timestamptz)", + "asc": true, + "isExpression": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "stripe_dispute_actions_case_id_stripe_dispute_cases_id_fk": { + "name": "stripe_dispute_actions_case_id_stripe_dispute_cases_id_fk", + "tableFrom": "stripe_dispute_actions", + "tableTo": "stripe_dispute_cases", + "columnsFrom": [ + "case_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "restrict", + "onUpdate": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "UQ_stripe_dispute_actions_case_type_target": { + "name": "UQ_stripe_dispute_actions_case_type_target", + "nullsNotDistinct": false, + "columns": [ + "case_id", + "action_type", + "target_key" + ] + } + }, + "policies": {}, + "checkConstraints": { + "stripe_dispute_actions_action_type_check": { + "name": "stripe_dispute_actions_action_type_check", + "value": "\"stripe_dispute_actions\".\"action_type\" IN ('stripe_acceptance', 'user_block', 'auto_top_up_disable', 'credit_balance_reset', 'subscription_cancellation', 'access_termination', 'kiloclaw_suspension')" + }, + "stripe_dispute_actions_status_check": { + "name": "stripe_dispute_actions_status_check", + "value": "\"stripe_dispute_actions\".\"status\" IN ('queued', 'processing', 'completed', 'failed', 'skipped')" + }, + "stripe_dispute_actions_attempt_count_non_negative_check": { + "name": "stripe_dispute_actions_attempt_count_non_negative_check", + "value": "\"stripe_dispute_actions\".\"attempt_count\" >= 0" + }, + "stripe_dispute_actions_target_key_not_empty_check": { + "name": "stripe_dispute_actions_target_key_not_empty_check", + "value": "length(\"stripe_dispute_actions\".\"target_key\") > 0" + } + }, + "isRLSEnabled": false + }, + "public.stripe_dispute_cases": { + "name": "stripe_dispute_cases", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "pg_catalog.gen_random_uuid()" + }, + "stripe_dispute_id": { + "name": "stripe_dispute_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "stripe_event_id": { + "name": "stripe_event_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "stripe_event_created_at": { + "name": "stripe_event_created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "stripe_charge_id": { + "name": "stripe_charge_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "stripe_payment_intent_id": { + "name": "stripe_payment_intent_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "stripe_customer_id": { + "name": "stripe_customer_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "amount_minor_units": { + "name": "amount_minor_units", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "currency": { + "name": "currency", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "dispute_reason": { + "name": "dispute_reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "stripe_status": { + "name": "stripe_status", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "owner_classification": { + "name": "owner_classification", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "kilo_user_id": { + "name": "kilo_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "organization_id": { + "name": "organization_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'needs_action'" + }, + "status_reason": { + "name": "status_reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "failure_context": { + "name": "failure_context", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "stripe_created_at": { + "name": "stripe_created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "evidence_due_by": { + "name": "evidence_due_by", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "synced_at": { + "name": "synced_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "accepted_by_kilo_user_id": { + "name": "accepted_by_kilo_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "acceptance_started_at": { + "name": "acceptance_started_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "next_retry_at": { + "name": "next_retry_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "accepted_at": { + "name": "accepted_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "enforcement_completed_at": { + "name": "enforcement_completed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "review_required_at": { + "name": "review_required_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "closed_at": { + "name": "closed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "IDX_stripe_dispute_cases_event_id": { + "name": "IDX_stripe_dispute_cases_event_id", + "columns": [ + { + "expression": "stripe_event_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_stripe_dispute_cases_charge_id": { + "name": "IDX_stripe_dispute_cases_charge_id", + "columns": [ + { + "expression": "stripe_charge_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_stripe_dispute_cases_payment_intent_id": { + "name": "IDX_stripe_dispute_cases_payment_intent_id", + "columns": [ + { + "expression": "stripe_payment_intent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_stripe_dispute_cases_customer_id": { + "name": "IDX_stripe_dispute_cases_customer_id", + "columns": [ + { + "expression": "stripe_customer_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_stripe_dispute_cases_kilo_user_id": { + "name": "IDX_stripe_dispute_cases_kilo_user_id", + "columns": [ + { + "expression": "kilo_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_stripe_dispute_cases_organization_id": { + "name": "IDX_stripe_dispute_cases_organization_id", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_stripe_dispute_cases_status_due_by": { + "name": "IDX_stripe_dispute_cases_status_due_by", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "evidence_due_by", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "stripe_created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "stripe_dispute_cases_kilo_user_id_kilocode_users_id_fk": { + "name": "stripe_dispute_cases_kilo_user_id_kilocode_users_id_fk", + "tableFrom": "stripe_dispute_cases", + "tableTo": "kilocode_users", + "columnsFrom": [ + "kilo_user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "cascade" + }, + "stripe_dispute_cases_organization_id_organizations_id_fk": { + "name": "stripe_dispute_cases_organization_id_organizations_id_fk", + "tableFrom": "stripe_dispute_cases", + "tableTo": "organizations", + "columnsFrom": [ + "organization_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "cascade" + }, + "stripe_dispute_cases_accepted_by_kilo_user_id_kilocode_users_id_fk": { + "name": "stripe_dispute_cases_accepted_by_kilo_user_id_kilocode_users_id_fk", + "tableFrom": "stripe_dispute_cases", + "tableTo": "kilocode_users", + "columnsFrom": [ + "accepted_by_kilo_user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "UQ_stripe_dispute_cases_dispute_id": { + "name": "UQ_stripe_dispute_cases_dispute_id", + "nullsNotDistinct": false, + "columns": [ + "stripe_dispute_id" + ] + } + }, + "policies": {}, + "checkConstraints": { + "stripe_dispute_cases_owner_classification_check": { + "name": "stripe_dispute_cases_owner_classification_check", + "value": "\"stripe_dispute_cases\".\"owner_classification\" IN ('personal', 'organization', 'ambiguous', 'unmatched')" + }, + "stripe_dispute_cases_status_check": { + "name": "stripe_dispute_cases_status_check", + "value": "\"stripe_dispute_cases\".\"status\" IN ('needs_action', 'processing', 'accepted', 'acceptance_failed', 'enforcement_failed', 'review_required', 'closed')" + }, + "stripe_dispute_cases_amount_minor_units_non_negative_check": { + "name": "stripe_dispute_cases_amount_minor_units_non_negative_check", + "value": "\"stripe_dispute_cases\".\"amount_minor_units\" IS NULL OR \"stripe_dispute_cases\".\"amount_minor_units\" >= 0" + } + }, + "isRLSEnabled": false + }, + "public.stripe_early_fraud_warning_actions": { + "name": "stripe_early_fraud_warning_actions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "pg_catalog.gen_random_uuid()" + }, + "case_id": { + "name": "case_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "action_type": { + "name": "action_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "target_key": { + "name": "target_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'queued'" + }, + "attempt_count": { + "name": "attempt_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "next_retry_at": { + "name": "next_retry_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "claimed_at": { + "name": "claimed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "last_attempt_at": { + "name": "last_attempt_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "completed_at": { + "name": "completed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "terminal_at": { + "name": "terminal_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "result_code": { + "name": "result_code", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "result_reference_id": { + "name": "result_reference_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "failure_context": { + "name": "failure_context", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "IDX_stripe_early_fraud_warning_actions_case_id": { + "name": "IDX_stripe_early_fraud_warning_actions_case_id", + "columns": [ + { + "expression": "case_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_stripe_early_fraud_warning_actions_claim_path": { + "name": "IDX_stripe_early_fraud_warning_actions_claim_path", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "coalesce(\"next_retry_at\", '-infinity'::timestamptz)", + "asc": true, + "isExpression": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "stripe_early_fraud_warning_actions_case_id_stripe_early_fraud_warning_cases_id_fk": { + "name": "stripe_early_fraud_warning_actions_case_id_stripe_early_fraud_warning_cases_id_fk", + "tableFrom": "stripe_early_fraud_warning_actions", + "tableTo": "stripe_early_fraud_warning_cases", + "columnsFrom": [ + "case_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "restrict", + "onUpdate": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "UQ_stripe_early_fraud_warning_actions_case_type_target": { + "name": "UQ_stripe_early_fraud_warning_actions_case_type_target", + "nullsNotDistinct": false, + "columns": [ + "case_id", + "action_type", + "target_key" + ] + } + }, + "policies": {}, + "checkConstraints": { + "stripe_early_fraud_warning_actions_action_type_check": { + "name": "stripe_early_fraud_warning_actions_action_type_check", + "value": "\"stripe_early_fraud_warning_actions\".\"action_type\" IN ('containment', 'refund', 'payment_value_clawback', 'subscription_termination', 'access_termination', 'kiloclaw_suspension', 'affiliate_payout_reversal', 'referral_reward_reversal', 'user_notice')" + }, + "stripe_early_fraud_warning_actions_status_check": { + "name": "stripe_early_fraud_warning_actions_status_check", + "value": "\"stripe_early_fraud_warning_actions\".\"status\" IN ('queued', 'processing', 'completed', 'failed', 'review_required', 'dismissed')" + }, + "stripe_early_fraud_warning_actions_attempt_count_non_negative_check": { + "name": "stripe_early_fraud_warning_actions_attempt_count_non_negative_check", + "value": "\"stripe_early_fraud_warning_actions\".\"attempt_count\" >= 0" + }, + "stripe_early_fraud_warning_actions_target_key_not_empty_check": { + "name": "stripe_early_fraud_warning_actions_target_key_not_empty_check", + "value": "length(\"stripe_early_fraud_warning_actions\".\"target_key\") > 0" + } + }, + "isRLSEnabled": false + }, + "public.stripe_early_fraud_warning_cases": { + "name": "stripe_early_fraud_warning_cases", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "pg_catalog.gen_random_uuid()" + }, + "stripe_early_fraud_warning_id": { + "name": "stripe_early_fraud_warning_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "stripe_event_id": { + "name": "stripe_event_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "stripe_charge_id": { + "name": "stripe_charge_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "stripe_payment_intent_id": { + "name": "stripe_payment_intent_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "stripe_customer_id": { + "name": "stripe_customer_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "amount_minor_units": { + "name": "amount_minor_units", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "currency": { + "name": "currency", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "owner_classification": { + "name": "owner_classification", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "kilo_user_id": { + "name": "kilo_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "organization_id": { + "name": "organization_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'queued'" + }, + "reason": { + "name": "reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "failure_context": { + "name": "failure_context", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "warning_created_at": { + "name": "warning_created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "contained_at": { + "name": "contained_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "processing_started_at": { + "name": "processing_started_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "completed_at": { + "name": "completed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "review_required_at": { + "name": "review_required_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "remediated_at": { + "name": "remediated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "dismissed_at": { + "name": "dismissed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "IDX_stripe_early_fraud_warning_cases_event_id": { + "name": "IDX_stripe_early_fraud_warning_cases_event_id", + "columns": [ + { + "expression": "stripe_event_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_stripe_early_fraud_warning_cases_charge_id": { + "name": "IDX_stripe_early_fraud_warning_cases_charge_id", + "columns": [ + { + "expression": "stripe_charge_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_stripe_early_fraud_warning_cases_payment_intent_id": { + "name": "IDX_stripe_early_fraud_warning_cases_payment_intent_id", + "columns": [ + { + "expression": "stripe_payment_intent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_stripe_early_fraud_warning_cases_customer_id": { + "name": "IDX_stripe_early_fraud_warning_cases_customer_id", + "columns": [ + { + "expression": "stripe_customer_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_stripe_early_fraud_warning_cases_kilo_user_id": { + "name": "IDX_stripe_early_fraud_warning_cases_kilo_user_id", + "columns": [ + { + "expression": "kilo_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_stripe_early_fraud_warning_cases_organization_id": { + "name": "IDX_stripe_early_fraud_warning_cases_organization_id", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_stripe_early_fraud_warning_cases_status_created_at": { + "name": "IDX_stripe_early_fraud_warning_cases_status_created_at", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "stripe_early_fraud_warning_cases_kilo_user_id_kilocode_users_id_fk": { + "name": "stripe_early_fraud_warning_cases_kilo_user_id_kilocode_users_id_fk", + "tableFrom": "stripe_early_fraud_warning_cases", + "tableTo": "kilocode_users", + "columnsFrom": [ + "kilo_user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "cascade" + }, + "stripe_early_fraud_warning_cases_organization_id_organizations_id_fk": { + "name": "stripe_early_fraud_warning_cases_organization_id_organizations_id_fk", + "tableFrom": "stripe_early_fraud_warning_cases", + "tableTo": "organizations", + "columnsFrom": [ + "organization_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "UQ_stripe_early_fraud_warning_cases_warning_id": { + "name": "UQ_stripe_early_fraud_warning_cases_warning_id", + "nullsNotDistinct": false, + "columns": [ + "stripe_early_fraud_warning_id" + ] + } + }, + "policies": {}, + "checkConstraints": { + "stripe_early_fraud_warning_cases_owner_classification_check": { + "name": "stripe_early_fraud_warning_cases_owner_classification_check", + "value": "\"stripe_early_fraud_warning_cases\".\"owner_classification\" IN ('personal', 'organization', 'ambiguous', 'unmatched')" + }, + "stripe_early_fraud_warning_cases_status_check": { + "name": "stripe_early_fraud_warning_cases_status_check", + "value": "\"stripe_early_fraud_warning_cases\".\"status\" IN ('queued', 'contained', 'processing', 'completed', 'review_required', 'failed', 'remediated', 'dismissed')" + }, + "stripe_early_fraud_warning_cases_amount_minor_units_non_negative_check": { + "name": "stripe_early_fraud_warning_cases_amount_minor_units_non_negative_check", + "value": "\"stripe_early_fraud_warning_cases\".\"amount_minor_units\" IS NULL OR \"stripe_early_fraud_warning_cases\".\"amount_minor_units\" >= 0" + } + }, + "isRLSEnabled": false + }, + "public.stripe_service_fee_assessments": { + "name": "stripe_service_fee_assessments", + "schema": "", + "columns": { + "assessment_key": { + "name": "assessment_key", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "version": { + "name": "version", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "flow": { + "name": "flow", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "outcome": { + "name": "outcome", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "currency": { + "name": "currency", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "kilo_user_id": { + "name": "kilo_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "organization_id": { + "name": "organization_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "stripe_customer_id": { + "name": "stripe_customer_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "stripe_checkout_session_id": { + "name": "stripe_checkout_session_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "stripe_invoice_id": { + "name": "stripe_invoice_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "stripe_payment_intent_id": { + "name": "stripe_payment_intent_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "stripe_charge_id": { + "name": "stripe_charge_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "stripe_fee_price_id": { + "name": "stripe_fee_price_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "stripe_checkout_fee_line_item_id": { + "name": "stripe_checkout_fee_line_item_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "stripe_invoice_fee_item_id": { + "name": "stripe_invoice_fee_item_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "stripe_invoice_fee_line_item_id": { + "name": "stripe_invoice_fee_line_item_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "eligibility_created_at": { + "name": "eligibility_created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "eligible_subtotal_minor": { + "name": "eligible_subtotal_minor", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "expected_fee_minor": { + "name": "expected_fee_minor", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "charged_fee_minor": { + "name": "charged_fee_minor", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "gross_paid_minor": { + "name": "gross_paid_minor", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "settled_product_minor": { + "name": "settled_product_minor", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "refunded_product_minor": { + "name": "refunded_product_minor", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "refunded_fee_minor": { + "name": "refunded_fee_minor", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "refunded_gross_minor": { + "name": "refunded_gross_minor", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "disputed_fee_minor": { + "name": "disputed_fee_minor", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "settled_at": { + "name": "settled_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "exemption_id": { + "name": "exemption_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "failure_code": { + "name": "failure_code", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "metadata": { + "name": "metadata", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "IDX_stripe_service_fee_assessments_stripe_customer_id": { + "name": "IDX_stripe_service_fee_assessments_stripe_customer_id", + "columns": [ + { + "expression": "stripe_customer_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "UQ_stripe_service_fee_assessments_checkout_session_id": { + "name": "UQ_stripe_service_fee_assessments_checkout_session_id", + "columns": [ + { + "expression": "stripe_checkout_session_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"stripe_service_fee_assessments\".\"stripe_checkout_session_id\" is not null", + "concurrently": false, + "method": "btree", + "with": {} + }, + "UQ_stripe_service_fee_assessments_stripe_invoice_id": { + "name": "UQ_stripe_service_fee_assessments_stripe_invoice_id", + "columns": [ + { + "expression": "stripe_invoice_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"stripe_service_fee_assessments\".\"stripe_invoice_id\" is not null", + "concurrently": false, + "method": "btree", + "with": {} + }, + "UQ_stripe_service_fee_assessments_stripe_payment_intent_id": { + "name": "UQ_stripe_service_fee_assessments_stripe_payment_intent_id", + "columns": [ + { + "expression": "stripe_payment_intent_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"stripe_service_fee_assessments\".\"stripe_payment_intent_id\" is not null", + "concurrently": false, + "method": "btree", + "with": {} + }, + "UQ_stripe_service_fee_assessments_stripe_charge_id": { + "name": "UQ_stripe_service_fee_assessments_stripe_charge_id", + "columns": [ + { + "expression": "stripe_charge_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"stripe_service_fee_assessments\".\"stripe_charge_id\" is not null", + "concurrently": false, + "method": "btree", + "with": {} + }, + "UQ_stripe_service_fee_assessments_checkout_fee_line_item_id": { + "name": "UQ_stripe_service_fee_assessments_checkout_fee_line_item_id", + "columns": [ + { + "expression": "stripe_checkout_fee_line_item_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"stripe_service_fee_assessments\".\"stripe_checkout_fee_line_item_id\" is not null", + "concurrently": false, + "method": "btree", + "with": {} + }, + "UQ_stripe_service_fee_assessments_invoice_fee_item_id": { + "name": "UQ_stripe_service_fee_assessments_invoice_fee_item_id", + "columns": [ + { + "expression": "stripe_invoice_fee_item_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"stripe_service_fee_assessments\".\"stripe_invoice_fee_item_id\" is not null", + "concurrently": false, + "method": "btree", + "with": {} + }, + "UQ_stripe_service_fee_assessments_invoice_fee_line_item_id": { + "name": "UQ_stripe_service_fee_assessments_invoice_fee_line_item_id", + "columns": [ + { + "expression": "stripe_invoice_fee_line_item_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"stripe_service_fee_assessments\".\"stripe_invoice_fee_line_item_id\" is not null", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "FK_stripe_svc_fee_assessments_kilo_user": { + "name": "FK_stripe_svc_fee_assessments_kilo_user", + "tableFrom": "stripe_service_fee_assessments", + "tableTo": "kilocode_users", + "columnsFrom": [ + "kilo_user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "cascade" + }, + "FK_stripe_svc_fee_assessments_organization": { + "name": "FK_stripe_svc_fee_assessments_organization", + "tableFrom": "stripe_service_fee_assessments", + "tableTo": "organizations", + "columnsFrom": [ + "organization_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "restrict", + "onUpdate": "cascade" + }, + "FK_stripe_svc_fee_assessments_exemption": { + "name": "FK_stripe_svc_fee_assessments_exemption", + "tableFrom": "stripe_service_fee_assessments", + "tableTo": "organization_service_fee_exemptions", + "columnsFrom": [ + "exemption_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "restrict", + "onUpdate": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "stripe_service_fee_assessments_flow_check": { + "name": "stripe_service_fee_assessments_flow_check", + "value": "\"stripe_service_fee_assessments\".\"flow\" IN ('personal_top_up', 'organization_top_up', 'personal_auto_top_up_setup', 'organization_auto_top_up_setup', 'personal_auto_top_up', 'organization_auto_top_up', 'personal_kilo_pass', 'organization_kilo_pass')" + }, + "stripe_service_fee_assessments_outcome_check": { + "name": "stripe_service_fee_assessments_outcome_check", + "value": "\"stripe_service_fee_assessments\".\"outcome\" IN ('pending', 'charged', 'exempt', 'pre_activation', 'zero_rounded', 'unsupported_currency', 'missed')" + }, + "stripe_service_fee_assessments_currency_check": { + "name": "stripe_service_fee_assessments_currency_check", + "value": "\"stripe_service_fee_assessments\".\"currency\" ~ '^[a-z]{3}$'" + }, + "stripe_service_fee_assessments_owner_check": { + "name": "stripe_service_fee_assessments_owner_check", + "value": "(\n \"stripe_service_fee_assessments\".\"flow\" LIKE 'personal_%'\n AND \"stripe_service_fee_assessments\".\"kilo_user_id\" IS NOT NULL\n AND \"stripe_service_fee_assessments\".\"organization_id\" IS NULL\n ) OR (\n \"stripe_service_fee_assessments\".\"flow\" LIKE 'organization_%'\n AND \"stripe_service_fee_assessments\".\"organization_id\" IS NOT NULL\n )" + }, + "stripe_service_fee_assessments_amounts_nonnegative_check": { + "name": "stripe_service_fee_assessments_amounts_nonnegative_check", + "value": "\"stripe_service_fee_assessments\".\"eligible_subtotal_minor\" >= 0\n AND \"stripe_service_fee_assessments\".\"expected_fee_minor\" >= 0\n AND \"stripe_service_fee_assessments\".\"charged_fee_minor\" >= 0\n AND \"stripe_service_fee_assessments\".\"gross_paid_minor\" >= 0\n AND \"stripe_service_fee_assessments\".\"settled_product_minor\" >= 0\n AND \"stripe_service_fee_assessments\".\"refunded_product_minor\" >= 0\n AND \"stripe_service_fee_assessments\".\"refunded_fee_minor\" >= 0\n AND \"stripe_service_fee_assessments\".\"refunded_gross_minor\" >= 0\n AND \"stripe_service_fee_assessments\".\"disputed_fee_minor\" >= 0" + }, + "stripe_service_fee_assessments_refund_fee_check": { + "name": "stripe_service_fee_assessments_refund_fee_check", + "value": "\"stripe_service_fee_assessments\".\"refunded_fee_minor\" <= \"stripe_service_fee_assessments\".\"charged_fee_minor\"" + }, + "stripe_service_fee_assessments_refund_product_check": { + "name": "stripe_service_fee_assessments_refund_product_check", + "value": "\"stripe_service_fee_assessments\".\"refunded_product_minor\" <= \"stripe_service_fee_assessments\".\"settled_product_minor\"" + }, + "stripe_service_fee_assessments_disputed_fee_check": { + "name": "stripe_service_fee_assessments_disputed_fee_check", + "value": "\"stripe_service_fee_assessments\".\"disputed_fee_minor\" <= \"stripe_service_fee_assessments\".\"charged_fee_minor\"" + }, + "stripe_service_fee_assessments_pending_check": { + "name": "stripe_service_fee_assessments_pending_check", + "value": "\"stripe_service_fee_assessments\".\"outcome\" <> 'pending'\n OR (\"stripe_service_fee_assessments\".\"charged_fee_minor\" = 0 AND \"stripe_service_fee_assessments\".\"settled_at\" IS NULL)" + }, + "stripe_service_fee_assessments_charged_check": { + "name": "stripe_service_fee_assessments_charged_check", + "value": "\"stripe_service_fee_assessments\".\"outcome\" <> 'charged' OR (\n (\n \"stripe_service_fee_assessments\".\"stripe_invoice_fee_item_id\" IS NOT NULL\n OR \"stripe_service_fee_assessments\".\"stripe_invoice_fee_line_item_id\" IS NOT NULL\n OR \"stripe_service_fee_assessments\".\"stripe_checkout_fee_line_item_id\" IS NOT NULL\n OR \"stripe_service_fee_assessments\".\"settled_at\" IS NOT NULL\n )\n AND (\"stripe_service_fee_assessments\".\"charged_fee_minor\" <> 0 OR \"stripe_service_fee_assessments\".\"settled_product_minor\" = 0)\n )" + }, + "stripe_service_fee_assessments_missed_check": { + "name": "stripe_service_fee_assessments_missed_check", + "value": "\"stripe_service_fee_assessments\".\"outcome\" <> 'missed' OR (\n \"stripe_service_fee_assessments\".\"expected_fee_minor\" > 0\n AND \"stripe_service_fee_assessments\".\"charged_fee_minor\" = 0\n AND \"stripe_service_fee_assessments\".\"failure_code\" IS NOT NULL\n AND length(trim(\"stripe_service_fee_assessments\".\"failure_code\")) > 0\n )" + }, + "stripe_service_fee_assessments_zero_rounded_check": { + "name": "stripe_service_fee_assessments_zero_rounded_check", + "value": "\"stripe_service_fee_assessments\".\"outcome\" <> 'zero_rounded' OR \"stripe_service_fee_assessments\".\"expected_fee_minor\" = 0" + }, + "stripe_service_fee_assessments_uncharged_outcome_check": { + "name": "stripe_service_fee_assessments_uncharged_outcome_check", + "value": "\"stripe_service_fee_assessments\".\"outcome\" NOT IN ('exempt', 'pre_activation', 'zero_rounded', 'unsupported_currency')\n OR \"stripe_service_fee_assessments\".\"charged_fee_minor\" = 0" + }, + "stripe_service_fee_assessments_exemption_check": { + "name": "stripe_service_fee_assessments_exemption_check", + "value": "(\"stripe_service_fee_assessments\".\"outcome\" = 'exempt') = (\"stripe_service_fee_assessments\".\"exemption_id\" IS NOT NULL)" + } + }, + "isRLSEnabled": false + }, + "public.stytch_fingerprints": { + "name": "stytch_fingerprints", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "pg_catalog.gen_random_uuid()" + }, + "kilo_user_id": { + "name": "kilo_user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "visitor_fingerprint": { + "name": "visitor_fingerprint", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "browser_fingerprint": { + "name": "browser_fingerprint", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "browser_id": { + "name": "browser_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "hardware_fingerprint": { + "name": "hardware_fingerprint", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "network_fingerprint": { + "name": "network_fingerprint", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "visitor_id": { + "name": "visitor_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "verdict_action": { + "name": "verdict_action", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "detected_device_type": { + "name": "detected_device_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "is_authentic_device": { + "name": "is_authentic_device", + "type": "boolean", + "primaryKey": false, + "notNull": true + }, + "reasons": { + "name": "reasons", + "type": "text[]", + "primaryKey": false, + "notNull": true, + "default": "'{\"\"}'" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "status_code": { + "name": "status_code", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "fingerprint_data": { + "name": "fingerprint_data", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "kilo_free_tier_allowed": { + "name": "kilo_free_tier_allowed", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "http_x_forwarded_for": { + "name": "http_x_forwarded_for", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "http_x_vercel_ip_city": { + "name": "http_x_vercel_ip_city", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "http_x_vercel_ip_country": { + "name": "http_x_vercel_ip_country", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "http_x_vercel_ip_latitude": { + "name": "http_x_vercel_ip_latitude", + "type": "real", + "primaryKey": false, + "notNull": false + }, + "http_x_vercel_ip_longitude": { + "name": "http_x_vercel_ip_longitude", + "type": "real", + "primaryKey": false, + "notNull": false + }, + "http_x_vercel_ja4_digest": { + "name": "http_x_vercel_ja4_digest", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "http_user_agent": { + "name": "http_user_agent", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "idx_hardware_fingerprint": { + "name": "idx_hardware_fingerprint", + "columns": [ + { + "expression": "hardware_fingerprint", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_kilo_user_id": { + "name": "idx_kilo_user_id", + "columns": [ + { + "expression": "kilo_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_stytch_fingerprints_reasons_gin": { + "name": "idx_stytch_fingerprints_reasons_gin", + "columns": [ + { + "expression": "reasons", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "gin", + "with": {} + }, + "idx_verdict_action": { + "name": "idx_verdict_action", + "columns": [ + { + "expression": "verdict_action", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "idx_visitor_fingerprint": { + "name": "idx_visitor_fingerprint", + "columns": [ + { + "expression": "visitor_fingerprint", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.system_prompt_prefix": { + "name": "system_prompt_prefix", + "schema": "", + "columns": { + "system_prompt_prefix_id": { + "name": "system_prompt_prefix_id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "system_prompt_prefix": { + "name": "system_prompt_prefix", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "UQ_system_prompt_prefix": { + "name": "UQ_system_prompt_prefix", + "columns": [ + { + "expression": "system_prompt_prefix", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.transactional_email_log": { + "name": "transactional_email_log", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "organization_id": { + "name": "organization_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "email_type": { + "name": "email_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "idempotency_key": { + "name": "idempotency_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "sent_at": { + "name": "sent_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "UQ_transactional_email_log_type_idempotency_key": { + "name": "UQ_transactional_email_log_type_idempotency_key", + "columns": [ + { + "expression": "email_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "idempotency_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_transactional_email_log_user_id": { + "name": "IDX_transactional_email_log_user_id", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_transactional_email_log_organization_id": { + "name": "IDX_transactional_email_log_organization_id", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "transactional_email_log_user_id_kilocode_users_id_fk": { + "name": "transactional_email_log_user_id_kilocode_users_id_fk", + "tableFrom": "transactional_email_log", + "tableTo": "kilocode_users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "transactional_email_log_organization_id_organizations_id_fk": { + "name": "transactional_email_log_organization_id_organizations_id_fk", + "tableFrom": "transactional_email_log", + "tableTo": "organizations", + "columnsFrom": [ + "organization_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "CHK_transactional_email_log_owner": { + "name": "CHK_transactional_email_log_owner", + "value": "\"transactional_email_log\".\"user_id\" IS NOT NULL OR \"transactional_email_log\".\"organization_id\" IS NOT NULL" + } + }, + "isRLSEnabled": false + }, + "public.user_activity_tokens": { + "name": "user_activity_tokens", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "token": { + "name": "token", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "kind": { + "name": "kind", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "platform": { + "name": "platform", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "organization_id": { + "name": "organization_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "superseded_at": { + "name": "superseded_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "UQ_user_activity_tokens_token": { + "name": "UQ_user_activity_tokens_token", + "columns": [ + { + "expression": "token", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "UQ_user_activity_tokens_live_ios_activity": { + "name": "UQ_user_activity_tokens_live_ios_activity", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "coalesce(\"organization_id\", '')", + "asc": true, + "isExpression": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"user_activity_tokens\".\"kind\" = 'ios_activity' AND \"user_activity_tokens\".\"superseded_at\" IS NULL", + "concurrently": true, + "method": "btree", + "with": {} + }, + "IDX_user_activity_tokens_user_org": { + "name": "IDX_user_activity_tokens_user_org", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "user_activity_tokens_user_id_kilocode_users_id_fk": { + "name": "user_activity_tokens_user_id_kilocode_users_id_fk", + "tableFrom": "user_activity_tokens", + "tableTo": "kilocode_users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.user_admin_notes": { + "name": "user_admin_notes", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "pg_catalog.gen_random_uuid()" + }, + "kilo_user_id": { + "name": "kilo_user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "note_content": { + "name": "note_content", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "admin_kilo_user_id": { + "name": "admin_kilo_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "IDX_34517df0b385234babc38fe81b": { + "name": "IDX_34517df0b385234babc38fe81b", + "columns": [ + { + "expression": "admin_kilo_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_ccbde98c4c14046daa5682ec4f": { + "name": "IDX_ccbde98c4c14046daa5682ec4f", + "columns": [ + { + "expression": "kilo_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_d0270eb24ef6442d65a0b7853c": { + "name": "IDX_d0270eb24ef6442d65a0b7853c", + "columns": [ + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.user_affiliate_attributions": { + "name": "user_affiliate_attributions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "pg_catalog.gen_random_uuid()" + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "provider": { + "name": "provider", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "tracking_id": { + "name": "tracking_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "IDX_user_affiliate_attributions_user_id": { + "name": "IDX_user_affiliate_attributions_user_id", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "user_affiliate_attributions_user_id_kilocode_users_id_fk": { + "name": "user_affiliate_attributions_user_id_kilocode_users_id_fk", + "tableFrom": "user_affiliate_attributions", + "tableTo": "kilocode_users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "UQ_user_affiliate_attributions_user_provider": { + "name": "UQ_user_affiliate_attributions_user_provider", + "nullsNotDistinct": false, + "columns": [ + "user_id", + "provider" + ] + } + }, + "policies": {}, + "checkConstraints": { + "user_affiliate_attributions_provider_check": { + "name": "user_affiliate_attributions_provider_check", + "value": "\"user_affiliate_attributions\".\"provider\" IN ('impact')" + } + }, + "isRLSEnabled": false + }, + "public.user_affiliate_events": { + "name": "user_affiliate_events", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "pg_catalog.gen_random_uuid()" + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "provider": { + "name": "provider", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "event_type": { + "name": "event_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "dedupe_key": { + "name": "dedupe_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "parent_event_id": { + "name": "parent_event_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "delivery_state": { + "name": "delivery_state", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'queued'" + }, + "payload_json": { + "name": "payload_json", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "stripe_charge_id": { + "name": "stripe_charge_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "impact_action_id": { + "name": "impact_action_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "impact_submission_uri": { + "name": "impact_submission_uri", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "attempt_count": { + "name": "attempt_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "next_retry_at": { + "name": "next_retry_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "claimed_at": { + "name": "claimed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "IDX_user_affiliate_events_claim_path": { + "name": "IDX_user_affiliate_events_claim_path", + "columns": [ + { + "expression": "delivery_state", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "coalesce(\"next_retry_at\", '-infinity'::timestamptz)", + "asc": true, + "isExpression": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_user_affiliate_events_parent_event_id": { + "name": "IDX_user_affiliate_events_parent_event_id", + "columns": [ + { + "expression": "parent_event_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_user_affiliate_events_provider_event_type_charge": { + "name": "IDX_user_affiliate_events_provider_event_type_charge", + "columns": [ + { + "expression": "provider", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "event_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "stripe_charge_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "user_affiliate_events_user_id_kilocode_users_id_fk": { + "name": "user_affiliate_events_user_id_kilocode_users_id_fk", + "tableFrom": "user_affiliate_events", + "tableTo": "kilocode_users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "cascade" + }, + "user_affiliate_events_parent_event_id_fk": { + "name": "user_affiliate_events_parent_event_id_fk", + "tableFrom": "user_affiliate_events", + "tableTo": "user_affiliate_events", + "columnsFrom": [ + "parent_event_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "UQ_user_affiliate_events_dedupe_key": { + "name": "UQ_user_affiliate_events_dedupe_key", + "nullsNotDistinct": false, + "columns": [ + "dedupe_key" + ] + } + }, + "policies": {}, + "checkConstraints": { + "user_affiliate_events_provider_check": { + "name": "user_affiliate_events_provider_check", + "value": "\"user_affiliate_events\".\"provider\" IN ('impact')" + }, + "user_affiliate_events_event_type_check": { + "name": "user_affiliate_events_event_type_check", + "value": "\"user_affiliate_events\".\"event_type\" IN ('signup', 'trial_start', 'trial_end', 'sale', 'sale_reversal')" + }, + "user_affiliate_events_delivery_state_check": { + "name": "user_affiliate_events_delivery_state_check", + "value": "\"user_affiliate_events\".\"delivery_state\" IN ('queued', 'blocked', 'sending', 'delivered', 'failed')" + }, + "user_affiliate_events_attempt_count_non_negative_check": { + "name": "user_affiliate_events_attempt_count_non_negative_check", + "value": "\"user_affiliate_events\".\"attempt_count\" >= 0" + } + }, + "isRLSEnabled": false + }, + "public.user_auth_provider": { + "name": "user_auth_provider", + "schema": "", + "columns": { + "kilo_user_id": { + "name": "kilo_user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "provider": { + "name": "provider", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "provider_account_id": { + "name": "provider_account_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "avatar_url": { + "name": "avatar_url", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "display_name": { + "name": "display_name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "hosted_domain": { + "name": "hosted_domain", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "IDX_user_auth_provider_kilo_user_id": { + "name": "IDX_user_auth_provider_kilo_user_id", + "columns": [ + { + "expression": "kilo_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_user_auth_provider_hosted_domain": { + "name": "IDX_user_auth_provider_hosted_domain", + "columns": [ + { + "expression": "hosted_domain", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_user_auth_provider_lower_email": { + "name": "IDX_user_auth_provider_lower_email", + "columns": [ + { + "expression": "lower(\"email\")", + "asc": true, + "isExpression": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": true, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": { + "user_auth_provider_provider_provider_account_id_pk": { + "name": "user_auth_provider_provider_provider_account_id_pk", + "columns": [ + "provider", + "provider_account_id" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.user_data_export_object_deletions": { + "name": "user_data_export_object_deletions", + "schema": "", + "columns": { + "object_key": { + "name": "object_key", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "multipart_upload_id": { + "name": "multipart_upload_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "reason": { + "name": "reason", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'account_deletion'" + }, + "attempt_count": { + "name": "attempt_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "available_at": { + "name": "available_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "IDX_user_data_export_object_deletions_ready": { + "name": "IDX_user_data_export_object_deletions_ready", + "columns": [ + { + "expression": "available_at", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "object_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "user_data_export_object_deletions_reason_check": { + "name": "user_data_export_object_deletions_reason_check", + "value": "\"user_data_export_object_deletions\".\"reason\" IN ('account_deletion', 'admin_cancel', 'admin_replace')" + }, + "user_data_export_object_deletions_attempt_count_nonnegative": { + "name": "user_data_export_object_deletions_attempt_count_nonnegative", + "value": "\"user_data_export_object_deletions\".\"attempt_count\" >= 0" + } + }, + "isRLSEnabled": false + }, + "public.user_data_export_outbox": { + "name": "user_data_export_outbox", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "pg_catalog.gen_random_uuid()" + }, + "export_id": { + "name": "export_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "generation": { + "name": "generation", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "operation": { + "name": "operation", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'generate'" + }, + "available_at": { + "name": "available_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "attempt_count": { + "name": "attempt_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "sent_at": { + "name": "sent_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "IDX_user_data_export_outbox_pending": { + "name": "IDX_user_data_export_outbox_pending", + "columns": [ + { + "expression": "available_at", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"user_data_export_outbox\".\"sent_at\" IS NULL", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "user_data_export_outbox_export_id_user_data_exports_id_fk": { + "name": "user_data_export_outbox_export_id_user_data_exports_id_fk", + "tableFrom": "user_data_export_outbox", + "tableTo": "user_data_exports", + "columnsFrom": [ + "export_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "UQ_user_data_export_outbox_generation_operation": { + "name": "UQ_user_data_export_outbox_generation_operation", + "nullsNotDistinct": false, + "columns": [ + "export_id", + "generation", + "operation" + ] + } + }, + "policies": {}, + "checkConstraints": { + "user_data_export_outbox_operation_check": { + "name": "user_data_export_outbox_operation_check", + "value": "\"user_data_export_outbox\".\"operation\" = 'generate'" + }, + "user_data_export_outbox_generation_nonnegative": { + "name": "user_data_export_outbox_generation_nonnegative", + "value": "\"user_data_export_outbox\".\"generation\" >= 0" + }, + "user_data_export_outbox_attempt_count_nonnegative": { + "name": "user_data_export_outbox_attempt_count_nonnegative", + "value": "\"user_data_export_outbox\".\"attempt_count\" >= 0" + } + }, + "isRLSEnabled": false + }, + "public.user_data_export_parts": { + "name": "user_data_export_parts", + "schema": "", + "columns": { + "export_id": { + "name": "export_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "part_number": { + "name": "part_number", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "etag": { + "name": "etag", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "size_bytes": { + "name": "size_bytes", + "type": "bigint", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "user_data_export_parts_export_id_user_data_exports_id_fk": { + "name": "user_data_export_parts_export_id_user_data_exports_id_fk", + "tableFrom": "user_data_export_parts", + "tableTo": "user_data_exports", + "columnsFrom": [ + "export_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "user_data_export_parts_export_id_part_number_pk": { + "name": "user_data_export_parts_export_id_part_number_pk", + "columns": [ + "export_id", + "part_number" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "user_data_export_parts_part_number_positive": { + "name": "user_data_export_parts_part_number_positive", + "value": "\"user_data_export_parts\".\"part_number\" > 0" + }, + "user_data_export_parts_size_bytes_nonnegative": { + "name": "user_data_export_parts_size_bytes_nonnegative", + "value": "\"user_data_export_parts\".\"size_bytes\" >= 0" + } + }, + "isRLSEnabled": false + }, + "public.user_data_exports": { + "name": "user_data_exports", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "pg_catalog.gen_random_uuid()" + }, + "kilo_user_id": { + "name": "kilo_user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "subject_type": { + "name": "subject_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'user'" + }, + "organization_id": { + "name": "organization_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'queued'" + }, + "schema_version": { + "name": "schema_version", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 1 + }, + "snapshot_at": { + "name": "snapshot_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "current_source": { + "name": "current_source", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "source_cursor": { + "name": "source_cursor", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "multipart_upload_id": { + "name": "multipart_upload_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "next_part_number": { + "name": "next_part_number", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 1 + }, + "dispatch_generation": { + "name": "dispatch_generation", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "lease_token": { + "name": "lease_token", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "lease_expires_at": { + "name": "lease_expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "attempt_count": { + "name": "attempt_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "row_count": { + "name": "row_count", + "type": "bigint", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "size_bytes": { + "name": "size_bytes", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "r2_object_key": { + "name": "r2_object_key", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "r2_etag": { + "name": "r2_etag", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "failure_code": { + "name": "failure_code", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "last_error_redacted": { + "name": "last_error_redacted", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "requested_at": { + "name": "requested_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "started_at": { + "name": "started_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "completed_at": { + "name": "completed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "email_status": { + "name": "email_status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "email_attempt_count": { + "name": "email_attempt_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "email_lease_token": { + "name": "email_lease_token", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "email_lease_expires_at": { + "name": "email_lease_expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "email_sent_at": { + "name": "email_sent_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "UQ_user_data_exports_single_active": { + "name": "UQ_user_data_exports_single_active", + "columns": [ + { + "expression": "kilo_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"user_data_exports\".\"status\" IN ('queued', 'processing', 'finalizing') AND \"user_data_exports\".\"subject_type\" = 'user'", + "concurrently": false, + "method": "btree", + "with": {} + }, + "UQ_user_data_exports_single_active_org": { + "name": "UQ_user_data_exports_single_active_org", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"user_data_exports\".\"status\" IN ('queued', 'processing', 'finalizing') AND \"user_data_exports\".\"subject_type\" = 'organization'", + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_user_data_exports_user_created": { + "name": "IDX_user_data_exports_user_created", + "columns": [ + { + "expression": "kilo_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_user_data_exports_org_created": { + "name": "IDX_user_data_exports_org_created", + "columns": [ + { + "expression": "organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"user_data_exports\".\"organization_id\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_user_data_exports_lease_expiry": { + "name": "IDX_user_data_exports_lease_expiry", + "columns": [ + { + "expression": "lease_expires_at", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"user_data_exports\".\"status\" IN ('processing', 'finalizing')", + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_user_data_exports_ready_expiry": { + "name": "IDX_user_data_exports_ready_expiry", + "columns": [ + { + "expression": "expires_at", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"user_data_exports\".\"status\" = 'ready'", + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_user_data_exports_failed_multipart": { + "name": "IDX_user_data_exports_failed_multipart", + "columns": [ + { + "expression": "updated_at", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"user_data_exports\".\"status\" = 'failed' AND \"user_data_exports\".\"multipart_upload_id\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_user_data_exports_email_lease_expiry": { + "name": "IDX_user_data_exports_email_lease_expiry", + "columns": [ + { + "expression": "email_lease_expires_at", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"user_data_exports\".\"email_status\" = 'sending'", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "user_data_exports_kilo_user_id_kilocode_users_id_fk": { + "name": "user_data_exports_kilo_user_id_kilocode_users_id_fk", + "tableFrom": "user_data_exports", + "tableTo": "kilocode_users", + "columnsFrom": [ + "kilo_user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "restrict", + "onUpdate": "no action" + }, + "user_data_exports_organization_id_organizations_id_fk": { + "name": "user_data_exports_organization_id_organizations_id_fk", + "tableFrom": "user_data_exports", + "tableTo": "organizations", + "columnsFrom": [ + "organization_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "restrict", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "user_data_exports_status_check": { + "name": "user_data_exports_status_check", + "value": "\"user_data_exports\".\"status\" IN ('queued', 'processing', 'finalizing', 'ready', 'failed', 'expired')" + }, + "user_data_exports_subject_type_check": { + "name": "user_data_exports_subject_type_check", + "value": "\"user_data_exports\".\"subject_type\" IN ('user', 'organization')" + }, + "user_data_exports_subject_shape": { + "name": "user_data_exports_subject_shape", + "value": "(\"user_data_exports\".\"subject_type\" = 'user' AND \"user_data_exports\".\"organization_id\" IS NULL)\n OR (\"user_data_exports\".\"subject_type\" = 'organization' AND \"user_data_exports\".\"organization_id\" IS NOT NULL)" + }, + "user_data_exports_schema_version_positive": { + "name": "user_data_exports_schema_version_positive", + "value": "\"user_data_exports\".\"schema_version\" > 0" + }, + "user_data_exports_next_part_number_positive": { + "name": "user_data_exports_next_part_number_positive", + "value": "\"user_data_exports\".\"next_part_number\" > 0" + }, + "user_data_exports_dispatch_generation_nonnegative": { + "name": "user_data_exports_dispatch_generation_nonnegative", + "value": "\"user_data_exports\".\"dispatch_generation\" >= 0" + }, + "user_data_exports_attempt_count_nonnegative": { + "name": "user_data_exports_attempt_count_nonnegative", + "value": "\"user_data_exports\".\"attempt_count\" >= 0" + }, + "user_data_exports_row_count_nonnegative": { + "name": "user_data_exports_row_count_nonnegative", + "value": "\"user_data_exports\".\"row_count\" >= 0" + }, + "user_data_exports_size_bytes_nonnegative": { + "name": "user_data_exports_size_bytes_nonnegative", + "value": "\"user_data_exports\".\"size_bytes\" IS NULL OR \"user_data_exports\".\"size_bytes\" >= 0" + }, + "user_data_exports_lease_shape": { + "name": "user_data_exports_lease_shape", + "value": "(\"user_data_exports\".\"lease_token\" IS NULL) = (\"user_data_exports\".\"lease_expires_at\" IS NULL)" + }, + "user_data_exports_ready_shape": { + "name": "user_data_exports_ready_shape", + "value": "\"user_data_exports\".\"status\" <> 'ready' OR (\"user_data_exports\".\"r2_object_key\" IS NOT NULL AND \"user_data_exports\".\"size_bytes\" IS NOT NULL AND \"user_data_exports\".\"completed_at\" IS NOT NULL AND \"user_data_exports\".\"expires_at\" IS NOT NULL)" + }, + "user_data_exports_last_error_redacted_length": { + "name": "user_data_exports_last_error_redacted_length", + "value": "\"user_data_exports\".\"last_error_redacted\" IS NULL OR length(\"user_data_exports\".\"last_error_redacted\") <= 500" + }, + "user_data_exports_email_attempt_count_nonnegative": { + "name": "user_data_exports_email_attempt_count_nonnegative", + "value": "\"user_data_exports\".\"email_attempt_count\" >= 0" + }, + "user_data_exports_email_status_check": { + "name": "user_data_exports_email_status_check", + "value": "\"user_data_exports\".\"email_status\" IN ('pending', 'sending', 'sent', 'failed')" + }, + "user_data_exports_email_lease_shape": { + "name": "user_data_exports_email_lease_shape", + "value": "(\"user_data_exports\".\"email_status\" = 'sending') = (\"user_data_exports\".\"email_lease_token\" IS NOT NULL AND \"user_data_exports\".\"email_lease_expires_at\" IS NOT NULL)" + }, + "user_data_exports_email_sent_shape": { + "name": "user_data_exports_email_sent_shape", + "value": "(\"user_data_exports\".\"email_status\" = 'sent') = (\"user_data_exports\".\"email_sent_at\" IS NOT NULL)" + } + }, + "isRLSEnabled": false + }, + "public.user_deletion_activity": { + "name": "user_deletion_activity", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "pg_catalog.gen_random_uuid()" + }, + "request_id": { + "name": "request_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "step_key": { + "name": "step_key", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "event_type": { + "name": "event_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "details_json": { + "name": "details_json", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "IDX_user_deletion_activity_request_created": { + "name": "IDX_user_deletion_activity_request_created", + "columns": [ + { + "expression": "request_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "user_deletion_activity_request_id_user_deletion_requests_id_fk": { + "name": "user_deletion_activity_request_id_user_deletion_requests_id_fk", + "tableFrom": "user_deletion_activity", + "tableTo": "user_deletion_requests", + "columnsFrom": [ + "request_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.user_deletion_audit_events": { + "name": "user_deletion_audit_events", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "pg_catalog.gen_random_uuid()" + }, + "request_id": { + "name": "request_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "event_type": { + "name": "event_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "actor_kilo_user_id": { + "name": "actor_kilo_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "target_email_hmac": { + "name": "target_email_hmac", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "subject_key": { + "name": "subject_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "details_json": { + "name": "details_json", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "UQ_user_deletion_audit_events_idempotent": { + "name": "UQ_user_deletion_audit_events_idempotent", + "columns": [ + { + "expression": "request_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "event_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "subject_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"user_deletion_audit_events\".\"request_id\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_user_deletion_audit_events_request_id": { + "name": "IDX_user_deletion_audit_events_request_id", + "columns": [ + { + "expression": "request_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_user_deletion_audit_events_hmac": { + "name": "IDX_user_deletion_audit_events_hmac", + "columns": [ + { + "expression": "target_email_hmac", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "user_deletion_audit_events_request_id_user_deletion_requests_id_fk": { + "name": "user_deletion_audit_events_request_id_user_deletion_requests_id_fk", + "tableFrom": "user_deletion_audit_events", + "tableTo": "user_deletion_requests", + "columnsFrom": [ + "request_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "user_deletion_audit_events_event_type_check": { + "name": "user_deletion_audit_events_event_type_check", + "value": "\"user_deletion_audit_events\".\"event_type\" IN ('request_created', 'intake_refused', 'access_disabled', 'access_absent', 'preflight_disposition', 'task_disposition', 'manual_retry', 'manual_action', 'anonymized', 'deletion_ready_for_customer_reply', 'cancelled', 'completed')" + } + }, + "isRLSEnabled": false + }, + "public.user_deletion_provider_credentials": { + "name": "user_deletion_provider_credentials", + "schema": "", + "columns": { + "provider_scope": { + "name": "provider_scope", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "encrypted_material": { + "name": "encrypted_material", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_by_kilo_user_id": { + "name": "updated_by_kilo_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "user_deletion_provider_credentials_updated_by_kilo_user_id_kilocode_users_id_fk": { + "name": "user_deletion_provider_credentials_updated_by_kilo_user_id_kilocode_users_id_fk", + "tableFrom": "user_deletion_provider_credentials", + "tableTo": "kilocode_users", + "columnsFrom": [ + "updated_by_kilo_user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "user_deletion_provider_credentials_scope_check": { + "name": "user_deletion_provider_credentials_scope_check", + "value": "\"user_deletion_provider_credentials\".\"provider_scope\" IN ('kiloclaw', 'customerio', 'cloud_storage', 'session_ingest', 'posthog', 'substack', 'pylon', 'csa')" + } + }, + "isRLSEnabled": false + }, + "public.user_deletion_requests": { + "name": "user_deletion_requests", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "pg_catalog.gen_random_uuid()" + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "catalog_version": { + "name": "catalog_version", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 1 + }, + "requested_by_kilo_user_id": { + "name": "requested_by_kilo_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "requested_by_email": { + "name": "requested_by_email", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "target_email": { + "name": "target_email", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "target_email_hmac": { + "name": "target_email_hmac", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "pylon_ticket_ref": { + "name": "pylon_ticket_ref", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "cloud_subject_resolution": { + "name": "cloud_subject_resolution", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "cloud_subject_proof_ref": { + "name": "cloud_subject_proof_ref", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "preflight_attention_code": { + "name": "preflight_attention_code", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "last_progress_at": { + "name": "last_progress_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "anonymized_at": { + "name": "anonymized_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "completed_at": { + "name": "completed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "cancelled_at": { + "name": "cancelled_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "UQ_user_deletion_requests_active_email_hmac": { + "name": "UQ_user_deletion_requests_active_email_hmac", + "columns": [ + { + "expression": "target_email_hmac", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"user_deletion_requests\".\"target_email_hmac\" IS NOT NULL AND \"user_deletion_requests\".\"status\" IN ('pending', 'in_progress', 'finalizing')", + "concurrently": false, + "method": "btree", + "with": {} + }, + "UQ_user_deletion_requests_active_user_id": { + "name": "UQ_user_deletion_requests_active_user_id", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"user_deletion_requests\".\"user_id\" IS NOT NULL AND \"user_deletion_requests\".\"status\" IN ('pending', 'in_progress', 'finalizing')", + "concurrently": false, + "method": "btree", + "with": {} + }, + "UQ_user_deletion_requests_active_pylon_ticket": { + "name": "UQ_user_deletion_requests_active_pylon_ticket", + "columns": [ + { + "expression": "regexp_replace(\"pylon_ticket_ref\", '^#', '')", + "asc": true, + "isExpression": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"user_deletion_requests\".\"pylon_ticket_ref\" IS NOT NULL AND \"user_deletion_requests\".\"status\" IN ('pending', 'in_progress', 'finalizing')", + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_user_deletion_requests_fairness": { + "name": "IDX_user_deletion_requests_fairness", + "columns": [ + { + "expression": "last_progress_at", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"user_deletion_requests\".\"status\" IN ('pending', 'in_progress', 'finalizing')", + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_user_deletion_requests_email_hmac": { + "name": "IDX_user_deletion_requests_email_hmac", + "columns": [ + { + "expression": "target_email_hmac", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_user_deletion_requests_user_id": { + "name": "IDX_user_deletion_requests_user_id", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "user_deletion_requests_user_id_kilocode_users_id_fk": { + "name": "user_deletion_requests_user_id_kilocode_users_id_fk", + "tableFrom": "user_deletion_requests", + "tableTo": "kilocode_users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "user_deletion_requests_requested_by_kilo_user_id_kilocode_users_id_fk": { + "name": "user_deletion_requests_requested_by_kilo_user_id_kilocode_users_id_fk", + "tableFrom": "user_deletion_requests", + "tableTo": "kilocode_users", + "columnsFrom": [ + "requested_by_kilo_user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "user_deletion_requests_status_check": { + "name": "user_deletion_requests_status_check", + "value": "\"user_deletion_requests\".\"status\" IN ('pending', 'in_progress', 'finalizing', 'completed', 'cancelled')" + }, + "user_deletion_requests_cloud_subject_resolution_check": { + "name": "user_deletion_requests_cloud_subject_resolution_check", + "value": "\"user_deletion_requests\".\"cloud_subject_resolution\" IN ('current_user', 'authoritative_absence', 'prior_queue_cleanup', 'legacy_identity_unresolved', 'unresolved')" + }, + "user_deletion_requests_catalog_version_positive": { + "name": "user_deletion_requests_catalog_version_positive", + "value": "\"user_deletion_requests\".\"catalog_version\" >= 1" + }, + "user_deletion_requests_completed_at_check": { + "name": "user_deletion_requests_completed_at_check", + "value": "(\"user_deletion_requests\".\"status\" = 'completed') = (\"user_deletion_requests\".\"completed_at\" IS NOT NULL)" + }, + "user_deletion_requests_cancelled_at_check": { + "name": "user_deletion_requests_cancelled_at_check", + "value": "(\"user_deletion_requests\".\"status\" = 'cancelled') = (\"user_deletion_requests\".\"cancelled_at\" IS NOT NULL)" + }, + "user_deletion_requests_active_email_check": { + "name": "user_deletion_requests_active_email_check", + "value": "(\"user_deletion_requests\".\"status\" NOT IN ('in_progress', 'finalizing') OR \"user_deletion_requests\".\"target_email\" IS NOT NULL) AND (\"user_deletion_requests\".\"status\" NOT IN ('completed', 'cancelled') OR \"user_deletion_requests\".\"target_email\" IS NULL)" + } + }, + "isRLSEnabled": false + }, + "public.user_deletion_steps": { + "name": "user_deletion_steps", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "pg_catalog.gen_random_uuid()" + }, + "request_id": { + "name": "request_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "step_key": { + "name": "step_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "available_at": { + "name": "available_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "claim_token": { + "name": "claim_token", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "claimed_until": { + "name": "claimed_until", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "window_attempt_count": { + "name": "window_attempt_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "lifetime_attempt_count": { + "name": "lifetime_attempt_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "progress_json": { + "name": "progress_json", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "last_error_code": { + "name": "last_error_code", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "rate_limited_since": { + "name": "rate_limited_since", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "manual_evidence_json": { + "name": "manual_evidence_json", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "IDX_user_deletion_steps_due": { + "name": "IDX_user_deletion_steps_due", + "columns": [ + { + "expression": "status", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "available_at", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "where": "\"user_deletion_steps\".\"status\" IN ('pending', 'retry_wait', 'running')", + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_user_deletion_steps_request_id": { + "name": "IDX_user_deletion_steps_request_id", + "columns": [ + { + "expression": "request_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "user_deletion_steps_request_id_user_deletion_requests_id_fk": { + "name": "user_deletion_steps_request_id_user_deletion_requests_id_fk", + "tableFrom": "user_deletion_steps", + "tableTo": "user_deletion_requests", + "columnsFrom": [ + "request_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "UQ_user_deletion_steps_request_step": { + "name": "UQ_user_deletion_steps_request_step", + "nullsNotDistinct": false, + "columns": [ + "request_id", + "step_key" + ] + } + }, + "policies": {}, + "checkConstraints": { + "user_deletion_steps_step_key_check": { + "name": "user_deletion_steps_step_key_check", + "value": "\"user_deletion_steps\".\"step_key\" IN ('kiloclaw_destroy', 'customerio', 'cli_v1_blobs', 'cli_v2_sessions', 'usage_prompt_prefixes', 'posthog', 'substack', 'anonymize', 'pylon_reply', 'pylon_finalize', 'completion_email', 'pylon_contact', 'csa_support_db')" + }, + "user_deletion_steps_status_check": { + "name": "user_deletion_steps_status_check", + "value": "\"user_deletion_steps\".\"status\" IN ('pending', 'running', 'retry_wait', 'needs_attention', 'manual_action_required', 'succeeded', 'not_applicable', 'manually_verified')" + }, + "user_deletion_steps_window_attempt_count_nonnegative": { + "name": "user_deletion_steps_window_attempt_count_nonnegative", + "value": "\"user_deletion_steps\".\"window_attempt_count\" >= 0" + }, + "user_deletion_steps_lifetime_attempt_count_nonnegative": { + "name": "user_deletion_steps_lifetime_attempt_count_nonnegative", + "value": "\"user_deletion_steps\".\"lifetime_attempt_count\" >= 0" + }, + "user_deletion_steps_claim_fields_check": { + "name": "user_deletion_steps_claim_fields_check", + "value": "(\"user_deletion_steps\".\"claim_token\" IS NULL) = (\"user_deletion_steps\".\"claimed_until\" IS NULL)" + }, + "user_deletion_steps_manual_evidence_check": { + "name": "user_deletion_steps_manual_evidence_check", + "value": "(\"user_deletion_steps\".\"status\" = 'manually_verified') = (\"user_deletion_steps\".\"manual_evidence_json\" IS NOT NULL)" + } + }, + "isRLSEnabled": false + }, + "public.user_feedback": { + "name": "user_feedback", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "pg_catalog.gen_random_uuid()" + }, + "kilo_user_id": { + "name": "kilo_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "feedback_text": { + "name": "feedback_text", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "feedback_for": { + "name": "feedback_for", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'unknown'" + }, + "feedback_batch": { + "name": "feedback_batch", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "source": { + "name": "source", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'unknown'" + }, + "context_json": { + "name": "context_json", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'{}'::jsonb" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "IDX_user_feedback_created_at": { + "name": "IDX_user_feedback_created_at", + "columns": [ + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_user_feedback_kilo_user_id": { + "name": "IDX_user_feedback_kilo_user_id", + "columns": [ + { + "expression": "kilo_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_user_feedback_feedback_for": { + "name": "IDX_user_feedback_feedback_for", + "columns": [ + { + "expression": "feedback_for", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_user_feedback_feedback_batch": { + "name": "IDX_user_feedback_feedback_batch", + "columns": [ + { + "expression": "feedback_batch", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_user_feedback_source": { + "name": "IDX_user_feedback_source", + "columns": [ + { + "expression": "source", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "user_feedback_kilo_user_id_kilocode_users_id_fk": { + "name": "user_feedback_kilo_user_id_kilocode_users_id_fk", + "tableFrom": "user_feedback", + "tableTo": "kilocode_users", + "columnsFrom": [ + "kilo_user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.user_github_app_tokens": { + "name": "user_github_app_tokens", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "pg_catalog.gen_random_uuid()" + }, + "kilo_user_id": { + "name": "kilo_user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "github_app_type": { + "name": "github_app_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'standard'" + }, + "github_user_id": { + "name": "github_user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "github_login": { + "name": "github_login", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "access_token_encrypted": { + "name": "access_token_encrypted", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "access_token_expires_at": { + "name": "access_token_expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "refresh_token_encrypted": { + "name": "refresh_token_encrypted", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "refresh_token_expires_at": { + "name": "refresh_token_expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "credential_version": { + "name": "credential_version", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 1 + }, + "revoked_at": { + "name": "revoked_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "revocation_reason": { + "name": "revocation_reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "last_used_at": { + "name": "last_used_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "UQ_user_github_app_tokens_user_app": { + "name": "UQ_user_github_app_tokens_user_app", + "columns": [ + { + "expression": "kilo_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "github_app_type", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "UQ_user_github_app_tokens_github_user_app": { + "name": "UQ_user_github_app_tokens_github_user_app", + "columns": [ + { + "expression": "github_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "github_app_type", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "user_github_app_tokens_kilo_user_id_kilocode_users_id_fk": { + "name": "user_github_app_tokens_kilo_user_id_kilocode_users_id_fk", + "tableFrom": "user_github_app_tokens", + "tableTo": "kilocode_users", + "columnsFrom": [ + "kilo_user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "user_github_app_tokens_app_type_check": { + "name": "user_github_app_tokens_app_type_check", + "value": "\"user_github_app_tokens\".\"github_app_type\" IN ('standard', 'lite')" + } + }, + "isRLSEnabled": false + }, + "public.user_model_preferences": { + "name": "user_model_preferences", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "pg_catalog.gen_random_uuid()" + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "favorites": { + "name": "favorites", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "last_selected": { + "name": "last_selected", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "UQ_user_model_preferences_user_id": { + "name": "UQ_user_model_preferences_user_id", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "user_model_preferences_user_id_kilocode_users_id_fk": { + "name": "user_model_preferences_user_id_kilocode_users_id_fk", + "tableFrom": "user_model_preferences", + "tableTo": "kilocode_users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "cascade" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.user_moderation_blocks": { + "name": "user_moderation_blocks", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "pg_catalog.gen_random_uuid()" + }, + "blocker_user_id": { + "name": "blocker_user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "blocked_github_login": { + "name": "blocked_github_login", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "UQ_user_moderation_blocks_blocker_login": { + "name": "UQ_user_moderation_blocks_blocker_login", + "columns": [ + { + "expression": "blocker_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "blocked_github_login", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.user_moderation_mutes": { + "name": "user_moderation_mutes", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "pg_catalog.gen_random_uuid()" + }, + "blocker_user_id": { + "name": "blocker_user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "muted_github_login": { + "name": "muted_github_login", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "UQ_user_moderation_mutes_blocker_login": { + "name": "UQ_user_moderation_mutes_blocker_login", + "columns": [ + { + "expression": "blocker_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "muted_github_login", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.user_notification_preferences": { + "name": "user_notification_preferences", + "schema": "", + "columns": { + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "agent_push_enabled": { + "name": "agent_push_enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "chat_messages_enabled": { + "name": "chat_messages_enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "agent_attention_enabled": { + "name": "agent_attention_enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "session_status_enabled": { + "name": "session_status_enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "kiloclaw_activity_enabled": { + "name": "kiloclaw_activity_enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "balance_alerts_enabled": { + "name": "balance_alerts_enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "spend_alerts_enabled": { + "name": "spend_alerts_enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "security_findings_enabled": { + "name": "security_findings_enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "notification_previews": { + "name": "notification_previews", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'generic'" + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "user_notification_preferences_user_id_kilocode_users_id_fk": { + "name": "user_notification_preferences_user_id_kilocode_users_id_fk", + "tableFrom": "user_notification_preferences", + "tableTo": "kilocode_users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.user_period_cache": { + "name": "user_period_cache", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "pg_catalog.gen_random_uuid()" + }, + "kilo_user_id": { + "name": "kilo_user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "cache_type": { + "name": "cache_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "period_type": { + "name": "period_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "period_key": { + "name": "period_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "data": { + "name": "data", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "computed_at": { + "name": "computed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "version": { + "name": "version", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 1 + }, + "shared_url_token": { + "name": "shared_url_token", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "shared_at": { + "name": "shared_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "IDX_user_period_cache_kilo_user_id": { + "name": "IDX_user_period_cache_kilo_user_id", + "columns": [ + { + "expression": "kilo_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "UQ_user_period_cache": { + "name": "UQ_user_period_cache", + "columns": [ + { + "expression": "kilo_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "cache_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "period_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "period_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_user_period_cache_lookup": { + "name": "IDX_user_period_cache_lookup", + "columns": [ + { + "expression": "cache_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "period_type", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "period_key", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "UQ_user_period_cache_share_token": { + "name": "UQ_user_period_cache_share_token", + "columns": [ + { + "expression": "shared_url_token", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "where": "\"user_period_cache\".\"shared_url_token\" IS NOT NULL", + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "user_period_cache_kilo_user_id_kilocode_users_id_fk": { + "name": "user_period_cache_kilo_user_id_kilocode_users_id_fk", + "tableFrom": "user_period_cache", + "tableTo": "kilocode_users", + "columnsFrom": [ + "kilo_user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "user_period_cache_period_type_check": { + "name": "user_period_cache_period_type_check", + "value": "\"user_period_cache\".\"period_type\" IN ('year', 'quarter', 'month', 'week', 'custom')" + } + }, + "isRLSEnabled": false + }, + "public.user_push_tokens": { + "name": "user_push_tokens", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "token": { + "name": "token", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "platform": { + "name": "platform", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "app_version": { + "name": "app_version", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "locale": { + "name": "locale", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "UQ_user_push_tokens_token": { + "name": "UQ_user_push_tokens_token", + "columns": [ + { + "expression": "token", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_user_push_tokens_user_id": { + "name": "IDX_user_push_tokens_user_id", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "user_push_tokens_user_id_kilocode_users_id_fk": { + "name": "user_push_tokens_user_id_kilocode_users_id_fk", + "tableFrom": "user_push_tokens", + "tableTo": "kilocode_users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.user_terms_acceptances": { + "name": "user_terms_acceptances", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "pg_catalog.gen_random_uuid()" + }, + "kilo_user_id": { + "name": "kilo_user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "terms_version": { + "name": "terms_version", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "age_posture": { + "name": "age_posture", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'13_plus'" + }, + "accepted_at": { + "name": "accepted_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "UQ_user_terms_acceptances_user_version": { + "name": "UQ_user_terms_acceptances_user_version", + "columns": [ + { + "expression": "kilo_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "terms_version", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.vercel_ip_city": { + "name": "vercel_ip_city", + "schema": "", + "columns": { + "vercel_ip_city_id": { + "name": "vercel_ip_city_id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "vercel_ip_city": { + "name": "vercel_ip_city", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "UQ_vercel_ip_city": { + "name": "UQ_vercel_ip_city", + "columns": [ + { + "expression": "vercel_ip_city", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.vercel_ip_country": { + "name": "vercel_ip_country", + "schema": "", + "columns": { + "vercel_ip_country_id": { + "name": "vercel_ip_country_id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "vercel_ip_country": { + "name": "vercel_ip_country", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "UQ_vercel_ip_country": { + "name": "UQ_vercel_ip_country", + "columns": [ + { + "expression": "vercel_ip_country", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.webhook_events": { + "name": "webhook_events", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "pg_catalog.gen_random_uuid()" + }, + "owned_by_organization_id": { + "name": "owned_by_organization_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "owned_by_user_id": { + "name": "owned_by_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "platform": { + "name": "platform", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "event_type": { + "name": "event_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "event_action": { + "name": "event_action", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "payload": { + "name": "payload", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "headers": { + "name": "headers", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "processed": { + "name": "processed", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "processed_at": { + "name": "processed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "handlers_triggered": { + "name": "handlers_triggered", + "type": "text[]", + "primaryKey": false, + "notNull": true, + "default": "'{}'" + }, + "errors": { + "name": "errors", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "event_signature": { + "name": "event_signature", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "IDX_webhook_events_owned_by_org_id": { + "name": "IDX_webhook_events_owned_by_org_id", + "columns": [ + { + "expression": "owned_by_organization_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_webhook_events_owned_by_user_id": { + "name": "IDX_webhook_events_owned_by_user_id", + "columns": [ + { + "expression": "owned_by_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_webhook_events_platform": { + "name": "IDX_webhook_events_platform", + "columns": [ + { + "expression": "platform", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_webhook_events_event_type": { + "name": "IDX_webhook_events_event_type", + "columns": [ + { + "expression": "event_type", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "IDX_webhook_events_created_at": { + "name": "IDX_webhook_events_created_at", + "columns": [ + { + "expression": "created_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "webhook_events_owned_by_organization_id_organizations_id_fk": { + "name": "webhook_events_owned_by_organization_id_organizations_id_fk", + "tableFrom": "webhook_events", + "tableTo": "organizations", + "columnsFrom": [ + "owned_by_organization_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "webhook_events_owned_by_user_id_kilocode_users_id_fk": { + "name": "webhook_events_owned_by_user_id_kilocode_users_id_fk", + "tableFrom": "webhook_events", + "tableTo": "kilocode_users", + "columnsFrom": [ + "owned_by_user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "UQ_webhook_events_signature": { + "name": "UQ_webhook_events_signature", + "nullsNotDistinct": false, + "columns": [ + "event_signature" + ] + } + }, + "policies": {}, + "checkConstraints": { + "webhook_events_owner_check": { + "name": "webhook_events_owner_check", + "value": "(\n (\"webhook_events\".\"owned_by_user_id\" IS NOT NULL AND \"webhook_events\".\"owned_by_organization_id\" IS NULL) OR\n (\"webhook_events\".\"owned_by_user_id\" IS NULL AND \"webhook_events\".\"owned_by_organization_id\" IS NOT NULL)\n )" + } + }, + "isRLSEnabled": false + } + }, + "enums": {}, + "schemas": {}, + "sequences": {}, + "roles": {}, + "policies": {}, + "views": { + "public.microdollar_usage_view": { + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "kilo_user_id": { + "name": "kilo_user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "message_id": { + "name": "message_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "cost": { + "name": "cost", + "type": "bigint", + "primaryKey": false, + "notNull": true + }, + "input_tokens": { + "name": "input_tokens", + "type": "bigint", + "primaryKey": false, + "notNull": true + }, + "output_tokens": { + "name": "output_tokens", + "type": "bigint", + "primaryKey": false, + "notNull": true + }, + "cache_write_tokens": { + "name": "cache_write_tokens", + "type": "bigint", + "primaryKey": false, + "notNull": true + }, + "cache_hit_tokens": { + "name": "cache_hit_tokens", + "type": "bigint", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "http_x_forwarded_for": { + "name": "http_x_forwarded_for", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "http_x_vercel_ip_city": { + "name": "http_x_vercel_ip_city", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "http_x_vercel_ip_country": { + "name": "http_x_vercel_ip_country", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "http_x_vercel_ip_latitude": { + "name": "http_x_vercel_ip_latitude", + "type": "real", + "primaryKey": false, + "notNull": false + }, + "http_x_vercel_ip_longitude": { + "name": "http_x_vercel_ip_longitude", + "type": "real", + "primaryKey": false, + "notNull": false + }, + "http_x_vercel_ja4_digest": { + "name": "http_x_vercel_ja4_digest", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "provider": { + "name": "provider", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "model": { + "name": "model", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "requested_model": { + "name": "requested_model", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "user_prompt_prefix": { + "name": "user_prompt_prefix", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "system_prompt_prefix": { + "name": "system_prompt_prefix", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "system_prompt_length": { + "name": "system_prompt_length", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "http_user_agent": { + "name": "http_user_agent", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "cache_discount": { + "name": "cache_discount", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "max_tokens": { + "name": "max_tokens", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "has_middle_out_transform": { + "name": "has_middle_out_transform", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "has_error": { + "name": "has_error", + "type": "boolean", + "primaryKey": false, + "notNull": true + }, + "abuse_classification": { + "name": "abuse_classification", + "type": "smallint", + "primaryKey": false, + "notNull": true + }, + "organization_id": { + "name": "organization_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "inference_provider": { + "name": "inference_provider", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "project_id": { + "name": "project_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status_code": { + "name": "status_code", + "type": "smallint", + "primaryKey": false, + "notNull": false + }, + "upstream_id": { + "name": "upstream_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "finish_reason": { + "name": "finish_reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "latency": { + "name": "latency", + "type": "real", + "primaryKey": false, + "notNull": false + }, + "moderation_latency": { + "name": "moderation_latency", + "type": "real", + "primaryKey": false, + "notNull": false + }, + "generation_time": { + "name": "generation_time", + "type": "real", + "primaryKey": false, + "notNull": false + }, + "is_byok": { + "name": "is_byok", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "is_user_byok": { + "name": "is_user_byok", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "streamed": { + "name": "streamed", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "cancelled": { + "name": "cancelled", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "editor_name": { + "name": "editor_name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "api_kind": { + "name": "api_kind", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "has_tools": { + "name": "has_tools", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "machine_id": { + "name": "machine_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "feature": { + "name": "feature", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "session_id": { + "name": "session_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "mode": { + "name": "mode", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "auto_model": { + "name": "auto_model", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "market_cost": { + "name": "market_cost", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "is_free": { + "name": "is_free", + "type": "boolean", + "primaryKey": false, + "notNull": false + }, + "abuse_delay": { + "name": "abuse_delay", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "abuse_downgraded_from": { + "name": "abuse_downgraded_from", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "definition": "\n SELECT\n mu.id,\n mu.kilo_user_id,\n meta.message_id,\n mu.cost,\n mu.input_tokens,\n mu.output_tokens,\n mu.cache_write_tokens,\n mu.cache_hit_tokens,\n mu.created_at,\n ip.http_ip AS http_x_forwarded_for,\n city.vercel_ip_city AS http_x_vercel_ip_city,\n country.vercel_ip_country AS http_x_vercel_ip_country,\n meta.vercel_ip_latitude AS http_x_vercel_ip_latitude,\n meta.vercel_ip_longitude AS http_x_vercel_ip_longitude,\n ja4.ja4_digest AS http_x_vercel_ja4_digest,\n mu.provider,\n mu.model,\n mu.requested_model,\n meta.user_prompt_prefix,\n spp.system_prompt_prefix,\n meta.system_prompt_length,\n ua.http_user_agent,\n mu.cache_discount,\n meta.max_tokens,\n meta.has_middle_out_transform,\n mu.has_error,\n mu.abuse_classification,\n mu.organization_id,\n mu.inference_provider,\n mu.project_id,\n meta.status_code,\n meta.upstream_id,\n frfr.finish_reason,\n meta.latency,\n meta.moderation_latency,\n meta.generation_time,\n meta.is_byok,\n meta.is_user_byok,\n meta.streamed,\n meta.cancelled,\n edit.editor_name,\n ak.api_kind,\n meta.has_tools,\n meta.machine_id,\n feat.feature,\n meta.session_id,\n md.mode,\n am.auto_model,\n meta.market_cost,\n meta.is_free,\n meta.abuse_delay,\n meta.abuse_downgraded_from\n FROM \"microdollar_usage\" mu\n LEFT JOIN \"microdollar_usage_metadata\" meta ON mu.id = meta.id\n LEFT JOIN \"http_ip\" ip ON meta.http_ip_id = ip.http_ip_id\n LEFT JOIN \"vercel_ip_city\" city ON meta.vercel_ip_city_id = city.vercel_ip_city_id\n LEFT JOIN \"vercel_ip_country\" country ON meta.vercel_ip_country_id = country.vercel_ip_country_id\n LEFT JOIN \"ja4_digest\" ja4 ON meta.ja4_digest_id = ja4.ja4_digest_id\n LEFT JOIN \"system_prompt_prefix\" spp ON meta.system_prompt_prefix_id = spp.system_prompt_prefix_id\n LEFT JOIN \"http_user_agent\" ua ON meta.http_user_agent_id = ua.http_user_agent_id\n LEFT JOIN \"finish_reason\" frfr ON meta.finish_reason_id = frfr.finish_reason_id\n LEFT JOIN \"editor_name\" edit ON meta.editor_name_id = edit.editor_name_id\n LEFT JOIN \"api_kind\" ak ON meta.api_kind_id = ak.api_kind_id\n LEFT JOIN \"feature\" feat ON meta.feature_id = feat.feature_id\n LEFT JOIN \"mode\" md ON meta.mode_id = md.mode_id\n LEFT JOIN \"auto_model\" am ON meta.auto_model_id = am.auto_model_id\n", + "name": "microdollar_usage_view", + "schema": "public", + "isExisting": false, + "materialized": false + } + }, + "_meta": { + "columns": {}, + "schemas": {}, + "tables": {} + } +} \ No newline at end of file diff --git a/packages/db/src/migrations/meta/_journal.json b/packages/db/src/migrations/meta/_journal.json index c41a490fbea..de22856b167 100644 --- a/packages/db/src/migrations/meta/_journal.json +++ b/packages/db/src/migrations/meta/_journal.json @@ -1821,6 +1821,13 @@ "when": 1790187043975, "tag": "0259_github_connection_role_indexes", "breakpoints": true + }, + { + "idx": 260, + "version": "7", + "when": 1790271690967, + "tag": "0260_free_amazoness", + "breakpoints": true } ] } \ No newline at end of file diff --git a/packages/db/src/schema.ts b/packages/db/src/schema.ts index 1d79b3392d1..78419f25186 100644 --- a/packages/db/src/schema.ts +++ b/packages/db/src/schema.ts @@ -6386,6 +6386,14 @@ export const cli_sessions_v2 = pgTable( organization_id: uuid().references(() => organizations.id, { onDelete: 'set null', }), + // The profile the session was prepared with, resolved server-side (an + // explicit pick, the effective default, or a repository binding). Null on + // rows created before this column existed and on sessions whose create + // origin resolved no profile. `set null` so deleting a profile keeps the + // session history readable. + profile_id: uuid().references(() => agent_environment_profiles.id, { + onDelete: 'set null', + }), cloud_agent_session_id: text(), cloud_agent_session_scope_id: text(), cloud_agent_worktree_id: text(), @@ -6416,6 +6424,10 @@ export const cli_sessions_v2 = pgTable( table.parent_session_id, table.kilo_user_id ), + // Supports the ON DELETE SET NULL scan when a profile is deleted. Built + // concurrently — `cli_sessions_v2` is large, so a plain build blocks writes + // for the whole scan, the same reason the `user_*` indexes below do. + index('IDX_cli_sessions_v2_profile_id').on(table.profile_id).concurrently(), uniqueIndex('UQ_cli_sessions_v2_public_id') .on(table.public_id) .where(isNotNull(table.public_id)), diff --git a/packages/session-ingest-contracts/src/rpc-contract.ts b/packages/session-ingest-contracts/src/rpc-contract.ts index 2c4a25a3c70..0963c8d751b 100644 --- a/packages/session-ingest-contracts/src/rpc-contract.ts +++ b/packages/session-ingest-contracts/src/rpc-contract.ts @@ -155,6 +155,11 @@ export const createSessionForCloudAgentSchema = z.object({ title: z.string().optional(), // Compatibility: old Cloud Agent workers omit gitUrl; remove after all deployed workers send it. gitUrl: z.string().optional(), + // The profile the session was prepared with, as resolved by the caller + // (explicit pick, effective default, or repository binding). Optional and + // nullable so a caller that predates profile recording still creates the + // session; that row then carries no profile. + profileId: z.string().min(1).nullable().optional(), // Compatibility: old callers omit this field and create empty destination // storage. Remove that path only when all deployed callers require cloning. cloneFromKiloSessionId: sessionIdSchema.optional(), diff --git a/packages/trpc/src/agent-profiles-mobile.ts b/packages/trpc/src/agent-profiles-mobile.ts index 518b6b26f16..230b4a93ffa 100644 --- a/packages/trpc/src/agent-profiles-mobile.ts +++ b/packages/trpc/src/agent-profiles-mobile.ts @@ -3,13 +3,43 @@ import { createTRPCRouter } from '@/lib/trpc/init'; import { agentProfilesRouter } from '@/routers/agent-profiles-router'; /** - * Mobile-scoped agent profiles router. Exposes only the read-side procedures - * the mobile new-session flow needs — `list`, `listCombined`, and `get`. The - * mutation surface (create/update/delete/...) stays web-only, so the mobile - * client cannot build a profile editor through this mount. + * Mobile-scoped agent profiles router. Exposes the profile-management + * procedures the mobile profiles screens call — the read-side helpers + * (`list`, `listCombined`, `get`) plus the mutations for creating, editing, + * deleting, and configuring profiles. Every procedure keeps the server-side + * `baseProcedure` auth and the per-call `ensureOrganizationAccess` check + * inside `agentProfilesRouter`; this mount only re-exposes them to the mobile + * client and adds no server behavior of its own. */ export const agentProfilesMobileRouter = createTRPCRouter({ list: agentProfilesRouter.list, listCombined: agentProfilesRouter.listCombined, get: agentProfilesRouter.get, + create: agentProfilesRouter.create, + update: agentProfilesRouter.update, + delete: agentProfilesRouter.delete, + setAsDefault: agentProfilesRouter.setAsDefault, + clearDefault: agentProfilesRouter.clearDefault, + setVar: agentProfilesRouter.setVar, + deleteVar: agentProfilesRouter.deleteVar, + setCommands: agentProfilesRouter.setCommands, + createCustomSkill: agentProfilesRouter.createCustomSkill, + updateSkill: agentProfilesRouter.updateSkill, + deleteSkill: agentProfilesRouter.deleteSkill, + setSkillEnabled: agentProfilesRouter.setSkillEnabled, + createMcp: agentProfilesRouter.createMcp, + updateMcp: agentProfilesRouter.updateMcp, + deleteMcp: agentProfilesRouter.deleteMcp, + setMcpEnabled: agentProfilesRouter.setMcpEnabled, + createAgent: agentProfilesRouter.createAgent, + updateAgent: agentProfilesRouter.updateAgent, + deleteAgent: agentProfilesRouter.deleteAgent, + createKiloCommand: agentProfilesRouter.createKiloCommand, + updateKiloCommand: agentProfilesRouter.updateKiloCommand, + deleteKiloCommand: agentProfilesRouter.deleteKiloCommand, + setKiloCommandEnabled: agentProfilesRouter.setKiloCommandEnabled, + reorderKiloCommands: agentProfilesRouter.reorderKiloCommands, + bindToRepo: agentProfilesRouter.bindToRepo, + unbindRepo: agentProfilesRouter.unbindRepo, + listRepoBindings: agentProfilesRouter.listRepoBindings, }); diff --git a/services/cloud-agent-next/src/router/handlers/session-prepare.ts b/services/cloud-agent-next/src/router/handlers/session-prepare.ts index 5a14b5ff1c9..0d00b1e21f5 100644 --- a/services/cloud-agent-next/src/router/handlers/session-prepare.ts +++ b/services/cloud-agent-next/src/router/handlers/session-prepare.ts @@ -162,6 +162,10 @@ function applyProfileResolution( ...input, profile: { ...input.profile, + // The resolved active profile id. Absent when no profile applied, so a + // session that runs on no profile records none rather than the client's + // requested id. + ...(resolved.resolvedProfileId ? { resolvedProfileId: resolved.resolvedProfileId } : {}), resolved: { envVars: resolved.envVars, setupCommands: resolved.setupCommands, diff --git a/services/cloud-agent-next/src/session-prepare.test.ts b/services/cloud-agent-next/src/session-prepare.test.ts index cf060884ef8..f0c497b7410 100644 --- a/services/cloud-agent-next/src/session-prepare.test.ts +++ b/services/cloud-agent-next/src/session-prepare.test.ts @@ -776,6 +776,7 @@ describe('prepareSession endpoint', () => { 'test-user-123', expect.any(Object), 'f47ac10b-58cc-4372-a567-0e02b2c3d479', + undefined, 'code-review', expect.stringMatching(/^New session - /), 'https://github.com/acme/repo', @@ -844,6 +845,34 @@ describe('prepareSession endpoint', () => { expect(selectSandboxForNewSessionMock).not.toHaveBeenCalled(); }); + it('persists the resolved profile id on the created session, not the requested one', async () => { + mergeProfileConfigurationMock.mockResolvedValue({ resolvedProfileId: 'profile-abc123' }); + const doStub = createMockDOStub(); + const caller = appRouter.createCaller(createInternalApiContext({ doStub })); + + await caller.prepareSession({ + prompt: 'Test prompt', + mode: 'code', + model: 'claude-3', + githubRepo: 'acme/repo', + profileId: 'a1111111-1111-4111-8111-111111111111', + createdOnPlatform: 'cloud-agent-web', + }); + + expect(createCliSessionMock).toHaveBeenCalledWith( + expect.any(String), + expect.any(String), + 'test-user-123', + expect.any(Object), + undefined, + 'profile-abc123', + 'cloud-agent-web', + expect.stringMatching(/^New session - /), + 'https://github.com/acme/repo', + undefined + ); + }); + it('rejects organization attribution when the internal caller user is not a member', async () => { organizationMembershipLimitMock.mockResolvedValueOnce([]); const doStub = createMockDOStub(); diff --git a/services/cloud-agent-next/src/session-service.test.ts b/services/cloud-agent-next/src/session-service.test.ts index b2d8cc11741..88a7c5ea3d1 100644 --- a/services/cloud-agent-next/src/session-service.test.ts +++ b/services/cloud-agent-next/src/session-service.test.ts @@ -4235,6 +4235,7 @@ describe('SessionService session-ingest compatibility', () => { 'user_test', env, undefined, + undefined, 'cloud-agent' ); @@ -4255,6 +4256,7 @@ describe('SessionService session-ingest compatibility', () => { 'oauth/google:1234', env, undefined, + undefined, 'cloud-agent-web', undefined, undefined, @@ -4270,6 +4272,26 @@ describe('SessionService session-ingest compatibility', () => { ); }); + it('forwards the resolved profileId when creating the ownership row', async () => { + const env = createEnv(); + const service = new SessionService(); + + await service.createCliSessionViaSessionIngest( + 'ses_12345678901234567890123456', + 'agent_12345678-1234-1234-1234-123456789abc', + 'user_test', + env, + undefined, + 'profile-abc123', + 'cloud-agent' + ); + + // eslint-disable-next-line @typescript-eslint/unbound-method + expect(env.SESSION_INGEST.createSessionForCloudAgent).toHaveBeenCalledWith( + expect.objectContaining({ profileId: 'profile-abc123' }) + ); + }); + it('forwards cloneFromKiloSessionId and returns the typed RPC result', async () => { const env = createEnv(); const service = new SessionService(); @@ -4285,6 +4307,7 @@ describe('SessionService session-ingest compatibility', () => { 'user_test', env, undefined, + undefined, 'cloud-agent', undefined, undefined, @@ -4298,6 +4321,7 @@ describe('SessionService session-ingest compatibility', () => { kiloUserId: 'user_test', cloudAgentSessionId: 'agent_12345678-1234-1234-1234-123456789abc', organizationId: undefined, + profileId: undefined, createdOnPlatform: 'cloud-agent', title: undefined, gitUrl: undefined, @@ -4315,6 +4339,7 @@ describe('SessionService session-ingest compatibility', () => { 'user_test', env, undefined, + undefined, 'cloud-agent', undefined, 'ses_aaaaaaaaaaaaaaaaaaaaaaaaaa' diff --git a/services/cloud-agent-next/src/session-service.ts b/services/cloud-agent-next/src/session-service.ts index 260c9deab90..922aac951ce 100644 --- a/services/cloud-agent-next/src/session-service.ts +++ b/services/cloud-agent-next/src/session-service.ts @@ -3012,6 +3012,7 @@ export class SessionService { kiloUserId: string, env: PersistenceEnv, organizationId: string | undefined, + profileId: string | null | undefined, createdOnPlatform: string, title?: string, gitUrl?: string, @@ -3028,6 +3029,7 @@ export class SessionService { createdOnPlatform, title, gitUrl, + profileId, cloneFromKiloSessionId, ...(cloudAgentWorktreeId ? { cloudAgentWorktreeId } : {}), ...(cloudAgentWorktreeLocation ? { cloudAgentWorktreeLocation } : {}), diff --git a/services/cloud-agent-next/src/session/session-prepare.test.ts b/services/cloud-agent-next/src/session/session-prepare.test.ts index bbafa67d699..33a847c0010 100644 --- a/services/cloud-agent-next/src/session/session-prepare.test.ts +++ b/services/cloud-agent-next/src/session/session-prepare.test.ts @@ -1544,6 +1544,7 @@ describe('createSessionWithLedger admission ladder', () => { USER_ID, expect.any(Object), undefined, + undefined, 'cloud-agent-web', expect.any(String), 'https://github.com/acme/repo', @@ -1604,7 +1605,7 @@ describe('createSessionWithLedger admission ladder', () => { expect(result.cloudAgentSessionId).toBe(WORKSPACE_SESSION_ID); expect(generateSessionIdMock).toHaveBeenCalledWith('control'); - expect(createCliSessionMock.mock.calls[0]).toHaveLength(9); + expect(createCliSessionMock.mock.calls[0]).toHaveLength(10); expect(doStub.createSessionWithInitialAdmission).toHaveBeenCalledWith( expect.objectContaining({ finalization: { autoCommit: true }, @@ -1830,7 +1831,7 @@ describe('createSessionWithLedger admission ladder', () => { }) ); - expect(createCliSessionMock.mock.calls[0]).toHaveLength(9); + expect(createCliSessionMock.mock.calls[0]).toHaveLength(10); expect(doStub.createSessionWithInitialAdmission).toHaveBeenCalledWith( expect.objectContaining({ finalization: { autoCommit: true }, @@ -1868,7 +1869,7 @@ describe('createSessionWithLedger admission ladder', () => { }) ); - expect(createCliSessionMock.mock.calls[0]).toHaveLength(9); + expect(createCliSessionMock.mock.calls[0]).toHaveLength(10); expect(doStub.createSessionWithInitialAdmission).toHaveBeenCalledWith( expect.objectContaining({ finalization: { autoCommit: true }, @@ -1893,7 +1894,7 @@ describe('createSessionWithLedger admission ladder', () => { }) ); - expect(createCliSessionMock.mock.calls[0]).toHaveLength(9); + expect(createCliSessionMock.mock.calls[0]).toHaveLength(10); expect(doStub.createSessionWithInitialAdmission).toHaveBeenCalledWith( expect.objectContaining({ finalization: { autoCommit: true } }) ); @@ -1916,7 +1917,7 @@ describe('createSessionWithLedger admission ladder', () => { }) ); - expect(createCliSessionMock.mock.calls[0]).toHaveLength(9); + expect(createCliSessionMock.mock.calls[0]).toHaveLength(10); expect(doStub.createSessionWithInitialAdmission).toHaveBeenCalledWith( expect.objectContaining({ finalization: { autoCommit: true } }) ); @@ -1939,6 +1940,7 @@ describe('createSessionWithLedger admission ladder', () => { USER_ID, expect.any(Object), undefined, + undefined, 'cloud-agent', expect.any(String), 'https://github.com/acme/widgets', @@ -1963,6 +1965,7 @@ describe('createSessionWithLedger admission ladder', () => { USER_ID, expect.any(Object), undefined, + undefined, 'cloud-agent', expect.any(String), 'https://gitlab.com/acme/widgets', @@ -1992,6 +1995,7 @@ describe('createSessionWithLedger admission ladder', () => { USER_ID, expect.any(Object), undefined, + undefined, 'cloud-agent', expect.any(String), 'https://bitbucket.org/acme/widgets', @@ -3472,7 +3476,7 @@ describe('createSessionWithLedger worktree rollout and ownership reconciliation' }); expect(createCliSessionMock).toHaveBeenCalledTimes(1); - expect(createCliSessionMock.mock.calls[0]).toHaveLength(9); + expect(createCliSessionMock.mock.calls[0]).toHaveLength(10); expect(generateSessionIdMock).toHaveBeenCalledTimes(1); expect(generateKiloSessionIdMock).toHaveBeenCalledTimes(1); expect(doStub.createSessionWithInitialAdmission).toHaveBeenCalledTimes(1); @@ -3541,6 +3545,7 @@ describe('createSessionWithLedger worktree rollout and ownership reconciliation' USER_ID, expect.any(Object), undefined, + undefined, 'cloud-agent-web', expect.any(String), 'https://github.com/acme/repo', @@ -4273,6 +4278,7 @@ describe('createSessionWithLedger clone allocation outcomes', () => { USER_ID, expect.any(Object), undefined, + undefined, 'cloud-agent', expect.any(String), 'https://github.com/acme/repo', @@ -4523,6 +4529,7 @@ describe('createSessionWithLedger clone allocation outcomes', () => { USER_ID, expect.any(Object), undefined, + undefined, 'cloud-agent', expect.any(String), 'https://github.com/acme/repo', @@ -4817,6 +4824,7 @@ describe('createSessionWithLedger clone reconciliation', () => { USER_ID, expect.any(Object), undefined, + undefined, 'cloud-agent', expect.any(String), 'https://github.com/acme/repo', @@ -5029,6 +5037,7 @@ describe('createSessionWithLedger clone reconciliation', () => { USER_ID, expect.any(Object), undefined, + undefined, 'cloud-agent', expect.any(String), 'https://github.com/acme/repo', @@ -5138,6 +5147,7 @@ describe('createSessionWithLedger clone reconciliation', () => { USER_ID, expect.any(Object), undefined, + undefined, 'cloud-agent', expect.any(String), 'https://github.com/acme/repo', @@ -5191,6 +5201,7 @@ describe('createSessionWithLedger clone reconciliation', () => { USER_ID, expect.any(Object), undefined, + undefined, 'cloud-agent', expect.any(String), 'https://github.com/acme/repo', @@ -5232,6 +5243,7 @@ describe('createSessionWithLedger clone reconciliation', () => { USER_ID, expect.any(Object), undefined, + undefined, 'cloud-agent', expect.any(String), 'https://github.com/acme/repo', diff --git a/services/cloud-agent-next/src/session/session-registration.ts b/services/cloud-agent-next/src/session/session-registration.ts index 48508c61211..da87488d0c8 100644 --- a/services/cloud-agent-next/src/session/session-registration.ts +++ b/services/cloud-agent-next/src/session/session-registration.ts @@ -773,6 +773,7 @@ async function allocateNewSession( ctx.userId, ctx.env, input.options?.kilocodeOrganizationId, + input.profile?.resolvedProfileId, createdOnPlatform, defaultTitle, canonicalRepositoryUrl, @@ -1877,6 +1878,7 @@ async function resumeCloneCreate( ctx.userId, ctx.env, input.options?.kilocodeOrganizationId, + input.profile?.resolvedProfileId, createdOnPlatform, defaultTitle, canonicalRepositoryUrl, @@ -1972,6 +1974,7 @@ async function resumeFirstWorktreeCreate( ctx.userId, ctx.env, input.options?.kilocodeOrganizationId, + input.profile?.resolvedProfileId, input.options?.createdOnPlatform ?? 'cloud-agent', `New session - ${new Date().toISOString()}`, deriveCanonicalRepositoryUrl(input.repository), diff --git a/services/cloud-agent-next/src/session/session-requests.ts b/services/cloud-agent-next/src/session/session-requests.ts index 2d1b76a5fa6..fe508e4b272 100644 --- a/services/cloud-agent-next/src/session/session-requests.ts +++ b/services/cloud-agent-next/src/session/session-requests.ts @@ -63,6 +63,13 @@ export type SessionCreateRequest = { id?: string; overrides?: ProfileOverrides; resolved?: SessionProfileBundle; + /** + * The active profile id after server-side resolution, set by + * `applyProfileResolution`. Distinguished from `id` (the client's + * requested pick) because a repository binding or the effective default + * can decide the session's profile. Persisted on `cli_sessions_v2`. + */ + resolvedProfileId?: string; }; finalization?: SessionFinalization; options?: { diff --git a/services/session-ingest/src/session-ingest-rpc.test.ts b/services/session-ingest/src/session-ingest-rpc.test.ts index 2d9afc2df11..878046f2884 100644 --- a/services/session-ingest/src/session-ingest-rpc.test.ts +++ b/services/session-ingest/src/session-ingest-rpc.test.ts @@ -252,6 +252,25 @@ describe('createSessionForCloudAgent', () => { ); }); + it('persists the resolved profileId on the created root', async () => { + const profileId = '22222222-2222-4222-8222-222222222222'; + const fake = makeRootWriteDb({ created: { session_id: params.sessionId } }); + const rpc = makeRpc(fake.db as never); + + await rpc.createSessionForCloudAgent({ ...params, profileId }); + + expect(fake.values).toHaveBeenCalledWith(expect.objectContaining({ profile_id: profileId })); + }); + + it('writes a null profile_id when the caller sends none', async () => { + const fake = makeRootWriteDb({ created: { session_id: params.sessionId } }); + const rpc = makeRpc(fake.db as never); + + await rpc.createSessionForCloudAgent(params); + + expect(fake.values).toHaveBeenCalledWith(expect.objectContaining({ profile_id: null })); + }); + it('persists the worktree ID when creating a grouped root', async () => { const cloudAgentWorktreeId = 'worktree_11111111-1111-4111-8111-111111111111'; const row = { diff --git a/services/session-ingest/src/session-ingest-rpc.ts b/services/session-ingest/src/session-ingest-rpc.ts index 829aa41cf6e..a1692372ee2 100644 --- a/services/session-ingest/src/session-ingest-rpc.ts +++ b/services/session-ingest/src/session-ingest-rpc.ts @@ -327,6 +327,7 @@ export class SessionIngestRPC extends WorkerEntrypoint implements SessionIn cloud_agent_session_scope_id: parsed.cloudAgentSessionId, cloud_agent_worktree_id: parsed.cloudAgentWorktreeId ?? null, organization_id: parsed.organizationId ?? null, + profile_id: parsed.profileId ?? null, created_on_platform: parsed.createdOnPlatform, ...(parsed.title !== undefined ? { title: parsed.title } : {}), ...(inputGitUrl !== undefined ? { git_url: inputGitUrl } : {}), diff --git a/tools/i18n/check-catalogs.mjs b/tools/i18n/check-catalogs.mjs index 4239a9b53b2..5d79eada545 100644 --- a/tools/i18n/check-catalogs.mjs +++ b/tools/i18n/check-catalogs.mjs @@ -100,6 +100,23 @@ const ENGLISH_IDENTICAL_ALLOWLIST = new Set([ 'agentChat.sessionFilter.platformLinear', 'agentChat.repoPicker.platformBitbucket', 'agentChat.prBadge.label', + // Profile-editor examples that name the value's own syntax, not prose: a + // slug the field validator accepts, a server name, a JSON snippet, and + // command lines whose tool and package paths must stay literal. Every + // catalog keeps them English so the example never contradicts validation. + 'profiles.commandPlaceholder', + 'profiles.mcp.namePlaceholder', + 'profiles.mcp.commandPlaceholder', + 'profiles.mcp.jsonPlaceholder', + 'profiles.agents.slugPlaceholder', + // The sampling parameter's technical name: `top_p` is the knob's own + // identifier, so every catalog keeps it verbatim. + 'profiles.agents.topP', + 'profiles.slashCommands.namePlaceholder', + // The MCP count label. `MCP` is the protocol's own acronym, and its unit + // word is already as short as a compact count badge allows, so every catalog + // keeps the same "{{count}} MCP" the MCP-server rows use. + 'profiles.counts.mcp', 'share.reviewPrSubtitle', // Format-only strings with no translatable words: a placeholder-only screen // title, a placeholder-plus-UTC time-range label, the GitLab merge request @@ -182,6 +199,15 @@ function nestingRefs(value) { return [...value.matchAll(/\$t\(\s*([^)\s,]+)\s*\)/g)].map(match => match[1]).sort(); } +/** + * The `$TOKEN` expansion tokens of one string, e.g. `$ARGUMENTS`. Kilo expands + * these literally, so a translator who spells one out (or translates it) leaves + * the reader with a token nothing replaces. The set must match English. + */ +function expansionTokens(value) { + return [...value.matchAll(/\$[A-Z][A-Z0-9_]*/g)].map(match => match[0]).sort(); +} + /** The leading and trailing whitespace of one string, as a signature. */ function edges(value) { return JSON.stringify([/^\s*/.exec(value)[0], /\s*$/.exec(value)[0]]); @@ -540,6 +566,12 @@ for (const catalog of CATALOGS) { if (nestingRefs(value).join(',') !== expectedRefs) { fail(`${label}: "${key}" $t() references differ from English (${expectedRefs || 'none'})`); } + const expectedTokens = expansionTokens(englishValue).join(','); + if (expansionTokens(value).join(',') !== expectedTokens) { + fail( + `${label}: "${key}" $TOKEN expansion tokens differ from English (${expectedTokens || 'none'})` + ); + } if (edges(value) !== edges(englishValue)) { fail(`${label}: "${key}" leading or trailing space differs from English`); } diff --git a/tools/i18n/check-catalogs.test.mjs b/tools/i18n/check-catalogs.test.mjs index 02ebe40bf09..3c0967ac310 100644 --- a/tools/i18n/check-catalogs.test.mjs +++ b/tools/i18n/check-catalogs.test.mjs @@ -181,6 +181,19 @@ test('rejects an empty translated value', context => { assert.match(result.stderr, /mobile\/ru: "agents\.title" is empty/); }); +test('rejects a translated $TOKEN expansion token', context => { + const result = runChecker(context, { + english: { agents: { template: 'Explain $ARGUMENTS' } }, + translated: { agents: { template: 'Explique $ARGUMENTOS' } }, + source: "t('agents.template');", + }); + assert.equal(result.status, 1, result.stderr); + assert.match( + result.stderr, + /mobile\/ru: "agents\.template" \$TOKEN expansion tokens differ from English \(\$ARGUMENTS\)/ + ); +}); + test('rejects changed placeholders in an English plural sibling', context => { const result = runChecker(context, { translated: { agents: { ...TRANSLATED.agents, liveCount_one: '{{total}} localized session' } },