Skip to content

Commit d1435f8

Browse files
author
JosXa
committed
feat: Add Warp.dev terminal support
Add support for importing from and exporting to Warp.dev WARP.md format. - Add warp format to importers and exporters - Add comprehensive Warp test coverage - Update CLI to support warp format - Add project documentation files - Update .gitignore to exclude exported AI rule files
1 parent ef66926 commit d1435f8

12 files changed

Lines changed: 751 additions & 35 deletions

File tree

.agent/guidelines.md

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
---
2+
alwaysApply: true
3+
---
4+
## Project Overview
5+
6+
dotagent is a multi-file AI agent configuration manager that maintains a single source of truth for AI coding assistant rules across multiple IDEs and tools (including VS Code Copilot, Cursor, Claude Code, OpenCode, Windsurf, and more). It converts between a unified `.agent/` directory format and tool-specific formats, supporting import/export operations, nested folders, private rules, and both CLI and TypeScript API usage.
7+
8+
## Package Manager
9+
10+
This project uses **pnpm** for dependency management.
11+
12+
## Available Scripts
13+
14+
```bash
15+
# Install dependencies
16+
pnpm install
17+
18+
# Build the project
19+
pnpm build
20+
21+
# Development mode (watch)
22+
pnpm dev
23+
24+
# Run tests (recommended for AI agents - non-interactive)
25+
pnpm test:ci
26+
27+
# Type checking
28+
pnpm typecheck
29+
```

.gitignore

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,4 +9,19 @@ coverage/
99
*.tsbuildinfo
1010
.vitest/
1111
.cursor
12-
.claude
12+
.claude
13+
# Added by dotagent: ignore exported AI rule files
14+
.github/copilot-instructions.md
15+
.cursor/rules/**
16+
.clinerules
17+
.windsurfrules
18+
.rules
19+
AGENTS.md
20+
CONVENTIONS.md
21+
CLAUDE.md
22+
GEMINI.md
23+
best_practices.md
24+
.amazonq/
25+
.junie/
26+
.roo/
27+
WARP.md

AGENTS.md

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
## Project Overview
2+
3+
dotagent is a multi-file AI agent configuration manager that maintains a single source of truth for AI coding assistant rules across multiple IDEs and tools (including VS Code Copilot, Cursor, Claude Code, OpenCode, Windsurf, and more). It converts between a unified `.agent/` directory format and tool-specific formats, supporting import/export operations, nested folders, private rules, and both CLI and TypeScript API usage.
4+
5+
## Package Manager
6+
7+
This project uses **pnpm** for dependency management.
8+
9+
## Available Scripts
10+
11+
```bash
12+
# Install dependencies
13+
pnpm install
14+
15+
# Build the project
16+
pnpm build
17+
18+
# Development mode (watch)
19+
pnpm dev
20+
21+
# Run tests (recommended for AI agents - non-interactive)
22+
pnpm test:ci
23+
24+
# Type checking
25+
pnpm typecheck
26+
```

README.md

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# dotagent
22

3-
Multi-file AI agent configuration manager with .agent directory support. Maintain a single source of truth for AI coding assistant rules across Claude Code, VS Code Copilot, Cursor, Cline, Windsurf, Zed, Amazon Q Developer, and more.
3+
Multi-file AI agent configuration manager with .agent directory support. Maintain a single source of truth for AI coding assistant rules across multiple IDEs and tools including VS Code Copilot, Cursor, Claude Code, OpenCode, and more.
44

55
## Features
66

@@ -33,6 +33,7 @@ Multi-file AI agent configuration manager with .agent directory support. Maintai
3333
| Amazon Q Developer | `.amazonq/rules/*.md` | Plain Markdown | amazonq |
3434
| JetBrains Junie | `.junie/guidelines.md` | Plain Markdown | junie |
3535
| Roo Code | `.roo/rules/*.md` | Markdown with YAML frontmatter | roo |
36+
| Warp.dev | `WARP.md` | Plain Markdown | warp |
3637

3738
## Installation
3839

@@ -103,7 +104,7 @@ dotagent convert my-rules.md -f cursor
103104
| Flag | Short | Description |
104105
|------|-------|-------------|
105106
| `--help` | `-h` | Show help message |
106-
| `--format` | `-f` | Export to single format (copilot\|cursor\|cline\|windsurf\|zed\|codex\|aider\|claude\|gemini\|qodo\|junie\|roo\|opencode) |
107+
| `--format` | `-f` | Export to single format (copilot\|cursor\|cline\|windsurf\|zed\|codex\|aider\|claude\|gemini\|qodo\|junie\|roo\|opencode\|warp) |
107108
| `--formats` | | Export to multiple formats (comma-separated list) |
108109
| `--output` | `-o` | Output directory path |
109110
| `--overwrite` | `-w` | Overwrite existing files |
@@ -223,6 +224,7 @@ Confidential requirements
223224
| Gemini | `GEMINI.md` | `GEMINI.local.md` |
224225
| Junie | `.junie/guidelines.md` | `.junie/guidelines.local.md` |
225226
| Roo Code | `.roo/rules/*.md` | `.roo/rules/*.local.md` |
227+
| Warp.dev | `WARP.md` | `WARP.local.md` |
226228

227229
### CLI Options
228230

@@ -255,6 +257,7 @@ CLAUDE.local.md
255257
GEMINI.local.md
256258
.junie/guidelines.local.md
257259
.roo/rules/*.local.md
260+
WARP.local.md
258261
```
259262

260263
## Programmatic Usage
@@ -323,6 +326,7 @@ interface RuleMetadata {
323326
- `importAmazonQ(rulesDir: string): ImportResult` - Import Amazon Q Developer rules
324327
- `importJunie(filePath: string): ImportResult` - Import JetBrains Junie guidelines
325328
- `importRoo(rulesDir: string): ImportResult` - Import Roo Code rules
329+
- `importWarp(filePath: string): ImportResult` - Import Warp.dev rules
326330

327331
### Export Functions
328332

@@ -340,6 +344,7 @@ interface RuleMetadata {
340344
- `exportToQodo(rules: RuleBlock[], outputPath: string): void`
341345
- `exportToJunie(rules: RuleBlock[], outputPath: string): void`
342346
- `exportToRoo(rules: RuleBlock[], outputDir: string): void`
347+
- `exportToWarp(rules: RuleBlock[], outputPath: string): void`
343348

344349
## Development
345350

src/cli.ts

Lines changed: 44 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
import { existsSync, readFileSync, writeFileSync, appendFileSync, rmSync } from 'fs'
44
import { join, resolve, dirname } from 'path'
55
import { parseArgs } from 'util'
6-
import { importAll, importAgent, exportToAgent, exportAll, exportToCopilot, exportToCursor, exportToCline, exportToWindsurf, exportToZed, exportToCodex, exportToAider, exportToClaudeCode, exportToGemini, exportToQodo, importRoo, exportToRoo, exportToJunie, importOpenCode, exportToOpenCode } from './index.js'
6+
import { importAll, importAgent, exportToAgent, exportAll, exportToCopilot, exportToCursor, exportToCline, exportToWindsurf, exportToZed, exportToCodex, exportToAider, exportToClaudeCode, exportToGemini, exportToQodo, importRoo, exportToRoo, exportToJunie, importOpenCode, exportToOpenCode, importWarp, exportToWarp } from './index.js'
77
import { color, header, formatList } from './utils/colors.js'
88
import { select, confirm } from './utils/prompt.js'
99

@@ -42,7 +42,7 @@ ${color.bold('Usage:')}
4242
${color.bold('Options:')}
4343
${color.yellow('-h, --help')} Show this help message
4444
${color.yellow('-o, --output')} Output file path (for convert command)
45-
${color.yellow('-f, --format')} Specify format (copilot|cursor|cline|windsurf|zed|codex|aider|claude|gemini|qodo|roo|junie|opencode)
45+
${color.yellow('-f, --format')} Specify format (copilot|cursor|cline|windsurf|zed|codex|aider|claude|gemini|qodo|roo|junie|opencode|warp)
4646
${color.yellow('--formats')} Specify multiple formats (comma-separated)
4747
${color.yellow('-w, --overwrite')} Overwrite existing files
4848
${color.yellow('-d, --dry-run')} Preview operations without making changes
@@ -108,7 +108,8 @@ async function main() {
108108
'AGENTS.md',
109109
'CLAUDE.md',
110110
'GEMINI.md',
111-
'best_practices.md'
111+
'best_practices.md',
112+
'WARP.md'
112113
]))
113114
} else {
114115
console.log(color.success(`Found ${color.number(results.length.toString())} rule file(s):`))
@@ -180,7 +181,6 @@ async function main() {
180181
}
181182

182183
const outputDir = values.output || repoPath
183-
184184
const exportFormats = [
185185
{ name: 'All formats', value: 'all' },
186186
{ name: 'VS Code Copilot (.github/copilot-instructions.md)', value: 'copilot' },
@@ -195,7 +195,8 @@ async function main() {
195195
{ name: 'Qodo Merge (best_practices.md)', value: 'qodo' },
196196
{ name: 'Roo Code (.roo/rules/)', value: 'roo' },
197197
{ name: 'JetBrains Junie (.junie/guidelines.md)', value: 'junie' },
198-
{ name: 'OpenCode (AGENTS.md)', value: 'opencode' }
198+
{ name: 'OpenCode (AGENTS.md)', value: 'opencode' },
199+
{ name: 'Warp.dev (WARP.md)', value: 'warp' }
199200
]
200201

201202
// Handle format parameter or show interactive menu
@@ -207,6 +208,9 @@ async function main() {
207208
} else if (values.format) {
208209
// Single format from -f flag
209210
selectedFormats = [values.format]
211+
} else if (isDryRun) {
212+
// In dry-run mode, default to 'all' to avoid hanging on prompt
213+
selectedFormats = ['all']
210214
} else {
211215
// Interactive menu
212216
console.log()
@@ -215,7 +219,7 @@ async function main() {
215219
}
216220

217221
// Validate formats
218-
const validFormats = ['all', 'copilot', 'cursor', 'cline', 'windsurf', 'zed', 'codex', 'aider', 'claude', 'gemini', 'qodo', 'roo', 'junie', 'opencode']
222+
const validFormats = ['all', 'copilot', 'cursor', 'cline', 'windsurf', 'zed', 'codex', 'aider', 'claude', 'gemini', 'qodo', 'roo', 'junie', 'opencode', 'warp']
219223
const invalidFormats = selectedFormats.filter(f => !validFormats.includes(f))
220224
if (invalidFormats.length > 0) {
221225
console.error(color.error(`Invalid format(s): ${invalidFormats.join(', ')}`))
@@ -235,8 +239,21 @@ async function main() {
235239
if (selectedFormat === 'all') {
236240
if (!isDryRun) {
237241
exportAll(rules, outputDir, false, options)
242+
console.log(color.success('Exported to all formats'))
243+
} else {
244+
console.log(color.info('Would export to:'))
245+
console.log(color.dim(' - .github/copilot-instructions.md'))
246+
console.log(color.dim(' - .cursor/rules/'))
247+
console.log(color.dim(' - .clinerules'))
248+
console.log(color.dim(' - .windsurfrules'))
249+
console.log(color.dim(' - .rules'))
250+
console.log(color.dim(' - AGENTS.md'))
251+
console.log(color.dim(' - CONVENTIONS.md'))
252+
console.log(color.dim(' - CLAUDE.md'))
253+
console.log(color.dim(' - GEMINI.md'))
254+
console.log(color.dim(' - best_practices.md'))
255+
console.log(color.dim(' - WARP.md'))
238256
}
239-
console.log(color.success('Exported to all formats'))
240257
exportedPaths.push(
241258
'.github/copilot-instructions.md',
242259
'.cursor/rules/',
@@ -247,7 +264,8 @@ async function main() {
247264
'CONVENTIONS.md',
248265
'CLAUDE.md',
249266
'GEMINI.md',
250-
'best_practices.md'
267+
'best_practices.md',
268+
'WARP.md'
251269
)
252270
} else {
253271
// Export to specific format
@@ -319,10 +337,19 @@ async function main() {
319337
exportPath = join(outputDir, '.junie/guidelines.md')
320338
exportedPaths.push('.junie/guidelines.md')
321339
break
340+
case 'warp':
341+
exportPath = join(outputDir, 'WARP.md')
342+
if (!isDryRun) exportToWarp(rules, exportPath, options)
343+
exportedPaths.push('WARP.md')
344+
break
322345
}
323346

324347
if (exportPath) {
325-
console.log(color.success(`Exported to: ${color.path(exportPath)}`))
348+
if (isDryRun) {
349+
console.log(color.info(`Would export to: ${color.path(exportPath)}`))
350+
} else {
351+
console.log(color.success(`Exported to: ${color.path(exportPath)}`))
352+
}
326353
}
327354
}
328355
}
@@ -387,9 +414,10 @@ async function main() {
387414
else if (inputPath.endsWith('CONVENTIONS.md')) format = 'aider'
388415
else if (inputPath.endsWith('best_practices.md')) format = 'qodo'
389416
else if (inputPath.includes('.roo/rules')) format = 'roo'
417+
else if (inputPath.endsWith('WARP.md')) format = 'warp'
390418
else {
391419
console.error(color.error('Cannot auto-detect format'))
392-
console.error(color.dim('Hint: Specify format with -f (copilot|cursor|cline|windsurf|zed|codex|aider|claude|gemini|qodo|roo|opencode)'))
420+
console.error(color.dim('Hint: Specify format with -f (copilot|cursor|cline|windsurf|zed|codex|aider|claude|gemini|qodo|roo|opencode|warp)'))
393421
process.exit(1)
394422
}
395423
}
@@ -398,7 +426,7 @@ async function main() {
398426
console.log(`Input: ${color.path(inputPath)}`)
399427

400428
// Import using appropriate importer
401-
const { importCopilot, importCursor, importCline, importWindsurf, importZed, importCodex, importAider, importClaudeCode, importGemini, importQodo } = await import('./importers.js')
429+
const { importCopilot, importCursor, importCline, importWindsurf, importZed, importCodex, importAider, importClaudeCode, importGemini, importQodo, importWarp } = await import('./importers.js')
402430

403431
let result
404432
switch (format) {
@@ -438,6 +466,9 @@ async function main() {
438466
case 'roo':
439467
result = importRoo(inputPath)
440468
break
469+
case 'warp':
470+
result = importWarp(inputPath)
471+
break
441472
default:
442473
console.error(color.error(`Unknown format: ${format}`))
443474
process.exit(1)
@@ -550,7 +581,8 @@ function updateGitignore(repoPath: string): void {
550581
'AGENTS.local.md',
551582
'CONVENTIONS.local.md',
552583
'CLAUDE.local.md',
553-
'GEMINI.local.md'
584+
'GEMINI.local.md',
585+
'WARP.local.md'
554586
].join('\n')
555587

556588
if (existsSync(gitignorePath)) {

src/exporters.ts

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -543,6 +543,28 @@ export function exportToJunie(rules: RuleBlock[], outputDir: string, options?: E
543543
writeFileSync(filePath, fullContent, 'utf-8')
544544
}
545545

546+
export function exportToWarp(rules: RuleBlock[], outputPath: string, options?: ExportOptions): void {
547+
// Filter out private rules unless includePrivate is true
548+
const filteredRules = rules.filter(rule => !rule.metadata.private || options?.includePrivate)
549+
550+
const alwaysApplyRules = filteredRules.filter(r => r.metadata.alwaysApply !== false)
551+
const conditionalSection = generateConditionalRulesSection(filteredRules, dirname(outputPath))
552+
553+
const mainContent = alwaysApplyRules
554+
.map(rule => {
555+
const header = rule.metadata.description ? `# ${rule.metadata.description}\n\n` : ''
556+
return header + rule.content
557+
})
558+
.join('\n\n')
559+
560+
const fullContent = conditionalSection
561+
? `${mainContent}\n\n${conditionalSection}`
562+
: mainContent
563+
564+
ensureDirectoryExists(outputPath)
565+
writeFileSync(outputPath, fullContent, 'utf-8')
566+
}
567+
546568
export function exportAll(rules: RuleBlock[], repoPath: string, dryRun = false, options: ExportOptions = { includePrivate: false }): void {
547569
// Export to all supported formats
548570
if (!dryRun) {
@@ -561,6 +583,7 @@ export function exportAll(rules: RuleBlock[], repoPath: string, dryRun = false,
561583
exportToAmazonQ(rules, repoPath, options)
562584
exportToRoo(rules, repoPath, options)
563585
exportToJunie(rules, repoPath, options)
586+
exportToWarp(rules, join(repoPath, 'WARP.md'), options)
564587
}
565588
}
566589

0 commit comments

Comments
 (0)