302 lines
15 KiB
Markdown
302 lines
15 KiB
Markdown
# 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: '<base64url-encoded-32-byte-ed25519-seed>',
|
|
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 <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.
|
|
|
|
### 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 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`.
|
|
|
|
## 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: '<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
|
|
|
|
```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
|