console: sync the registration wizard and issue real API tokens
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
This commit is contained in:
1 parent
1bb9d4d49d
commit
edf9d73c3e
21 files changed
+967
-163
No files matched your search
@@ -353,3 +353,21 @@ body {
|
||||
.check-fail {
|
||||
color: #dc2626;
|
||||
}
|
||||
|
||||
/* Focus-visible styles for keyboard navigation (WCAG 2.1 AA) */
|
||||
:focus-visible {
|
||||
outline: 2px solid var(--ink-color);
|
||||
outline-offset: 2px;
|
||||
}
|
||||
|
||||
.btn-brutalist:focus-visible,
|
||||
.btn-ghost:focus-visible {
|
||||
outline: 2px solid var(--bg-color);
|
||||
outline-offset: 2px;
|
||||
box-shadow: 0 0 0 4px var(--ink-color);
|
||||
}
|
||||
|
||||
.sidebar-nav-item:focus-visible {
|
||||
outline: 2px solid #EAEAE5;
|
||||
outline-offset: -2px;
|
||||
}
|
||||
@@ -12,7 +12,7 @@ import Modal from '@/components/ui/Modal';
|
||||
import Link from 'next/link';
|
||||
import type { AgentKey, Operation } from '@elydora/shared';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { ADAPTER_INTEGRATION_IDS } from '@/features/agent-registration/integrations';
|
||||
import { HOOK_INTEGRATION_IDS } from '@/features/agent-registration/integrations';
|
||||
|
||||
export default function AgentDetailClient({ agentId }: { agentId: string }) {
|
||||
const { t } = useTranslation();
|
||||
@@ -37,7 +37,7 @@ export default function AgentDetailClient({ agentId }: { agentId: string }) {
|
||||
|
||||
const agent = agentData?.agent;
|
||||
const supportsFreeze = agent
|
||||
? ADAPTER_INTEGRATION_IDS.has(agent.integration_type)
|
||||
? HOOK_INTEGRATION_IDS.has(agent.integration_type)
|
||||
: false;
|
||||
const keys = agentData?.keys ?? [];
|
||||
const operations = opsData?.operations ?? [];
|
||||
|
||||
@@ -7,7 +7,9 @@ import type { IntegrationCatalogItem } from '@/features/agent-registration/integ
|
||||
import {
|
||||
buildInstallInstructions,
|
||||
SDK_LANGUAGES,
|
||||
SHELLS,
|
||||
type SdkLanguage,
|
||||
type Shell,
|
||||
} from '@/features/agent-registration/install-instructions';
|
||||
import CopyControl from './CopyControl';
|
||||
|
||||
@@ -28,6 +30,7 @@ export default function ConnectStep({
|
||||
}: ConnectStepProps) {
|
||||
const { t } = useTranslation();
|
||||
const [language, setLanguage] = useState<SdkLanguage>('node');
|
||||
const [shell, setShell] = useState<Shell>('posix');
|
||||
const baseUrl = process.env.NEXT_PUBLIC_API_BASE_URL ?? 'http://localhost:8787';
|
||||
const instructions = useMemo(
|
||||
() => buildInstallInstructions(language, {
|
||||
@@ -38,9 +41,12 @@ export default function ConnectStep({
|
||||
orgId: credentials.orgId,
|
||||
},
|
||||
baseUrl,
|
||||
}),
|
||||
[baseUrl, credentials.agentId, credentials.kid, credentials.orgId, integration, language],
|
||||
privateKey: credentials.privateKey,
|
||||
token,
|
||||
}, shell),
|
||||
[baseUrl, credentials, integration, language, shell, token],
|
||||
);
|
||||
const PostInstallList = instructions.postInstall?.kind === 'alternatives' ? 'ul' : 'ol';
|
||||
|
||||
return (
|
||||
<div className="space-y-5">
|
||||
@@ -66,10 +72,29 @@ export default function ConnectStep({
|
||||
))}
|
||||
</div>
|
||||
|
||||
{integration.mode === 'hooks' && (
|
||||
<div className="flex border-b border-border" role="tablist" aria-label={t('agentRegistration.shell')}>
|
||||
{SHELLS.map((item) => (
|
||||
<button
|
||||
key={item}
|
||||
type="button"
|
||||
role="tab"
|
||||
aria-selected={shell === item}
|
||||
onClick={() => setShell(item)}
|
||||
className={`px-4 py-2 font-mono text-[10px] tracking-wider border-b-2 -mb-px ${
|
||||
shell === item ? 'border-ink text-ink' : 'border-transparent text-ink-dim hover:text-ink'
|
||||
}`}
|
||||
>
|
||||
{item === 'posix' ? 'sh' : 'PowerShell'}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
|
||||
<section aria-labelledby="setup-command-label">
|
||||
<div className="flex items-center justify-between mb-2">
|
||||
<h3 id="setup-command-label" className="section-label">
|
||||
{integration.mode === 'adapter' ? t('agentRegistration.install') : t('agentRegistration.configure')}
|
||||
{integration.mode === 'hooks' ? t('agentRegistration.install') : t('agentRegistration.configure')}
|
||||
</h3>
|
||||
<CopyControl
|
||||
value={instructions.setup}
|
||||
@@ -138,14 +163,35 @@ export default function ConnectStep({
|
||||
)}
|
||||
|
||||
{instructions.postInstall && (
|
||||
<ol className="border-t border-border" aria-label={t('agentRegistration.postInstall')}>
|
||||
{instructions.postInstall.map((step, index) => (
|
||||
<li key={step} className="grid grid-cols-[2rem_1fr] gap-2 py-2 border-b border-border font-mono text-[11px] text-ink">
|
||||
<span className="text-ink-dim tabular-nums">{index + 1}</span>
|
||||
<span>{step}</span>
|
||||
<PostInstallList className="border-t border-border" aria-label={t('agentRegistration.postInstall')}>
|
||||
{instructions.postInstall.steps.map((step, index) => (
|
||||
<li
|
||||
key={`${step.key}:${step.command ?? ''}`}
|
||||
className="group grid grid-cols-[2rem_minmax(0,1fr)_auto] items-start gap-2 border-b border-border py-2 font-mono text-[11px] text-ink"
|
||||
>
|
||||
<span className="text-ink-dim tabular-nums">
|
||||
{step.variant ?? String(index + 1).padStart(2, '0')}
|
||||
</span>
|
||||
<span className="min-w-0">
|
||||
<span className="block">
|
||||
{t(`agentRegistration.postInstallSteps.${step.key}`, {
|
||||
integration: integration.name,
|
||||
})}
|
||||
</span>
|
||||
{step.command && (
|
||||
<code className="mt-1 block break-all text-ink-dim">{step.command}</code>
|
||||
)}
|
||||
</span>
|
||||
{step.command && (
|
||||
<CopyControl
|
||||
value={step.command}
|
||||
ariaLabel={t('agentRegistration.copyPostInstall', { command: step.command })}
|
||||
className="opacity-100 transition-opacity md:opacity-0 md:group-hover:opacity-100 md:group-focus-within:opacity-100"
|
||||
/>
|
||||
)}
|
||||
</li>
|
||||
))}
|
||||
</ol>
|
||||
</PostInstallList>
|
||||
)}
|
||||
|
||||
<div className="flex items-center justify-between pt-2">
|
||||
|
||||
@@ -35,6 +35,7 @@ function CredentialRow({ label, value }: { readonly label: string; readonly valu
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default function CredentialsStep({
|
||||
credentials,
|
||||
token,
|
||||
@@ -44,7 +45,7 @@ export default function CredentialsStep({
|
||||
onContinue,
|
||||
}: CredentialsStepProps) {
|
||||
const { t } = useTranslation();
|
||||
const [expiration, setExpiration] = useState<ExpirationId>('24hours');
|
||||
const [expiration, setExpiration] = useState<ExpirationId>('neverExpire');
|
||||
const [customDays, setCustomDays] = useState('');
|
||||
const [validationError, setValidationError] = useState<string | null>(null);
|
||||
|
||||
|
||||
@@ -32,11 +32,18 @@ function IntegrationGroup({
|
||||
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 focus-visible:outline-none"
|
||||
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="font-mono text-[10px] text-ink-dim opacity-0 group-hover:opacity-100 group-focus-visible:opacity-100 transition-opacity" aria-hidden="true">
|
||||
→
|
||||
<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>
|
||||
))}
|
||||
@@ -44,10 +51,11 @@ function IntegrationGroup({
|
||||
</section>
|
||||
);
|
||||
}
|
||||
|
||||
export default function IntegrationStep({ onSelect }: IntegrationStepProps) {
|
||||
return (
|
||||
<div className="space-y-6">
|
||||
<IntegrationGroup mode="adapter" onSelect={onSelect} />
|
||||
<IntegrationGroup mode="hooks" onSelect={onSelect} />
|
||||
<IntegrationGroup mode="sdk" onSelect={onSelect} />
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
import type { IntegrationCatalogItem } from './integrations';
|
||||
import type { IntegrationCatalogItem, PostInstallPlan } from './integrations';
|
||||
|
||||
export const SDK_LANGUAGES = ['node', 'python', 'go'] as const;
|
||||
export type SdkLanguage = (typeof SDK_LANGUAGES)[number];
|
||||
type SecretDelivery = 'hidden-prompts' | 'environment';
|
||||
export const SHELLS = ['posix', 'powershell'] as const;
|
||||
export type Shell = (typeof SHELLS)[number];
|
||||
type SecretDelivery = 'embedded' | 'environment';
|
||||
|
||||
interface AgentInstallIdentity {
|
||||
readonly agentId: string;
|
||||
@@ -14,13 +16,15 @@ interface InstructionInput {
|
||||
readonly integration: IntegrationCatalogItem;
|
||||
readonly identity: AgentInstallIdentity;
|
||||
readonly baseUrl: string;
|
||||
readonly privateKey: string;
|
||||
readonly token: string;
|
||||
}
|
||||
|
||||
export interface InstallInstructions {
|
||||
readonly setup: string;
|
||||
readonly usage?: string;
|
||||
readonly verify?: string;
|
||||
readonly postInstall?: readonly string[];
|
||||
readonly postInstall?: PostInstallPlan;
|
||||
readonly secretDelivery: SecretDelivery;
|
||||
}
|
||||
|
||||
@@ -31,42 +35,97 @@ function shellQuoted(value: string, label: string): string {
|
||||
return `'${value}'`;
|
||||
}
|
||||
|
||||
function adapterCommand(
|
||||
const INSTALLER: Record<SdkLanguage, string> = {
|
||||
node: 'npx @elydora/sdk install',
|
||||
python: 'elydora install',
|
||||
go: 'elydora install',
|
||||
};
|
||||
const BOOTSTRAP: Partial<Record<SdkLanguage, string>> = {
|
||||
python: 'python -m pip install elydora',
|
||||
go: 'go install github.com/Elydora-Infrastructure/Elydora-Go-SDK/v2/cmd/elydora@latest',
|
||||
};
|
||||
|
||||
interface CommandValues {
|
||||
readonly agent: string;
|
||||
readonly orgId: string;
|
||||
readonly agentId: string;
|
||||
readonly kid: string;
|
||||
readonly baseUrl: string;
|
||||
}
|
||||
|
||||
function credentialFileStem(agentId: string): string {
|
||||
if (!/^[A-Za-z0-9._-]+$/u.test(agentId)) {
|
||||
throw new Error('Agent ID cannot be used as a credential file name.');
|
||||
}
|
||||
return `.elydora-${agentId}`;
|
||||
}
|
||||
|
||||
function installArguments(
|
||||
language: SdkLanguage,
|
||||
{ integration, identity, baseUrl }: InstructionInput,
|
||||
values: CommandValues,
|
||||
keyFile: string,
|
||||
tokenFile: string,
|
||||
): string {
|
||||
const values = {
|
||||
if (language === 'go') {
|
||||
return `--agent ${values.agent} --org-id ${values.orgId} --agent-id ${values.agentId} --kid ${values.kid} --base-url ${values.baseUrl} --private-key-file ${keyFile} --token-file ${tokenFile}`;
|
||||
}
|
||||
return `--agent ${values.agent} --org_id ${values.orgId} --agent_id ${values.agentId} --kid ${values.kid} --base_url ${values.baseUrl} --private_key_file ${keyFile} --token_file ${tokenFile}`;
|
||||
}
|
||||
|
||||
function posixInstall(
|
||||
language: SdkLanguage,
|
||||
values: CommandValues,
|
||||
stem: string,
|
||||
privateKey: string,
|
||||
token: string,
|
||||
): string {
|
||||
const template = `"$HOME/${stem}.XXXXXX"`;
|
||||
const script = [
|
||||
`k=$(mktemp ${template})`,
|
||||
`trap 'rm -f "$k"' EXIT INT TERM`,
|
||||
`t=$(mktemp ${template})`,
|
||||
`trap 'rm -f "$k" "$t"' EXIT INT TERM`,
|
||||
`printf '%s\\n' ${shellQuoted(privateKey, 'Private key')} > "$k"`,
|
||||
`printf '%s\\n' ${shellQuoted(token, 'API token')} > "$t"`,
|
||||
`${INSTALLER[language]} ${installArguments(language, values, '"$k"', '"$t"')}`,
|
||||
].join(' && ');
|
||||
return `printf '%s' '${script.replace(/'/gu, "'\\''")}' | sh`;
|
||||
}
|
||||
|
||||
function powershellInstall(
|
||||
language: SdkLanguage,
|
||||
values: CommandValues,
|
||||
stem: string,
|
||||
privateKey: string,
|
||||
token: string,
|
||||
): string {
|
||||
const paths = `$k = Join-Path $HOME '${stem}.key'; $t = Join-Path $HOME '${stem}.token'`;
|
||||
const create = 'New-Item -ItemType File -Force -Path $k, $t | Out-Null';
|
||||
const protect = 'if ($IsLinux -or $IsMacOS) { chmod 600 $k $t; if ($LASTEXITCODE -ne 0) { throw "chmod failed" } } else { $u = [System.Security.Principal.WindowsIdentity]::GetCurrent().User.Value; foreach ($f in $k, $t) { icacls $f /inheritance:r /grant:r "*$($u):F" | Out-Null; if ($LASTEXITCODE -ne 0) { throw "icacls failed for $f" } } }';
|
||||
const store = `Set-Content -LiteralPath $k -Value ${shellQuoted(privateKey, 'Private key')}; Set-Content -LiteralPath $t -Value ${shellQuoted(token, 'API token')}`;
|
||||
const install = `${INSTALLER[language]} ${installArguments(language, values, '$k', '$t')}`;
|
||||
const status = 'if ($LASTEXITCODE -ne 0) { throw "elydora install exited with code $LASTEXITCODE" }';
|
||||
return `${paths}; try { ${create}; ${protect}; ${store}; ${install}; ${status} } finally { Remove-Item -LiteralPath $k, $t -Force -ErrorAction SilentlyContinue }`;
|
||||
}
|
||||
|
||||
function hookInstallerCommand(
|
||||
language: SdkLanguage,
|
||||
{ integration, identity, baseUrl, privateKey, token }: InstructionInput,
|
||||
shell: Shell,
|
||||
): string {
|
||||
const values: CommandValues = {
|
||||
agent: integration.id,
|
||||
orgId: shellQuoted(identity.orgId, 'Organization ID'),
|
||||
agentId: shellQuoted(identity.agentId, 'Agent ID'),
|
||||
kid: shellQuoted(identity.kid, 'Key ID'),
|
||||
baseUrl: shellQuoted(baseUrl, 'API base URL'),
|
||||
};
|
||||
|
||||
if (language === 'go') {
|
||||
return [
|
||||
'go install github.com/Elydora-Infrastructure/Elydora-Go-SDK/cmd/elydora@latest',
|
||||
`elydora install --agent ${values.agent} --org-id ${values.orgId} --agent-id ${values.agentId} --kid ${values.kid} --base-url ${values.baseUrl}`,
|
||||
].join('\n');
|
||||
}
|
||||
|
||||
const executable = language === 'node'
|
||||
? 'npx @elydora/sdk install'
|
||||
: 'python -m pip install elydora\nelydora install';
|
||||
return `${executable} --agent ${values.agent} --org_id ${values.orgId} --agent_id ${values.agentId} --kid ${values.kid} --base_url ${values.baseUrl}`;
|
||||
}
|
||||
|
||||
function postInstallSteps(integration: IntegrationCatalogItem): readonly string[] | undefined {
|
||||
if (integration.postInstall === 'review-hooks') {
|
||||
return ['Open the agent and run /hooks to review the active Elydora hooks.'];
|
||||
}
|
||||
if (integration.postInstall === 'start-kiro') {
|
||||
return [
|
||||
'Kiro CLI v2: kiro-cli --agent elydora-audit',
|
||||
'Kiro CLI v3: kiro-cli --v3',
|
||||
];
|
||||
}
|
||||
return undefined;
|
||||
const stem = credentialFileStem(identity.agentId);
|
||||
const command = shell === 'powershell'
|
||||
? powershellInstall(language, values, stem, privateKey, token)
|
||||
: posixInstall(language, values, stem, privateKey, token);
|
||||
const bootstrap = BOOTSTRAP[language];
|
||||
return bootstrap ? `${bootstrap}\n${command}` : command;
|
||||
}
|
||||
|
||||
function sdkSetup(language: SdkLanguage, input: InstructionInput): string {
|
||||
@@ -95,7 +154,7 @@ client.set_kid(${JSON.stringify(identity.kid)})`;
|
||||
}
|
||||
|
||||
if (language === 'go') {
|
||||
return `go get github.com/Elydora-Infrastructure/Elydora-Go-SDK
|
||||
return `go get github.com/Elydora-Infrastructure/Elydora-Go-SDK/v2
|
||||
|
||||
requireEnv := func(name string) string {
|
||||
value, ok := os.LookupEnv(name)
|
||||
@@ -176,13 +235,14 @@ await client.submitOperation(operation);`;
|
||||
export function buildInstallInstructions(
|
||||
language: SdkLanguage,
|
||||
input: InstructionInput,
|
||||
shell: Shell = 'posix',
|
||||
): InstallInstructions {
|
||||
if (input.integration.mode === 'adapter') {
|
||||
if (input.integration.mode === 'hooks') {
|
||||
return {
|
||||
setup: adapterCommand(language, input),
|
||||
setup: hookInstallerCommand(language, input, shell),
|
||||
verify: language === 'node' ? 'npx @elydora/sdk status' : 'elydora status',
|
||||
postInstall: postInstallSteps(input.integration),
|
||||
secretDelivery: 'hidden-prompts',
|
||||
postInstall: input.integration.postInstall,
|
||||
secretDelivery: 'embedded',
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -5,35 +5,169 @@ import {
|
||||
|
||||
export { INTEGRATION_TYPES };
|
||||
|
||||
export type IntegrationMode = 'adapter' | 'sdk';
|
||||
export type PostInstallAction = 'review-hooks' | 'start-kiro';
|
||||
export type IntegrationMode = 'hooks' | 'sdk';
|
||||
export type PostInstallStepKey =
|
||||
| 'inspectEnvironment'
|
||||
| 'inspectCursorHooks'
|
||||
| 'inspectGrokConfig'
|
||||
| 'inspectKiroIdeHooks'
|
||||
| 'launchKiroV2'
|
||||
| 'launchKiroV3'
|
||||
| 'listHooks'
|
||||
| 'openHooks'
|
||||
| 'reviewAndTrustHooks'
|
||||
| 'restartSession'
|
||||
| 'startSession'
|
||||
| 'startTask';
|
||||
|
||||
export interface PostInstallStep {
|
||||
readonly key: PostInstallStepKey;
|
||||
readonly command?: string;
|
||||
readonly variant?: string;
|
||||
}
|
||||
|
||||
export interface PostInstallPlan {
|
||||
readonly kind: 'sequence' | 'alternatives';
|
||||
readonly steps: readonly PostInstallStep[];
|
||||
}
|
||||
|
||||
export interface IntegrationCatalogItem {
|
||||
readonly id: IntegrationType;
|
||||
readonly name: string;
|
||||
readonly mode: IntegrationMode;
|
||||
readonly postInstall?: PostInstallAction;
|
||||
readonly postInstall?: PostInstallPlan;
|
||||
}
|
||||
|
||||
const INTEGRATION_METADATA: Record<
|
||||
IntegrationType,
|
||||
Omit<IntegrationCatalogItem, 'id'>
|
||||
> = {
|
||||
augment: { name: 'Augment Code', mode: 'adapter' },
|
||||
claudecode: { name: 'Claude Code', mode: 'adapter' },
|
||||
cline: { name: 'Cline', mode: 'adapter' },
|
||||
codex: { name: 'OpenAI Codex', mode: 'adapter', postInstall: 'review-hooks' },
|
||||
copilot: { name: 'GitHub Copilot CLI', mode: 'adapter' },
|
||||
cursor: { name: 'Cursor', mode: 'adapter' },
|
||||
droid: { name: 'Factory Droid', mode: 'adapter', postInstall: 'review-hooks' },
|
||||
gemini: { name: 'Gemini CLI', mode: 'adapter' },
|
||||
grok: { name: 'Grok Build', mode: 'adapter' },
|
||||
kimi: { name: 'Kimi CLI', mode: 'adapter', postInstall: 'review-hooks' },
|
||||
kirocli: { name: 'Kiro CLI', mode: 'adapter', postInstall: 'start-kiro' },
|
||||
kiroide: { name: 'Kiro IDE', mode: 'adapter' },
|
||||
letta: { name: 'Letta Code', mode: 'adapter' },
|
||||
opencode: { name: 'OpenCode', mode: 'adapter' },
|
||||
qwen: { name: 'Qwen Code', mode: 'adapter', postInstall: 'review-hooks' },
|
||||
augment: {
|
||||
name: 'Augment Code',
|
||||
mode: 'hooks',
|
||||
postInstall: {
|
||||
kind: 'sequence',
|
||||
steps: [{ key: 'startSession', command: 'auggie' }],
|
||||
},
|
||||
},
|
||||
claudecode: {
|
||||
name: 'Claude Code',
|
||||
mode: 'hooks',
|
||||
postInstall: {
|
||||
kind: 'sequence',
|
||||
steps: [{ key: 'openHooks', command: '/hooks' }],
|
||||
},
|
||||
},
|
||||
cline: {
|
||||
name: 'Cline',
|
||||
mode: 'hooks',
|
||||
postInstall: {
|
||||
kind: 'sequence',
|
||||
steps: [{ key: 'startTask' }],
|
||||
},
|
||||
},
|
||||
codex: {
|
||||
name: 'Codex CLI',
|
||||
mode: 'hooks',
|
||||
postInstall: {
|
||||
kind: 'sequence',
|
||||
steps: [{ key: 'reviewAndTrustHooks', command: '/hooks' }],
|
||||
},
|
||||
},
|
||||
copilot: {
|
||||
name: 'GitHub Copilot CLI',
|
||||
mode: 'hooks',
|
||||
postInstall: {
|
||||
kind: 'sequence',
|
||||
steps: [
|
||||
{ key: 'restartSession', command: '/restart' },
|
||||
{ key: 'inspectEnvironment', command: '/env' },
|
||||
],
|
||||
},
|
||||
},
|
||||
cursor: {
|
||||
name: 'Cursor CLI',
|
||||
mode: 'hooks',
|
||||
postInstall: {
|
||||
kind: 'sequence',
|
||||
steps: [{ key: 'inspectCursorHooks' }],
|
||||
},
|
||||
},
|
||||
droid: {
|
||||
name: 'Factory Droid',
|
||||
mode: 'hooks',
|
||||
postInstall: {
|
||||
kind: 'sequence',
|
||||
steps: [{ key: 'openHooks', command: '/hooks' }],
|
||||
},
|
||||
},
|
||||
gemini: {
|
||||
name: 'Gemini CLI',
|
||||
mode: 'hooks',
|
||||
postInstall: {
|
||||
kind: 'sequence',
|
||||
steps: [{ key: 'listHooks', command: '/hooks list' }],
|
||||
},
|
||||
},
|
||||
grok: {
|
||||
name: 'Grok Build',
|
||||
mode: 'hooks',
|
||||
postInstall: {
|
||||
kind: 'sequence',
|
||||
steps: [{ key: 'inspectGrokConfig', command: 'grok inspect --json' }],
|
||||
},
|
||||
},
|
||||
kimi: {
|
||||
name: 'Kimi Code',
|
||||
mode: 'hooks',
|
||||
postInstall: {
|
||||
kind: 'sequence',
|
||||
steps: [{ key: 'startSession', command: 'kimi' }],
|
||||
},
|
||||
},
|
||||
kirocli: {
|
||||
name: 'Kiro CLI',
|
||||
mode: 'hooks',
|
||||
postInstall: {
|
||||
kind: 'alternatives',
|
||||
steps: [
|
||||
{ key: 'launchKiroV2', command: 'kiro-cli --agent elydora-audit', variant: 'V2' },
|
||||
{ key: 'launchKiroV3', command: 'kiro-cli --v3', variant: 'V3' },
|
||||
],
|
||||
},
|
||||
},
|
||||
kiroide: {
|
||||
name: 'Kiro IDE',
|
||||
mode: 'hooks',
|
||||
postInstall: {
|
||||
kind: 'sequence',
|
||||
steps: [{ key: 'inspectKiroIdeHooks' }],
|
||||
},
|
||||
},
|
||||
letta: {
|
||||
name: 'Letta Code',
|
||||
mode: 'hooks',
|
||||
postInstall: {
|
||||
kind: 'sequence',
|
||||
steps: [{ key: 'openHooks', command: '/hooks' }],
|
||||
},
|
||||
},
|
||||
opencode: {
|
||||
name: 'OpenCode',
|
||||
mode: 'hooks',
|
||||
postInstall: {
|
||||
kind: 'sequence',
|
||||
steps: [{ key: 'startSession', command: 'opencode' }],
|
||||
},
|
||||
},
|
||||
qwen: {
|
||||
name: 'Qwen Code',
|
||||
mode: 'hooks',
|
||||
postInstall: {
|
||||
kind: 'sequence',
|
||||
steps: [{ key: 'listHooks', command: '/hooks list' }],
|
||||
},
|
||||
},
|
||||
enterprise: { name: 'Enterprise Agent', mode: 'sdk' },
|
||||
gui: { name: 'GUI Agent', mode: 'sdk' },
|
||||
sdk: { name: 'SDK', mode: 'sdk' },
|
||||
@@ -43,6 +177,6 @@ const INTEGRATION_METADATA: Record<
|
||||
export const INTEGRATION_CATALOG: readonly IntegrationCatalogItem[] =
|
||||
INTEGRATION_TYPES.map((id) => ({ id, ...INTEGRATION_METADATA[id] }));
|
||||
|
||||
export const ADAPTER_INTEGRATION_IDS: ReadonlySet<IntegrationType> = new Set(
|
||||
INTEGRATION_CATALOG.filter(({ mode }) => mode === 'adapter').map(({ id }) => id),
|
||||
export const HOOK_INTEGRATION_IDS: ReadonlySet<IntegrationType> = new Set(
|
||||
INTEGRATION_CATALOG.filter(({ mode }) => mode === 'hooks').map(({ id }) => id),
|
||||
);
|
||||
@@ -330,13 +330,15 @@
|
||||
"connect": "Connect"
|
||||
},
|
||||
"integrationGroups": {
|
||||
"adapter": "CLI adapters",
|
||||
"hooks": "Native hooks",
|
||||
"sdk": "Custom SDK"
|
||||
},
|
||||
"hookBadge": "HOOKS",
|
||||
"integration": "Integration",
|
||||
"copySetup": "Copy setup command",
|
||||
"copyUsage": "Copy operation example",
|
||||
"copyVerify": "Copy verification command",
|
||||
"copyPostInstall": "Copy activation command: {{command}}",
|
||||
"copyPrivateKey": "Copy private key",
|
||||
"copyApiToken": "Copy API token",
|
||||
"copyFailed": "Copy failed.",
|
||||
@@ -346,9 +348,24 @@
|
||||
"record": "Record an operation",
|
||||
"verify": "Verify",
|
||||
"postInstall": "Post-install steps",
|
||||
"postInstallSteps": {
|
||||
"inspectCursorHooks": "Open Customize → Hooks and confirm Elydora in the Hooks output channel.",
|
||||
"inspectEnvironment": "Confirm the Elydora hooks in the loaded environment.",
|
||||
"inspectGrokConfig": "Confirm the Elydora PreToolUse, PostToolUse, and PostToolUseFailure hooks.",
|
||||
"inspectKiroIdeHooks": "Open Agent Hooks in the Kiro panel and confirm Elydora Audit is enabled.",
|
||||
"launchKiroV2": "Launch the Elydora audit agent in Kiro CLI v2.",
|
||||
"launchKiroV3": "Start Kiro CLI v3 with the global Elydora hooks.",
|
||||
"listHooks": "List the loaded Elydora hooks.",
|
||||
"openHooks": "Open the hook manager and review the Elydora hooks.",
|
||||
"reviewAndTrustHooks": "Review and trust both Elydora command hooks.",
|
||||
"restartSession": "Restart the current {{integration}} session.",
|
||||
"startSession": "Start a new {{integration}} session.",
|
||||
"startTask": "Start a new {{integration}} task."
|
||||
},
|
||||
"sdkLanguage": "SDK language",
|
||||
"shell": "Shell",
|
||||
"secretDelivery": {
|
||||
"hidden-prompts": "The CLI requests both values with terminal echo disabled.",
|
||||
"embedded": "The command stores both credentials in owner-only files, installs the hook, then deletes them.",
|
||||
"environment": "Set ELYDORA_PRIVATE_KEY and ELYDORA_API_TOKEN in the runtime environment."
|
||||
}
|
||||
},
|
||||
|
||||
@@ -330,13 +330,15 @@
|
||||
"connect": "连接"
|
||||
},
|
||||
"integrationGroups": {
|
||||
"adapter": "CLI 适配器",
|
||||
"hooks": "原生 Hooks",
|
||||
"sdk": "自定义 SDK"
|
||||
},
|
||||
"hookBadge": "HOOKS",
|
||||
"integration": "集成",
|
||||
"copySetup": "复制安装命令",
|
||||
"copyUsage": "复制操作示例",
|
||||
"copyVerify": "复制验证命令",
|
||||
"copyPostInstall": "复制激活命令:{{command}}",
|
||||
"copyPrivateKey": "复制私钥",
|
||||
"copyApiToken": "复制 API 令牌",
|
||||
"copyFailed": "复制失败。",
|
||||
@@ -346,9 +348,24 @@
|
||||
"record": "记录操作",
|
||||
"verify": "验证",
|
||||
"postInstall": "安装后步骤",
|
||||
"postInstallSteps": {
|
||||
"inspectCursorHooks": "打开 Customize → Hooks,在 Hooks 输出通道中确认 Elydora。",
|
||||
"inspectEnvironment": "确认已加载环境包含 Elydora hooks。",
|
||||
"inspectGrokConfig": "确认 Elydora PreToolUse、PostToolUse 与 PostToolUseFailure hooks。",
|
||||
"inspectKiroIdeHooks": "打开 Kiro 面板中的 Agent Hooks,确认 Elydora Audit 已启用。",
|
||||
"launchKiroV2": "在 Kiro CLI v2 中启动 Elydora audit agent。",
|
||||
"launchKiroV3": "启动 Kiro CLI v3 并加载全局 Elydora hooks。",
|
||||
"listHooks": "列出已加载的 Elydora hooks。",
|
||||
"openHooks": "打开 hook 管理器并检查 Elydora hooks。",
|
||||
"reviewAndTrustHooks": "检查并信任两个 Elydora command hooks。",
|
||||
"restartSession": "重启当前 {{integration}} 会话。",
|
||||
"startSession": "启动新的 {{integration}} 会话。",
|
||||
"startTask": "启动新的 {{integration}} 任务。"
|
||||
},
|
||||
"sdkLanguage": "SDK 语言",
|
||||
"shell": "终端",
|
||||
"secretDelivery": {
|
||||
"hidden-prompts": "CLI 会在关闭终端回显后请求这两项凭据。",
|
||||
"embedded": "命令会把两项凭据写入仅所有者可读的文件,安装 hook 后立即删除。",
|
||||
"environment": "请在运行环境中设置 ELYDORA_PRIVATE_KEY 与 ELYDORA_API_TOKEN。"
|
||||
}
|
||||
},
|
||||
|
||||
Reference in new issue
Block a user