# Elydora Node SDK Official Node.js/TypeScript SDK for the [Elydora](https://elydora.com) tamper-evident audit platform. Build cryptographically verifiable audit trails for AI agent operations. ## Installation ```bash npm install @elydora/sdk ``` Requires Node.js 18+ (uses built-in `crypto` module with Ed25519 support). ## Quick Start ```typescript import { ElydoraClient } from '@elydora/sdk'; // Authenticate const auth = await ElydoraClient.login('https://api.elydora.com', 'user@example.com', 'password'); // Create client const client = new ElydoraClient({ orgId: auth.user.org_id, agentId: 'my-agent-id', privateKey: '', baseUrl: 'https://api.elydora.com', }); client.setToken(auth.token); // Create and submit an operation const eor = client.createOperation({ operationType: 'data.access', subject: { user_id: 'u-123', resource: 'patient-record' }, action: { type: 'read', scope: 'full' }, payload: { record_id: 'rec-456' }, }); const { receipt } = await client.submitOperation(eor); console.log('Operation submitted:', receipt.operation_id); ``` ## CLI The SDK includes a CLI for installing audit hooks into AI coding agents. ```bash npx elydora install \ --agent claudecode \ --org_id org-123 \ --agent_id agent-456 \ --kid agent-456-key-v1 ``` The CLI reads the private key and optional API token through hidden terminal prompts. For non-interactive installation, store each secret in an owner-only file and pass `--private_key_file ` and `--token_file `. Secret values are rejected as command-line arguments because process listings and shell history can expose them. Agent IDs map to one physical directory directly under `~/.elydora`; portable filename rules and physical-directory checks apply before writes or recursive removal. Ambiguous uninstall discovery requires an explicit agent ID. ### Commands | Command | Description | |---------|-------------| | `elydora install` | Install Elydora audit hook for a coding agent | | `elydora uninstall` | Remove Elydora audit hook for a coding agent | | `elydora status` | Show installation status for all agents | | `elydora agents` | List supported coding agents | ### Supported Agents | Agent | Key | |-------|-----| | Augment Code CLI | `augment` | | Claude Code | `claudecode` | | OpenAI Codex | `codex` | | Cline | `cline` | | Factory Droid | `droid` | | Kimi Code | `kimi` | | Grok Build | `grok` | | Qwen Code | `qwen` | | Cursor | `cursor` | | Gemini CLI | `gemini` | | Kiro CLI | `kirocli` | | Kiro IDE | `kiroide` | | OpenCode | `opencode` | | GitHub Copilot CLI | `copilot` | | Letta Code | `letta` | Claude Code installation writes exact matchless `PreToolUse`, `PostToolUse`, and `PostToolUseFailure` groups to `$CLAUDE_CONFIG_DIR/settings.json`, with `~/.claude/settings.json` selected when the variable is absent. Relative and empty overrides resolve from the current working directory, matching Claude Code's native path behavior. Each handler uses Claude Code's cross-platform exec form with the current Node.js executable, a single script argument, and a ten-second timeout. Managed runtimes preserve the complete native snake_case event payload and enforce frozen or revoked state through exit code `2`. Project, local, managed, plugin, skill, and agent hook sources remain unchanged and continue loading additively. User settings, generated runtimes, runtime config, and private key commit as one rollback-capable transaction. `disableAllHooks` blocks installation; managed hook policy and safe mode can suppress user hooks. Run `/hooks` and `claude doctor` after installation. Codex installation writes exact `PreToolUse` and `PostToolUse` command groups to `$CODEX_HOME/hooks.json` (`~/.codex/hooks.json` by default) and preserves the complete native event payload. A configured `CODEX_HOME` follows Codex's existing-directory canonicalization rule. User TOML, project, plugin, and managed sources remain unchanged and continue loading additively. The hook file, generated runtimes, runtime config, and private key commit as one rollback-capable update. Run `/hooks` after installation and approve both Elydora definition hashes. GitHub Copilot CLI installation writes exact `preToolUse`, `postToolUse`, and `postToolUseFailure` handlers to `$COPILOT_HOME/hooks/elydora-audit.json` (`~/.copilot/hooks/elydora-audit.json` by default). Empty home overrides select the default. Policy, repository, inline settings, cross-tool Claude, and plugin hook sources remain unchanged and continue loading additively. Elydora evaluates the effective `disableAllHooks` value across Copilot's documented settings precedence before writing. The managed hook file, exact legacy migration, generated runtimes, runtime config, and private key commit through one rollback-capable transaction. Managed commands preserve the complete native camelCase payload and propagate freeze or revocation through exit code `2`; Copilot timeouts retain their documented fail-open behavior. Restart active Copilot CLI sessions after installation. Cursor installation writes native `preToolUse`, `postToolUse`, and `postToolUseFailure` handlers to `~/.cursor/hooks.json` for both the CLI and IDE. Project and enterprise hook sources remain unchanged. The installation commits Cursor's hook file, generated runtimes, runtime config, and private key as one rollback-capable update. Managed commands use the current Node.js executable, preserve the complete Cursor JSON payload, return valid JSON, propagate freeze and revocation exit code `2`, and enable `failClosed` with a ten-second timeout. Gemini CLI installation writes named, matchless `BeforeTool` and `AfterTool` command hooks to `$GEMINI_CLI_HOME/.gemini/settings.json` (`~/.gemini/settings.json` by default). Empty home overrides select the OS home; relative and literal-tilde values retain Gemini CLI's native path behavior. JSON comments and unrelated settings remain intact. Workspace, system-default, system-override, and extension hook sources remain unchanged and continue loading additively. User settings, generated runtimes, runtime config, and private key commit through one rollback-capable transaction. Managed handlers preserve the complete native snake_case payload, emit valid JSON on successful execution, propagate freeze and revocation through exit code `2`, and use encoded PowerShell commands on Windows. `hooksConfig.enabled` and `hooksConfig.disabled` remain authoritative. Run `/hooks list` after installation to inspect effective hooks from every source. Cline installation writes `PreToolUse.mjs` and `PostToolUse.mjs` to `$CLINE_DIR/hooks` (default `~/.cline/hooks`). Documents, `.clinerules/hooks`, and workspace `.cline/hooks` remain unchanged and continue loading additively. Both hook files, generated runtimes, runtime config, and private key commit through one rollback-capable transaction. The wrappers preserve Cline 3's complete native payload, and the guard emits its documented one-line JSON cancellation control. Factory Droid 0.175.0 installation resolves one active user hook source in this order: `~/.factory/hooks.json`, the legacy `~/.factory/hooks/hooks.json`, then the `hooks` field from user `settings.local.json` and `settings.json`. Current hook files use the documented top-level `hooks` container; legacy direct event maps remain readable until Factory migrates them. Elydora writes exact `PreToolUse` and `PostToolUse` groups to that source, removes only exact Elydora ownership from inactive user sources, and keeps project, folder, plugin, and organization hook definitions read-only. System, project, folder, and user `hooksDisabled` values follow Factory's extension-only precedence; system-managed `allowManagedHooksOnly` blocks a user-hook installation. Hook sources, generated runtimes, runtime config, and private key commit through one rollback-capable transaction. Managed commands preserve the complete native snake_case payload, propagate frozen or revoked state through exit code `2`, and use Factory's native PowerShell call operator with explicit `$LASTEXITCODE` propagation on Windows. Run `/hooks` after installation to refresh Droid's session snapshot and verify remote organization policy. Kimi installation writes exact `PreToolUse`, `PostToolUse`, and `PostToolUseFailure` rules to every detected runtime: Kimi Code's `$KIMI_CODE_HOME/config.toml` (default `~/.kimi-code/config.toml`) and the legacy Python CLI's `~/.kimi/config.toml`. A fresh installation targets stable Kimi Code; an existing legacy home activates the migration contract. Empty `KIMI_CODE_HOME` values use the default. The selected TOML files, generated runtimes, runtime config, and private key commit as one rollback-capable transaction. Managed commands preserve Kimi's native snake_case event JSON, propagate freeze and revocation through exit code `2`, and use encoded PowerShell on Windows. Run `/hooks` to inspect the global rules. Grok Build installation writes native global `PreToolUse`, `PostToolUse`, and `PostToolUseFailure` hooks to `$GROK_HOME/hooks/elydora-audit.json` (default `~/.grok/hooks/elydora-audit.json`). The hook file, generated runtimes, runtime config, and private key commit as one rollback-capable transaction. Managed commands preserve Grok's native camelCase JSON, return `{"decision":"deny","reason":"..."}` with exit code `2` for frozen or revoked agents, and use encoded PowerShell on Windows. Project hooks follow Grok's `/hooks-trust` workflow; Elydora leaves project, plugin, `hooks-paths`, Claude Code, and Cursor compatibility sources unchanged. Run `grok inspect --json` to inspect the effective hook inventory. Qwen Code 0.20.0 installation writes exact, named `PreToolUse`, `PostToolUse`, and `PostToolUseFailure` command hooks to `$QWEN_HOME/settings.json` (default `~/.qwen/settings.json`). Explicit environment values lead Qwen's home bootstrap, followed by `/.env` or `~/.qwen/.env`, then `~/.env`; a discovered home contributes its own `.env`. System defaults, trusted workspace settings, system overrides, and trust rules remain read-only and participate in effective `disableAllHooks` evaluation. User settings, generated runtimes, runtime config, and private key commit through one rollback-capable transaction guarded by snapshots of every consulted source. Managed handlers use ten-second millisecond timeouts, preserve Qwen's complete native snake_case payload for success and failure events, and propagate frozen or revoked state through exit code `2`. Run `/hooks` after installation and restart active Qwen Code sessions. Auggie installation commits `~/.augment/settings.json`, both platform wrappers, generated runtimes, runtime config, and private key as one rollback-capable transaction. The managed `PreToolUse` guard propagates exit code `2`; `PostToolUse` preserves Auggie's complete native hook payload. System, workspace, local workspace, and alternate `--augment-cache-dir` settings remain unchanged. Run `auggie tools list` to validate the effective user configuration. Kiro IDE 1.0 installation is workspace-scoped. Run the installer from the workspace root; it writes `.kiro/hooks/elydora-audit.json` with exact `PreToolUse` and `PostToolUse` command hooks in Kiro's v1 JSON schema. The hook file, any exact Elydora legacy migration, generated runtimes, runtime config, and private key commit through one rollback-capable transaction. Managed handlers preserve Kiro's complete native payload and propagate frozen or revoked state through exit code `2`. Open the Agent Hooks panel after installation and confirm both Elydora entries. Kiro CLI installation commits the global v2 custom agent at `~/.kiro/agents/elydora-audit.json`, the CLI 2.13.0+ v3 global hooks at `~/.kiro/hooks/elydora-audit.json`, generated runtimes, runtime config, and private key through one rollback-capable transaction. The v2 agent uses exact lower-camel `preToolUse` and `postToolUse` handlers with ten-second millisecond timeouts. The v3 source uses Kiro's v1 schema with exact PascalCase `PreToolUse` and `PostToolUse` command hooks. Both contracts preserve the complete native event payload and propagate frozen or revoked state through exit code `2`. Validate v2 with `kiro-cli agent validate --path ~/.kiro/agents/elydora-audit.json` and start it with `kiro-cli --agent elydora-audit`; start the v3 TUI with `kiro-cli --v3`. ## API Reference ### Configuration ```typescript const client = new ElydoraClient({ orgId: string, // Organization ID agentId: string, // Agent ID privateKey: string, // Base64url-encoded Ed25519 seed baseUrl?: string, // API base URL (default: https://api.elydora.com) ttlMs?: number, // Operation TTL in ms (default: 30000) maxRetries?: number, // Max retries on transient failures (default: 3) kid?: string, // Key ID (default: {agentId}-key-v1) }); ``` ### Authentication ```typescript // Register a new user and organization (returns session token) const reg = await ElydoraClient.register(baseUrl, email, password, displayName?, orgName?); // Login and receive a session token const auth = await ElydoraClient.login(baseUrl, email, password); // Set token on client client.setToken(auth.token); // Get current authenticated user profile const { user } = await client.getMe(); // Issue a new API token (with optional TTL in seconds) const { token, expires_at } = await client.issueApiToken(3600); ``` ### Operations ```typescript // Create a signed EOR locally (synchronous, no network call) const eor = client.createOperation({ operationType: 'inference', subject: { model: 'gpt-4' }, action: { type: 'completion' }, payload: { prompt: 'Hello' }, }); // Submit to API const { receipt } = await client.submitOperation(eor); // Retrieve an operation const op = await client.getOperation(operationId); // Verify integrity (signature, chain, receipt, merkle) const result = await client.verifyOperation(operationId); ``` ### Agent Management ```typescript // Register a new agent const agent = await client.registerAgent({ agent_id: 'my-agent', integration_type: 'codex', display_name: 'My Agent', responsible_entity: 'team@example.com', keys: [{ kid: 'key-v1', public_key: '', algorithm: 'ed25519' }], }); // Get agent details const details = await client.getAgent(agentId); // Freeze an agent await client.freezeAgent(agentId, 'security review'); // Revoke a key await client.revokeKey(agentId, kid, 'key rotation'); // List all agents in the organization const { agents } = await client.listAgents(); // Unfreeze a previously frozen agent await client.unfreezeAgent(agentId, 'review complete'); // Delete an agent permanently const { deleted } = await client.deleteAgent(agentId); ``` ### Audit ```typescript const results = await client.queryAudit({ agent_id: 'agent-123', operation_type: 'inference', start_time: Date.now() - 86400000, end_time: Date.now(), limit: 50, }); ``` ### Epochs ```typescript const { epochs } = await client.listEpochs(); const { epoch } = await client.getEpoch(epochId); ``` ### Exports ```typescript const { export: exp } = await client.createExport({ start_time: Date.now() - 86400000, end_time: Date.now(), format: 'json', }); const { exports } = await client.listExports(); const { export: detail, download_url } = await client.getExport(exportId); // Download export file data const data = await client.downloadExport(exportId); ``` ### JWKS ```typescript const { keys } = await client.getJWKS(); ``` ### Health ```typescript // Check API health (no authentication required) const health = await client.health(); // health.status, health.version, health.protocol_version, health.timestamp ``` ### Client State ```typescript // Get the current chain hash (useful for debugging/inspection) const chainHash = client.getChainHash(); // Get the Ed25519 public key derived from the configured private key const publicKey = client.getPublicKey(); ``` ### Crypto Utilities The SDK exports low-level cryptographic primitives for advanced use: ```typescript import { jcsCanonicalise, // RFC 8785 JSON Canonicalization sha256Base64url, // SHA-256 hash as base64url computeChainHash, // Chain hash computation computePayloadHash, // Payload hash computation signEd25519, // Ed25519 signing derivePublicKey, // Derive public key from private seed ZERO_CHAIN_HASH, // Genesis chain hash constant } from '@elydora/sdk'; ``` ### Utility Functions ```typescript import { uuidv7, // Generate a UUIDv7 (time-ordered, RFC 9562) generateNonce, // Generate a 16-byte random nonce (base64url) base64urlEncode, // Encode Buffer/Uint8Array to base64url (no padding) base64urlDecode, // Decode base64url string to Buffer } from '@elydora/sdk'; ``` ## Error Handling ```typescript import { ElydoraError } from '@elydora/sdk'; try { await client.submitOperation(eor); } catch (err) { if (err instanceof ElydoraError) { console.error(err.code); // e.g. 'INVALID_SIGNATURE' console.error(err.message); // Human-readable message console.error(err.statusCode); // HTTP status code console.error(err.requestId); // Request ID for support } } ``` ## License MIT