Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
114 changes: 114 additions & 0 deletions .opencode/README.md
Original file line number Diff line number Diff line change
@@ -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)
150 changes: 150 additions & 0 deletions .opencode/__tests__/config.test.js
Original file line number Diff line number Diff line change
@@ -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);
}
16 changes: 16 additions & 0 deletions .opencode/config.json
Original file line number Diff line number Diff line change
@@ -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"]
}
7 changes: 7 additions & 0 deletions .opencode/tasks/build.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"name": "build",
"description": "Build all packages in the monorepo",
"command": "pnpm run build",
"cwd": "${workspaceRoot}",
"group": "build"
}
7 changes: 7 additions & 0 deletions .opencode/tasks/cli-dev.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"name": "cli-dev",
"description": "Run the CLI in development mode",
"command": "pnpm run polygon-agent",
"cwd": "${workspaceRoot}",
"group": "development"
}
7 changes: 7 additions & 0 deletions .opencode/tasks/lint.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"name": "lint",
"description": "Run linting and formatting checks",
"command": "pnpm run lint",
"cwd": "${workspaceRoot}",
"group": "quality"
}
7 changes: 7 additions & 0 deletions .opencode/tasks/typecheck.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"name": "typecheck",
"description": "Run TypeScript type checking across all packages",
"command": "pnpm run typecheck",
"cwd": "${workspaceRoot}",
"group": "quality"
}
6 changes: 6 additions & 0 deletions .opencode/workflows/ci.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"name": "ci",
"description": "Run full CI checks (typecheck + lint + build)",
"tasks": ["typecheck", "lint", "build"],
"sequential": true
}
6 changes: 6 additions & 0 deletions .opencode/workflows/new-command.json
Original file line number Diff line number Diff line change
@@ -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"]
}
Loading