Mirror the Console registration feature: native hook catalog with activation plans, one-command install that embeds both credentials in exclusively created owner-only files (mktemp on POSIX, icacls on Windows), sh/PowerShell tabs, never-expiring default token, focus-visible styles, merged i18n keys, and the matching e2e specs. The server previously answered /v1/auth/token with the 7-day Better Auth session token and ignored ttl_seconds. It now issues opaque 32-byte API tokens stored as SHA-256 hashes (migration 003), honours ttl_seconds with null meaning no expiry, and resolves bearer values as API tokens before session validation. Issuance requires a session credential. Claude-Session: https://claude.ai/code/session_01QoUjdc7HGKTsgj5GhG8HvF
63 lines
2.1 KiB
TypeScript
63 lines
2.1 KiB
TypeScript
'use client';
|
|
|
|
import { useTranslation } from 'react-i18next';
|
|
import {
|
|
INTEGRATION_CATALOG,
|
|
type IntegrationCatalogItem,
|
|
type IntegrationMode,
|
|
} from '@/features/agent-registration/integrations';
|
|
|
|
interface IntegrationStepProps {
|
|
readonly onSelect: (integration: IntegrationCatalogItem) => void;
|
|
}
|
|
|
|
function IntegrationGroup({
|
|
mode,
|
|
onSelect,
|
|
}: {
|
|
readonly mode: IntegrationMode;
|
|
readonly onSelect: (integration: IntegrationCatalogItem) => void;
|
|
}) {
|
|
const { t } = useTranslation();
|
|
const integrations = INTEGRATION_CATALOG.filter((item) => item.mode === mode);
|
|
|
|
return (
|
|
<section aria-labelledby={`integration-${mode}`}>
|
|
<h3 id={`integration-${mode}`} className="section-label mb-2">
|
|
{t(`agentRegistration.integrationGroups.${mode}`)}
|
|
</h3>
|
|
<div className="grid sm:grid-cols-2 border-t border-border sm:[&>button:nth-child(odd)]:border-r">
|
|
{integrations.map((integration) => (
|
|
<button
|
|
key={integration.id}
|
|
type="button"
|
|
onClick={() => onSelect(integration)}
|
|
className="group flex items-center justify-between gap-3 px-3 py-3 text-left border-b border-border hover:bg-surface focus-visible:bg-surface"
|
|
>
|
|
<span className="font-mono text-[12px] text-ink">{integration.name}</span>
|
|
<span className="flex items-center gap-2" aria-hidden="true">
|
|
{mode === 'hooks' && (
|
|
<span className="font-mono text-[9px] tracking-wider text-ink-dim border border-border px-1.5 py-0.5">
|
|
{t('agentRegistration.hookBadge')}
|
|
</span>
|
|
)}
|
|
<span className="font-mono text-[10px] text-ink-dim opacity-0 group-hover:opacity-100 group-focus-visible:opacity-100 transition-opacity">
|
|
→
|
|
</span>
|
|
</span>
|
|
</button>
|
|
))}
|
|
</div>
|
|
</section>
|
|
);
|
|
}
|
|
|
|
export default function IntegrationStep({ onSelect }: IntegrationStepProps) {
|
|
return (
|
|
<div className="space-y-6">
|
|
<IntegrationGroup mode="hooks" onSelect={onSelect} />
|
|
<IntegrationGroup mode="sdk" onSelect={onSelect} />
|
|
</div>
|
|
);
|
|
}
|