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:
雷电芽衣 committed 2026-09-02 09:56:40 -04:00
1 parent 1bb9d4d49d
commit edf9d73c3e
21 files changed
+967 -163

No files matched your search

@@ -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),
);