UNPKG

agent-file

Version:

Self-contained HTML agent format with built-in security

317 lines (304 loc) 11.3 kB
#!/usr/bin/env bun /** * agent.html CLI * Generate, validate, modify, and publish agent.html files */ import { AgentFile } from './agent-file'; import { generateHashes } from './security'; const VERSION = '1.0.2'; async function main() { const args = process.argv.slice(2); if (args.length === 0 || args[0] === '--help' || args[0] === '-h') { showHelp(); process.exit(0); } if (args[0] === '--version' || args[0] === '-v') { console.log(`agent.html v${VERSION}`); process.exit(0); } const command = args[0]; try { switch (command) { case 'generate': case 'gen': await generate(parseArgs(args.slice(1))); break; case 'validate': case 'val': await validate(parseArgs(args.slice(1))); break; case 'modify': case 'mod': await modify(parseArgs(args.slice(1))); break; case 'publish': case 'pub': await publish(parseArgs(args.slice(1))); break; default: console.error(`Unknown command: ${command}`); console.error('Run `agent-html --help` for usage information'); process.exit(1); } } catch (error) { console.error('Error:', error instanceof Error ? error.message : String(error)); process.exit(1); } } function showHelp() { console.log(` agent.html v${VERSION} USAGE: agent-html <command> [options] COMMANDS: generate, gen Generate a new agent.html file validate, val Validate an agent.html file modify, mod Modify an existing agent.html file publish, pub Publish an agent.html file to a registry GENERATE OPTIONS: --manifest <file> Path to manifest JSON file (required) --code <file> Path to agent code file (required) --output <file> Output path for generated HTML (default: agent.html) --ui <type> UI type: full, minimal, none (default: full) --styles <file> Path to custom styles CSS file --memory <file> Path to initial memory/state JSON file VALIDATE OPTIONS: <file> Path to agent.html file to validate (required) --verbose Show detailed validation information MODIFY OPTIONS: <file> Path to agent.html file to modify (required) --code <file> Replace agent code --memory <file> Replace memory/state --styles <file> Replace styles --output <file> Output path (default: overwrite input) PUBLISH OPTIONS: <file> Path to agent.html file to publish (required) --registry <url> Registry URL (default: official registry) EXAMPLES: # Generate an agent from manifest and code agent-html generate --manifest manifest.json --code agent.js # Generate with custom UI and styles agent-html gen --manifest manifest.json --code agent.js --ui minimal --styles custom.css # Validate an agent file agent-html validate my-agent.html # Modify agent code agent-html modify my-agent.html --code new-agent.js # Publish an agent agent-html publish my-agent.html OPTIONS: -h, --help Show this help message -v, --version Show version number `); } function parseArgs(args) { const parsed = {}; for (let i = 0; i < args.length; i++) { const arg = args[i]; if (arg.startsWith('--')) { const key = arg.slice(2); const value = args[i + 1]; if (value && !value.startsWith('--')) { parsed[key] = value; i++; } else { parsed[key] = true; } } else if (!parsed.file) { // First non-flag argument is the file parsed.file = arg; } } return parsed; } async function generate(options) { if (!options.manifest || !options.code) { console.error('Error: --manifest and --code are required'); console.error('Usage: agent-html generate --manifest <file> --code <file>'); process.exit(1); } console.log('Generating agent.html file...'); // Read manifest const manifestFile = await Bun.file(options.manifest).text(); const manifest = JSON.parse(manifestFile); // Read code const code = await Bun.file(options.code).text(); // Read optional files let styles; if (options.styles) { styles = await Bun.file(options.styles).text(); } let memory; if (options.memory) { const memoryFile = await Bun.file(options.memory).text(); memory = JSON.parse(memoryFile); } // Generate HTML const html = await AgentFile.create({ manifest, code, ui: options.ui || 'full', styles, memory }); // Write output const output = options.output || 'agent.html'; await Bun.write(output, html); console.log(`✓ Agent generated: ${output}`); console.log(` ID: ${manifest.id}`); console.log(` Name: ${manifest.name}`); console.log(` Version: ${manifest.version}`); } async function validate(options) { if (!options.file) { console.error('Error: file path is required'); console.error('Usage: agent-html validate <file>'); process.exit(1); } console.log(`Validating ${options.file}...`); // Read file const html = await Bun.file(options.file).text(); // Extract components const components = AgentFile.extract(html); // Extract integrity hashes from HTML meta tags const manifestHashMatch = html.match(/name="agent-hash-manifest"\s+content="([^"]+)"/); const codeHashMatch = html.match(/name="agent-hash-code"\s+content="([^"]+)"/); if (!manifestHashMatch || !codeHashMatch) { console.error('✗ Validation failed: Missing integrity hashes'); process.exit(1); } const storedHashes = { manifest: manifestHashMatch[1], code: codeHashMatch[1] }; // Generate hashes from extracted components const manifestJSON = JSON.stringify(components.manifest, null, 2); const computedHashes = await generateHashes(manifestJSON, components.code); // Verify integrity const isValid = storedHashes.manifest === computedHashes.manifest && storedHashes.code === computedHashes.code; if (isValid) { console.log('✓ Validation passed'); if (options.verbose) { console.log('\nManifest:'); console.log(` ID: ${components.manifest.id}`); console.log(` Name: ${components.manifest.name}`); console.log(` Version: ${components.manifest.version}`); console.log('\nPermissions:'); const perms = components.manifest.permissions; if (perms) { console.log(` Network: ${perms.network?.join(', ') || 'none'}`); console.log(` Storage: ${perms.storage || false}`); console.log(` Code: ${perms.code || false}`); } console.log('\nIntegrity:'); console.log(` Manifest Hash: ${storedHashes.manifest.slice(0, 16)}...`); console.log(` Code Hash: ${storedHashes.code.slice(0, 16)}...`); if (components.memory) { console.log('\nMemory: Present'); } } } else { console.error('✗ Validation failed: Integrity check failed'); console.error('The agent file may have been tampered with'); process.exit(1); } } async function modify(options) { if (!options.file) { console.error('Error: file path is required'); console.error('Usage: agent-html modify <file> [options]'); process.exit(1); } console.log(`Modifying ${options.file}...`); // Read original file const html = await Bun.file(options.file).text(); const components = AgentFile.extract(html); // Read new code if provided let code = components.code; if (options.code) { code = await Bun.file(options.code).text(); console.log(` Updated code from ${options.code}`); } // Read new memory if provided let memory = components.memory; if (options.memory) { const memoryFile = await Bun.file(options.memory).text(); memory = JSON.parse(memoryFile); console.log(` Updated memory from ${options.memory}`); } // Read new styles if provided let styles; if (options.styles) { styles = await Bun.file(options.styles).text(); console.log(` Updated styles from ${options.styles}`); } // Determine UI mode from original HTML let ui = 'full'; if (html.includes('id="agent-ui-minimal"')) { ui = 'minimal'; } else if (!html.includes('id="agent-ui"')) { ui = 'none'; } // Generate new HTML with updated components const newHtml = await AgentFile.create({ manifest: components.manifest, code, memory, styles, ui }); // Write output const output = options.output || options.file; await Bun.write(output, newHtml); console.log(`✓ Agent modified: ${output}`); } async function publish(options) { if (!options.file) { console.error('Error: file path is required'); console.error('Usage: agent-html publish <file>'); process.exit(1); } console.log(`Publishing ${options.file}...`); // Read and validate file first const html = await Bun.file(options.file).text(); const components = AgentFile.extract(html); // Extract integrity hashes from HTML meta tags const manifestHashMatch = html.match(/name="agent-hash-manifest"\s+content="([^"]+)"/); const codeHashMatch = html.match(/name="agent-hash-code"\s+content="([^"]+)"/); if (!manifestHashMatch || !codeHashMatch) { console.error('✗ Cannot publish: Missing integrity hashes'); process.exit(1); } const storedHashes = { manifest: manifestHashMatch[1], code: codeHashMatch[1] }; // Verify integrity const manifestJSON = JSON.stringify(components.manifest, null, 2); const computedHashes = await generateHashes(manifestJSON, components.code); const isValid = storedHashes.manifest === computedHashes.manifest && storedHashes.code === computedHashes.code; if (!isValid) { console.error('✗ Cannot publish: Agent file failed validation'); process.exit(1); } console.log('✓ Agent validated'); console.log(` ID: ${components.manifest.id}`); console.log(` Name: ${components.manifest.name}`); console.log(` Version: ${components.manifest.version}`); // Stub - to be implemented const registry = options.registry || 'https://agents.example.com'; console.log('\n⚠ Publishing is not yet implemented'); console.log(` Would publish to: ${registry}`); console.log(` Agent size: ${new Blob([html]).size} bytes`); console.log('\nTo implement publishing:'); console.log(' 1. Set up an agent registry server'); console.log(' 2. Configure authentication'); console.log(' 3. Implement the publish API endpoint'); } main();