Migrate to per-agent subdirectories, uniform 8-agent support, cross-platform fixes

- All paths use ~/.elydora/{agentId}/ per-agent isolation
- Add copilot, letta plugins; split kiro into kirocli + kiroide
- Remove augment (SDK-only agent, not one-click)
- Fix chmod to skip on Windows (platform check)
- Fix uninstall to filter by specific agentId
- Uniform 8 agents: claudecode, cursor, gemini, kirocli, kiroide, opencode, copilot, letta
- Update README agent table

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
ZacharyZhang-NYandClaude Opus 4.6 committed 2026-03-01 19:58:02 -05:00
1 parent cb7a6320d5
commit 9bb819b182
13 files changed
+729 -124

No files matched your search

+39 -27
View File
@@ -8,7 +8,6 @@ const AGENT_KEY = 'opencode';
const entry = SUPPORTED_AGENTS.get(AGENT_KEY)!;
function resolveConfigDir(): string {
// .opencode/plugins is project-relative, but for global install use home dir
return path.join(os.homedir(), entry.configDir);
}
@@ -20,50 +19,54 @@ function generatePluginScript(hookScriptPath: string, guardScriptPath: string):
return `// Elydora Audit Plugin for OpenCode
// Generated by @elydora/sdk — DO NOT EDIT
const { spawn, spawnSync } = require('node:child_process');
import { spawn, spawnSync } from 'node:child_process';
module.exports = {
name: 'elydora-audit',
version: '1.0.0',
description: 'Sends tool-use events to the Elydora tamper-evident audit platform',
hooks: {
preToolUse(context) {
// Synchronous guard — blocks tool if agent is frozen
export const ElydoraAuditPlugin = async (ctx) => {
return {
"tool.execute.before": async (input, output) => {
// Guard — blocks tool if agent is frozen
try {
const result = spawnSync('node', ['__ELYDORA_GUARD_SCRIPT_PATH__'], {
timeout: 5000,
stdio: ['pipe', 'ignore', 'pipe'],
});
if (result.status !== 0) {
if (result.status === 2) {
const msg = result.stderr ? result.stderr.toString().trim() : 'Agent is frozen by Elydora.';
return { blocked: true, reason: msg };
// Signal denial by setting a blocked flag
if (output && typeof output === 'object') {
output.blocked = true;
output.reason = msg;
}
}
} catch {
// Fail-open — allow if guard can't run
}
},
postToolUse(context) {
"tool.execute.after": async (input, output) => {
try {
const input = JSON.stringify({
tool_name: context.toolName || context.tool_name || 'unknown',
tool_input: context.toolInput || context.tool_input || {},
session_id: context.sessionId || context.session_id || 'unknown',
const toolName = input?.tool || 'unknown';
const toolInput = output?.args || input?.args || {};
const sessionId = ctx?.project?.name || 'unknown';
const data = JSON.stringify({
tool_name: toolName,
tool_input: toolInput,
session_id: sessionId,
});
const child = spawn('node', ['__ELYDORA_HOOK_SCRIPT_PATH__'], {
detached: true,
stdio: ['pipe', 'ignore', 'ignore'],
});
child.stdin.write(input);
child.stdin.write(data);
child.stdin.end();
child.unref();
} catch {
// Fire-and-forget — never block the developer
}
},
},
};
};
`;
}
@@ -81,7 +84,7 @@ export const opencodePlugin: AgentPlugin = {
await fsp.writeFile(configPath, script, 'utf-8');
},
async uninstall(): Promise<void> {
async uninstall(_agentId?: string): Promise<void> {
const configPath = resolveConfigPath();
try {
await fsp.unlink(configPath);
@@ -92,22 +95,31 @@ export const opencodePlugin: AgentPlugin = {
async status(): Promise<PluginStatus> {
const configPath = resolveConfigPath();
const hookScriptPath = path.join(os.homedir(), '.elydora', 'hooks', `${AGENT_KEY}-hook.js`);
let hookConfigured = false;
let hookScriptPath = '';
try {
await fsp.access(configPath);
const raw = await fsp.readFile(configPath, 'utf-8');
hookConfigured = true;
// Extract hook script path from plugin script content
// The plugin script contains a spawn call like: spawn('node', ['<path>'])
const match = raw.match(/spawn\('node',\s*\['([^']*elydora[^']*)'\]/);
if (match) {
hookScriptPath = match[1];
}
} catch {
// File doesn't exist
}
let hookScriptExists = false;
try {
await fsp.access(hookScriptPath);
hookScriptExists = true;
} catch {
// File doesn't exist
if (hookScriptPath) {
try {
await fsp.access(hookScriptPath);
hookScriptExists = true;
} catch {
// File doesn't exist
}
}
return {