diff --git a/.opencode/README.md b/.opencode/README.md new file mode 100644 index 0000000..94e4f4e --- /dev/null +++ b/.opencode/README.md @@ -0,0 +1,114 @@ +# OpenCode Configuration + +This directory contains the opencode configuration for the Polygon Agent CLI project. + +## Structure + +``` +.opencode/ +├── config.json # Main configuration file +├── README.md # This file +├── tasks/ # Reusable task definitions +│ ├── build.json +│ ├── typecheck.json +│ ├── lint.json +│ └── cli-dev.json +└── workflows/ # Workflow compositions + ├── ci.json + └── new-command.json +``` + +## Usage + +### Running Tasks + +Tasks can be executed individually: + +```bash +# Build all packages +opencode task build + +# Run type checking +opencode task typecheck + +# Run linting +opencode task lint + +# Run CLI in dev mode +opencode task cli-dev +``` + +### Running Workflows + +Workflows combine multiple tasks: + +```bash +# Run full CI pipeline +opencode workflow ci + +# Create a new command (interactive) +opencode workflow new-command +``` + +## Updating Configuration + +### Adding New Tasks + +1. Create a new JSON file in `.opencode/tasks/` +2. Follow the schema: + ```json + { + "name": "task-name", + "description": "What this task does", + "command": "pnpm run command", + "cwd": "${workspaceRoot}", + "group": "category" + } + ``` +3. Reference in workflows as needed + +### Adding New Workflows + +1. Create a new JSON file in `.opencode/workflows/` +2. Follow the schema: + ```json + { + "name": "workflow-name", + "description": "What this workflow does", + "tasks": ["task1", "task2"], + "sequential": true + } + ``` + +### Modifying AGENTS.md + +The `AGENTS.md` file at the repository root contains instructions for AI agents. Update it when: +- Project structure changes +- New commands are added +- Development workflows change +- Important conventions are established + +## Configuration Reference + +### config.json + +- `version`: Configuration version +- `project`: Project metadata +- `commands`: Shortcut commands for common operations +- `include`: Files to include in context +- `exclude`: Files to exclude from context + +### Task Schema + +- `name`: Unique task identifier +- `description`: Human-readable description +- `command`: Shell command to execute +- `cwd`: Working directory (use `${workspaceRoot}` for repo root) +- `group`: Category for organization + +### Workflow Schema + +- `name`: Unique workflow identifier +- `description`: Human-readable description +- `tasks`: Array of task names to execute +- `sequential`: Whether to run tasks in order (true) or parallel (false) diff --git a/.opencode/__tests__/config.test.js b/.opencode/__tests__/config.test.js new file mode 100644 index 0000000..0c37f65 --- /dev/null +++ b/.opencode/__tests__/config.test.js @@ -0,0 +1,150 @@ +/** + * Tests for opencode configuration files + * Run with: node .opencode/__tests__/config.test.js + */ + +import { readFileSync, readdirSync } from 'fs'; +import { join, dirname } from 'path'; +import { fileURLToPath } from 'url'; + +const __filename = fileURLToPath(import.meta.url); +const __dirname = dirname(__filename); +const OPENCODE_DIR = join(__dirname, '..'); + +// Test results +let passed = 0; +let failed = 0; + +function test(name, fn) { + try { + fn(); + console.log(`✓ ${name}`); + passed++; + } catch (error) { + console.error(`✗ ${name}`); + console.error(` ${error.message}`); + failed++; + } +} + +function assertEqual(actual, expected, message) { + if (actual !== expected) { + throw new Error(`${message}\n Expected: ${expected}\n Actual: ${actual}`); + } +} + +function assertTrue(value, message) { + if (!value) { + throw new Error(message); + } +} + +console.log('Running opencode configuration tests...\n'); + +// Test 1: Main config.json exists and is valid JSON +test('config.json exists and is valid JSON', () => { + const configPath = join(OPENCODE_DIR, 'config.json'); + const content = readFileSync(configPath, 'utf-8'); + const config = JSON.parse(content); + + assertTrue(config.version, 'config.json should have a version'); + assertTrue(config.project, 'config.json should have a project field'); + assertTrue(config.project.name, 'config.json should have a project name'); + assertTrue(config.commands, 'config.json should have commands'); +}); + +// Test 2: AGENTS.md exists +test('AGENTS.md exists at repository root', () => { + const rootDir = join(OPENCODE_DIR, '..'); + const agentsPath = join(rootDir, 'AGENTS.md'); + const content = readFileSync(agentsPath, 'utf-8'); + + assertTrue(content.length > 0, 'AGENTS.md should not be empty'); + assertTrue(content.includes('Polygon Agent CLI'), 'AGENTS.md should contain project name'); +}); + +// Test 3: All task files are valid JSON +test('all task files are valid JSON', () => { + const tasksDir = join(OPENCODE_DIR, 'tasks'); + const taskFiles = readdirSync(tasksDir).filter((f) => f.endsWith('.json')); + + assertTrue(taskFiles.length > 0, 'should have at least one task file'); + + for (const file of taskFiles) { + const content = readFileSync(join(tasksDir, file), 'utf-8'); + const task = JSON.parse(content); + + assertTrue(task.name, `Task ${file} should have a name`); + assertTrue(task.description, `Task ${file} should have a description`); + assertTrue(task.command, `Task ${file} should have a command`); + } +}); + +// Test 4: All workflow files are valid JSON +test('all workflow files are valid JSON', () => { + const workflowsDir = join(OPENCODE_DIR, 'workflows'); + const workflowFiles = readdirSync(workflowsDir).filter((f) => f.endsWith('.json')); + + assertTrue(workflowFiles.length > 0, 'should have at least one workflow file'); + + for (const file of workflowFiles) { + const content = readFileSync(join(workflowsDir, file), 'utf-8'); + const workflow = JSON.parse(content); + + assertTrue(workflow.name, `Workflow ${file} should have a name`); + assertTrue(workflow.description, `Workflow ${file} should have a description`); + + // Workflows can be either task-based or interactive (prompt-based) + const hasTasks = Array.isArray(workflow.tasks); + const hasPrompt = typeof workflow.prompt === 'string'; + assertTrue( + hasTasks || hasPrompt, + `Workflow ${file} should have either tasks array or prompt string` + ); + } +}); + +// Test 5: Tasks referenced in workflows exist +test('workflows reference existing tasks', () => { + const tasksDir = join(OPENCODE_DIR, 'tasks'); + const workflowsDir = join(OPENCODE_DIR, 'workflows'); + + const taskFiles = readdirSync(tasksDir).filter((f) => f.endsWith('.json')); + const taskNames = taskFiles.map((f) => { + const content = readFileSync(join(tasksDir, f), 'utf-8'); + return JSON.parse(content).name; + }); + + const workflowFiles = readdirSync(workflowsDir).filter((f) => f.endsWith('.json')); + + for (const file of workflowFiles) { + const content = readFileSync(join(workflowsDir, file), 'utf-8'); + const workflow = JSON.parse(content); + + if (workflow.tasks) { + for (const taskName of workflow.tasks) { + assertTrue( + taskNames.includes(taskName), + `Workflow ${file} references unknown task: ${taskName}` + ); + } + } + } +}); + +// Test 6: README.md exists +test('README.md exists in .opencode directory', () => { + const readmePath = join(OPENCODE_DIR, 'README.md'); + const content = readFileSync(readmePath, 'utf-8'); + + assertTrue(content.length > 0, 'README.md should not be empty'); + assertTrue(content.includes('OpenCode Configuration'), 'README.md should have title'); +}); + +// Summary +console.log('\n' + '='.repeat(50)); +console.log(`Tests: ${passed} passed, ${failed} failed`); + +if (failed > 0) { + process.exit(1); +} diff --git a/.opencode/config.json b/.opencode/config.json new file mode 100644 index 0000000..d046915 --- /dev/null +++ b/.opencode/config.json @@ -0,0 +1,16 @@ +{ + "version": "1.0.0", + "project": { + "name": "polygon-agent-cli", + "type": "pnpm-monorepo", + "description": "CLI tool for on-chain agent operations on Polygon" + }, + "commands": { + "build": "pnpm run build", + "test": "pnpm run lint", + "typecheck": "pnpm run typecheck", + "dev": "pnpm run polygon-agent" + }, + "include": ["AGENTS.md"], + "exclude": ["node_modules", "dist", ".git", "*.log"] +} diff --git a/.opencode/tasks/build.json b/.opencode/tasks/build.json new file mode 100644 index 0000000..e2125ec --- /dev/null +++ b/.opencode/tasks/build.json @@ -0,0 +1,7 @@ +{ + "name": "build", + "description": "Build all packages in the monorepo", + "command": "pnpm run build", + "cwd": "${workspaceRoot}", + "group": "build" +} diff --git a/.opencode/tasks/cli-dev.json b/.opencode/tasks/cli-dev.json new file mode 100644 index 0000000..9c2d6d0 --- /dev/null +++ b/.opencode/tasks/cli-dev.json @@ -0,0 +1,7 @@ +{ + "name": "cli-dev", + "description": "Run the CLI in development mode", + "command": "pnpm run polygon-agent", + "cwd": "${workspaceRoot}", + "group": "development" +} diff --git a/.opencode/tasks/lint.json b/.opencode/tasks/lint.json new file mode 100644 index 0000000..a600b61 --- /dev/null +++ b/.opencode/tasks/lint.json @@ -0,0 +1,7 @@ +{ + "name": "lint", + "description": "Run linting and formatting checks", + "command": "pnpm run lint", + "cwd": "${workspaceRoot}", + "group": "quality" +} diff --git a/.opencode/tasks/typecheck.json b/.opencode/tasks/typecheck.json new file mode 100644 index 0000000..c51335a --- /dev/null +++ b/.opencode/tasks/typecheck.json @@ -0,0 +1,7 @@ +{ + "name": "typecheck", + "description": "Run TypeScript type checking across all packages", + "command": "pnpm run typecheck", + "cwd": "${workspaceRoot}", + "group": "quality" +} diff --git a/.opencode/workflows/ci.json b/.opencode/workflows/ci.json new file mode 100644 index 0000000..7d46cab --- /dev/null +++ b/.opencode/workflows/ci.json @@ -0,0 +1,6 @@ +{ + "name": "ci", + "description": "Run full CI checks (typecheck + lint + build)", + "tasks": ["typecheck", "lint", "build"], + "sequential": true +} diff --git a/.opencode/workflows/new-command.json b/.opencode/workflows/new-command.json new file mode 100644 index 0000000..5affcab --- /dev/null +++ b/.opencode/workflows/new-command.json @@ -0,0 +1,6 @@ +{ + "name": "new-command", + "description": "Create a new CLI command following project conventions", + "prompt": "Create a new CLI command in packages/polygon-agent-cli/src/commands/ following the existing pattern. The command should:\n1. Export a yargs CommandModule with command, describe, builder, and handler\n2. Follow the existing code style (TypeScript, strict types)\n3. Include proper error handling\n4. Be registered in src/index.ts\n\nAsk the user for the command name and functionality before creating it.", + "variables": ["commandName", "commandDescription"] +} diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000..7be4b2d --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,91 @@ +# Polygon Agent CLI - Agent Instructions + +## Project Overview + +This is a **pnpm workspace monorepo** for building on-chain agents on Polygon. + +### Packages + +- **`packages/polygon-agent-cli/`** — CLI tool for on-chain agent operations + - Entry: `src/index.ts` + - Commands: `agent.ts`, `agent-legacy.ts`, `operations.ts`, `setup.ts`, `wallet.ts` + - Lib utilities: `dapp-client.ts`, `ethauth.ts`, `storage.ts`, `token-directory.ts`, `utils.ts` + - Contract ABIs: `contracts/IdentityRegistry.json`, `contracts/ReputationRegistry.json` + +- **`packages/connector-ui/`** — Wallet connector UI (Vite + React) + - Standard Vite React app with TypeScript + - Deployed via Cloudflare Workers + +## Development Standards + +### Requirements + +- **Node.js**: 24+ (see `.nvmrc`) +- **Package Manager**: pnpm 10.30.3 + +### Key Commands + +```bash +# Install dependencies +pnpm install + +# Build all packages +pnpm run build + +# Run type checking +pnpm run typecheck + +# Run linting +pnpm run lint + +# Run CLI from source +pnpm run polygon-agent +# or +node packages/polygon-agent-cli/src/index.ts +``` + +### Code Style + +- TypeScript with strict configuration +- ESLint with `@polygonlabs/apps-team-lint` config +- Prettier for formatting +- Conventional commits with commitlint + +### Architecture + +- CLI uses **yargs** with `CommandModule` builder/handler pattern +- Commands located in `src/commands/` +- Shared utilities in `src/lib/` +- Static assets (ABIs, skills) published with CLI but not source code + +## Working with This Codebase + +### Adding New Commands + +1. Create new file in `packages/polygon-agent-cli/src/commands/` +2. Export a yargs `CommandModule` with `command`, `describe`, `builder`, and `handler` +3. Import and register in `src/index.ts` + +### Adding Contract ABIs + +1. Add JSON file to `packages/polygon-agent-cli/contracts/` +2. Reference in code as needed + +### Connector UI Changes + +1. UI code in `packages/connector-ui/src/` +2. Config in `packages/connector-ui/src/config.ts` +3. Deploy via `wrangler deploy` (see `.github/workflows/deploy-connector-ui.yml`) + +## Important Notes + +- **Never commit secrets**: `.env` files, private keys, API keys +- **Build before testing**: Run `pnpm run build` after changes +- **Follow existing patterns**: Check similar commands/files before implementing new features +- **Type safety**: All code must pass `pnpm run typecheck` + +## Resources + +- Team standards: See CLAUDE.md (fetched from gist) +- Releasing: See `docs/RELEASING.md` +- CLI Skills: See `packages/polygon-agent-cli/skills/SKILL.md` diff --git a/package.json b/package.json index 025f6dc..7be8aee 100644 --- a/package.json +++ b/package.json @@ -12,6 +12,8 @@ "format:ts": "eslint . --fix", "build": "pnpm -r run build", "typecheck": "pnpm -r run typecheck", + "test": "node .opencode/__tests__/config.test.js", + "test:opencode": "node .opencode/__tests__/config.test.js", "polygon-agent": "node packages/polygon-agent-cli/src/index.ts" }, "engines": {