Files
Elydora-Open-Source/sdks/node/README.md
T

15 KiB

Elydora Node SDK

Official Node.js/TypeScript SDK for the Elydora tamper-evident audit platform. Build cryptographically verifiable audit trails for AI agent operations.

Installation

npm install @elydora/sdk

Requires Node.js 18+ (uses built-in crypto module with Ed25519 support).

Quick Start

import { ElydoraClient } from '@elydora/sdk';

// Initialize the client with your API token.
// Obtain an API token by signing in via the Elydora console or:
//   POST /api/auth/sign-in/email  →  get session token
//   POST /v1/auth/token           →  exchange for long-lived API token
const client = new ElydoraClient({
  orgId: 'org-123',
  agentId: 'my-agent-id',
  privateKey: '<base64url-encoded-32-byte-ed25519-seed>',
  baseUrl: 'https://api.elydora.com',
  token: 'your-api-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.

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 <path> and --token_file <path>. 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.

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 installation writes user hooks to $QWEN_HOME/settings.json (default ~/.qwen/settings.json). User-level .qwen/.env takes precedence over ~/.env when it defines QWEN_HOME; explicit process environment values take highest precedence. Workspace settings remain unchanged. Run /hooks to review the definitions. disableAllHooks and --safe-mode suspend hook execution.

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 CLI installation covers both runtime contracts. Kiro CLI v2 uses the generated custom agent through kiro-cli --agent elydora-audit. Kiro CLI v3 loads the global standalone hooks when started with kiro-cli --v3.

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

API Reference

Configuration

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

// Register a new user and organization
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

// 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

// 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: '<base64url>', 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

const results = await client.queryAudit({
  agent_id: 'agent-123',
  operation_type: 'inference',
  start_time: Date.now() - 86400000,
  end_time: Date.now(),
  limit: 50,
});

Epochs

const { epochs } = await client.listEpochs();
const { epoch } = await client.getEpoch(epochId);

Exports

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

const { keys } = await client.getJWKS();

Health

// Check API health (no authentication required)
const health = await client.health();
// health.status, health.version, health.protocol_version, health.timestamp

Client State

// 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:

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

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

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