dotagent
Version:
Multi-file AI agent configuration manager with .agent directory support
1,407 lines (1,375 loc) • 64.1 kB
JavaScript
#!/usr/bin/env node
var __defProp = Object.defineProperty;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __esm = (fn, res) => function __init() {
return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
};
var __export = (target, all) => {
for (var name in all)
__defProp(target, name, { get: all[name], enumerable: true });
};
// node_modules/.pnpm/tsup@8.5.0_postcss@8.5.6_typescript@5.8.3/node_modules/tsup/assets/esm_shims.js
import path from "path";
import { fileURLToPath } from "url";
var init_esm_shims = __esm({
"node_modules/.pnpm/tsup@8.5.0_postcss@8.5.6_typescript@5.8.3/node_modules/tsup/assets/esm_shims.js"() {
"use strict";
}
});
// src/yaml-parser.ts
import yaml2 from "js-yaml";
function createSafeYamlParser() {
return {
parse: (str) => {
const processedStr = str.replace(
/^(\s*\w+:\s*)(\*[^\n\r"']*?)(\s*(?:\r?\n|$))/gm,
(match, prefix, value, suffix) => {
if (value.startsWith('"') || value.startsWith("'")) {
return match;
}
return `${prefix}"${value}"${suffix}`;
}
);
const fullyProcessedStr = processedStr.replace(
/^(\s*-\s+)(\*[^\n\r"']*?)(\s*(?:\r?\n|$))/gm,
(match, prefix, value, suffix) => {
if (value.startsWith('"') || value.startsWith("'")) {
return match;
}
return `${prefix}"${value}"${suffix}`;
}
);
try {
return yaml2.load(fullyProcessedStr);
} catch (error) {
return yaml2.load(str);
}
},
stringify: (data) => {
const yamlOutput = yaml2.dump(data);
const lines = yamlOutput.split(/\r?\n/);
const out = [];
let inGlobsArray = false;
let globsIndent = "";
const containsGlob = (s) => s.includes("*");
for (let i = 0; i < lines.length; i++) {
let line = lines[i];
const globsMatch = line.match(/^(\s*)globs:\s*(.*)$/);
if (globsMatch) {
globsIndent = globsMatch[1];
const value = globsMatch[2];
if (value === "") {
inGlobsArray = true;
out.push(line);
continue;
}
const scalar = value.match(/^(['"])(.+)\1(\s*(?:#.*)?)$/);
if (scalar && containsGlob(scalar[2])) {
line = `${globsIndent}globs: ${scalar[2]}${scalar[3] ?? ""}`;
}
out.push(line);
continue;
}
if (inGlobsArray) {
if (!line.startsWith(globsIndent + " ")) {
inGlobsArray = false;
i--;
continue;
}
const item = line.match(/^(\s*-\s*)(['"])(.+)\2(\s*(?:#.*)?)$/);
if (item && containsGlob(item[3])) {
line = `${item[1]}${item[3]}${item[4] ?? ""}`;
}
out.push(line);
continue;
}
out.push(line);
}
return out.join("\n");
}
};
}
var grayMatterOptions;
var init_yaml_parser = __esm({
"src/yaml-parser.ts"() {
"use strict";
init_esm_shims();
grayMatterOptions = {
engines: {
yaml: createSafeYamlParser()
}
};
}
});
// src/importers.ts
var importers_exports = {};
__export(importers_exports, {
importAgent: () => importAgent,
importAider: () => importAider,
importAll: () => importAll,
importAmazonQ: () => importAmazonQ,
importClaudeCode: () => importClaudeCode,
importCline: () => importCline,
importCodex: () => importCodex,
importCopilot: () => importCopilot,
importCursor: () => importCursor,
importCursorLegacy: () => importCursorLegacy,
importGemini: () => importGemini,
importJunie: () => importJunie,
importOpenCode: () => importOpenCode,
importQodo: () => importQodo,
importRoo: () => importRoo,
importWindsurf: () => importWindsurf,
importZed: () => importZed
});
import { readFileSync, existsSync, readdirSync, statSync } from "fs";
import { join, basename } from "path";
import matter from "gray-matter";
function isPrivateRule(filePath) {
const lowerPath = filePath.toLowerCase();
return lowerPath.includes(".local.") || lowerPath.includes("/private/") || lowerPath.includes("\\private\\");
}
async function importAll(repoPath) {
const results = [];
const errors = [];
const agentDir = join(repoPath, ".agent");
if (existsSync(agentDir)) {
try {
results.push(importAgent(agentDir));
} catch (e) {
errors.push({ file: agentDir, error: String(e) });
}
}
const copilotPath = join(repoPath, ".github", "copilot-instructions.md");
if (existsSync(copilotPath)) {
try {
results.push(importCopilot(copilotPath));
} catch (e) {
errors.push({ file: copilotPath, error: String(e) });
}
}
const copilotLocalPath = join(repoPath, ".github", "copilot-instructions.local.md");
if (existsSync(copilotLocalPath)) {
try {
results.push(importCopilot(copilotLocalPath));
} catch (e) {
errors.push({ file: copilotLocalPath, error: String(e) });
}
}
const cursorDir = join(repoPath, ".cursor");
if (existsSync(cursorDir)) {
try {
results.push(importCursor(cursorDir));
} catch (e) {
errors.push({ file: cursorDir, error: String(e) });
}
}
const cursorRulesFile = join(repoPath, ".cursorrules");
if (existsSync(cursorRulesFile)) {
try {
results.push(importCursorLegacy(cursorRulesFile));
} catch (e) {
errors.push({ file: cursorRulesFile, error: String(e) });
}
}
const clinerules = join(repoPath, ".clinerules");
if (existsSync(clinerules)) {
try {
results.push(importCline(clinerules));
} catch (e) {
errors.push({ file: clinerules, error: String(e) });
}
}
const clinerulesLocal = join(repoPath, ".clinerules.local");
if (existsSync(clinerulesLocal)) {
try {
results.push(importCline(clinerulesLocal));
} catch (e) {
errors.push({ file: clinerulesLocal, error: String(e) });
}
}
const windsurfRules = join(repoPath, ".windsurfrules");
if (existsSync(windsurfRules)) {
try {
results.push(importWindsurf(windsurfRules));
} catch (e) {
errors.push({ file: windsurfRules, error: String(e) });
}
}
const windsurfRulesLocal = join(repoPath, ".windsurfrules.local");
if (existsSync(windsurfRulesLocal)) {
try {
results.push(importWindsurf(windsurfRulesLocal));
} catch (e) {
errors.push({ file: windsurfRulesLocal, error: String(e) });
}
}
const zedRules = join(repoPath, ".rules");
if (existsSync(zedRules)) {
try {
results.push(importZed(zedRules));
} catch (e) {
errors.push({ file: zedRules, error: String(e) });
}
}
const zedRulesLocal = join(repoPath, ".rules.local");
if (existsSync(zedRulesLocal)) {
try {
results.push(importZed(zedRulesLocal));
} catch (e) {
errors.push({ file: zedRulesLocal, error: String(e) });
}
}
const agentsMd = join(repoPath, "AGENTS.md");
if (existsSync(agentsMd)) {
try {
results.push(importCodex(agentsMd));
} catch (e) {
errors.push({ file: agentsMd, error: String(e) });
}
}
const agentsLocalMd = join(repoPath, "AGENTS.local.md");
if (existsSync(agentsLocalMd)) {
try {
results.push(importCodex(agentsLocalMd));
} catch (e) {
errors.push({ file: agentsLocalMd, error: String(e) });
}
}
const claudeMd = join(repoPath, "CLAUDE.md");
if (existsSync(claudeMd)) {
try {
results.push(importClaudeCode(claudeMd));
} catch (e) {
errors.push({ file: claudeMd, error: String(e) });
}
}
const opencodeMd = join(repoPath, "AGENTS.md");
if (existsSync(opencodeMd)) {
try {
results.push(importOpenCode(opencodeMd));
} catch (e) {
errors.push({ file: opencodeMd, error: String(e) });
}
}
const geminiMd = join(repoPath, "GEMINI.md");
if (existsSync(geminiMd)) {
try {
results.push(importGemini(geminiMd));
} catch (e) {
errors.push({ file: geminiMd, error: String(e) });
}
}
const bestPracticesMd = join(repoPath, "best_practices.md");
if (existsSync(bestPracticesMd)) {
try {
results.push(importQodo(bestPracticesMd));
} catch (e) {
errors.push({ file: bestPracticesMd, error: String(e) });
}
}
const claudeLocalMd = join(repoPath, "CLAUDE.local.md");
if (existsSync(claudeLocalMd)) {
try {
results.push(importClaudeCode(claudeLocalMd));
} catch (e) {
errors.push({ file: claudeLocalMd, error: String(e) });
}
}
const geminiLocalMd = join(repoPath, "GEMINI.local.md");
if (existsSync(geminiLocalMd)) {
try {
results.push(importGemini(geminiLocalMd));
} catch (e) {
errors.push({ file: geminiLocalMd, error: String(e) });
}
}
const conventionsMd = join(repoPath, "CONVENTIONS.md");
if (existsSync(conventionsMd)) {
try {
results.push(importAider(conventionsMd));
} catch (e) {
errors.push({ file: conventionsMd, error: String(e) });
}
}
const conventionsLocalMd = join(repoPath, "CONVENTIONS.local.md");
if (existsSync(conventionsLocalMd)) {
try {
results.push(importAider(conventionsLocalMd));
} catch (e) {
errors.push({ file: conventionsLocalMd, error: String(e) });
}
}
const amazonqRulesDir = join(repoPath, ".amazonq", "rules");
if (existsSync(amazonqRulesDir)) {
try {
results.push(importAmazonQ(amazonqRulesDir));
} catch (e) {
errors.push({ file: amazonqRulesDir, error: String(e) });
}
}
const rooRulesDir = join(repoPath, ".roo", "rules");
if (existsSync(rooRulesDir)) {
try {
results.push(importRoo(rooRulesDir));
} catch (e) {
errors.push({ file: rooRulesDir, error: String(e) });
}
}
const junieGuidelines = join(repoPath, ".junie", "guidelines.md");
if (existsSync(junieGuidelines)) {
try {
results.push(importJunie(junieGuidelines));
} catch (e) {
errors.push({ file: junieGuidelines, error: String(e) });
}
}
return { results, errors };
}
function importCopilot(filePath) {
const content = readFileSync(filePath, "utf-8");
const isPrivate = isPrivateRule(filePath);
const metadata = {
id: "copilot-instructions",
alwaysApply: true,
description: "GitHub Copilot custom instructions"
};
if (isPrivate) {
metadata.private = true;
}
const rules = [{
metadata,
content: content.trim()
}];
return {
format: "copilot",
filePath,
rules,
raw: content
};
}
function importAgent(agentDir) {
const rules = [];
function findMarkdownFiles(dir, relativePath = "") {
const entries = readdirSync(dir, { withFileTypes: true });
entries.sort((a, b) => {
if (a.isDirectory() && !b.isDirectory()) return -1;
if (!a.isDirectory() && b.isDirectory()) return 1;
return a.name.localeCompare(b.name);
});
for (const entry of entries) {
const fullPath = join(dir, entry.name);
const relPath = relativePath ? join(relativePath, entry.name) : entry.name;
if (entry.isDirectory()) {
findMarkdownFiles(fullPath, relPath);
} else if (entry.isFile() && entry.name.endsWith(".md")) {
const content = readFileSync(fullPath, "utf-8");
const { data, content: body } = matter(content, grayMatterOptions);
let segments = relPath.replace(/\.md$/, "").replace(/\\/g, "/").split("/").map((s) => s.replace(/^\d{2,}-/, "").replace(/\.local$/, ""));
if (segments[0] === "private") segments = segments.slice(1);
const defaultId = segments.join("/");
const isPrivateFile = isPrivateRule(fullPath);
const metadata = {
id: data.id || defaultId,
...data
};
if (metadata.alwaysApply === void 0) {
metadata.alwaysApply = false;
}
if (data.private === true || data.private === void 0 && isPrivateFile) {
metadata.private = true;
}
rules.push({
metadata,
content: body.trim()
});
}
}
}
findMarkdownFiles(agentDir);
return {
format: "agent",
filePath: agentDir,
rules
};
}
function importCursor(cursorDir) {
const rules = [];
function findCursorFiles(dir, relativePath = "") {
const entries = readdirSync(dir, { withFileTypes: true });
entries.sort((a, b) => {
if (a.isDirectory() && !b.isDirectory()) return -1;
if (!a.isDirectory() && b.isDirectory()) return 1;
return a.name.localeCompare(b.name);
});
for (const entry of entries) {
const fullPath = join(dir, entry.name);
const relPath = relativePath ? join(relativePath, entry.name) : entry.name;
if (entry.isDirectory()) {
findCursorFiles(fullPath, relPath);
} else if (entry.isFile() && (entry.name.endsWith(".mdc") || entry.name.endsWith(".md"))) {
const content = readFileSync(fullPath, "utf-8");
const { data, content: body } = matter(content, grayMatterOptions);
let segments = relPath.replace(/\.(mdc|md)$/, "").replace(/\\/g, "/").split("/").map((s) => s.replace(/^\d{2,}-/, "").replace(/\.local$/, ""));
if (segments[0] === "private") segments = segments.slice(1);
if (segments[0] === "rules" && segments.length === 2) segments = segments.slice(1);
const defaultId = segments.join("/");
const isPrivateFile = isPrivateRule(fullPath);
const metadata = {
id: data.id || defaultId,
...data
};
if (metadata.alwaysApply === void 0) {
metadata.alwaysApply = false;
}
if (data.private === true || data.private === void 0 && isPrivateFile) {
metadata.private = true;
}
rules.push({
metadata,
content: body.trim()
});
}
}
}
findCursorFiles(cursorDir);
return {
format: "cursor",
filePath: cursorDir,
rules
};
}
function importCursorLegacy(filePath) {
const content = readFileSync(filePath, "utf-8");
const rules = [{
metadata: {
id: "cursor-rules-legacy",
alwaysApply: true,
description: "Legacy Cursor rules"
},
content: content.trim()
}];
return {
format: "cursor",
filePath,
rules,
raw: content
};
}
function importCline(rulesPath) {
const rules = [];
if (existsSync(rulesPath) && statSync(rulesPath).isDirectory()) {
let findMdFiles2 = function(dir, relativePath = "") {
const entries = readdirSync(dir, { withFileTypes: true });
entries.sort((a, b) => {
if (a.isDirectory() && !b.isDirectory()) return -1;
if (!a.isDirectory() && b.isDirectory()) return 1;
return a.name.localeCompare(b.name);
});
for (const entry of entries) {
const fullPath = join(dir, entry.name);
const relPath = relativePath ? join(relativePath, entry.name) : entry.name;
if (entry.isDirectory()) {
findMdFiles2(fullPath, relPath);
} else if (entry.isFile() && entry.name.endsWith(".md")) {
const content = readFileSync(fullPath, "utf-8");
const isPrivateFile = isPrivateRule(fullPath);
let segments = relPath.replace(/\.md$/, "").replace(/\\/g, "/").split("/").map((s) => s.replace(/^\d{2,}-/, "").replace(/\.local$/, ""));
if (segments[0] === "private") segments = segments.slice(1);
const defaultId = segments.join("/");
const metadata = {
id: defaultId,
alwaysApply: true,
description: `Cline rules from ${relPath}`
};
if (isPrivateFile) {
metadata.private = true;
}
rules.push({
metadata,
content: content.trim()
});
}
}
};
var findMdFiles = findMdFiles2;
findMdFiles2(rulesPath);
} else {
const content = readFileSync(rulesPath, "utf-8");
const isPrivateFile = isPrivateRule(rulesPath);
const metadata = {
id: "cline-rules",
alwaysApply: true,
description: "Cline project rules"
};
if (isPrivateFile) {
metadata.private = true;
}
rules.push({
metadata,
content: content.trim()
});
}
return {
format: "cline",
filePath: rulesPath,
rules
};
}
function importWindsurf(filePath) {
const content = readFileSync(filePath, "utf-8");
const isPrivateFile = isPrivateRule(filePath);
const metadata = {
id: "windsurf-rules",
alwaysApply: true,
description: "Windsurf AI rules"
};
if (isPrivateFile) {
metadata.private = true;
}
const rules = [{
metadata,
content: content.trim()
}];
return {
format: "windsurf",
filePath,
rules,
raw: content
};
}
function importZed(filePath) {
const content = readFileSync(filePath, "utf-8");
const isPrivateFile = isPrivateRule(filePath);
const metadata = {
id: "zed-rules",
alwaysApply: true,
description: "Zed editor rules"
};
if (isPrivateFile) {
metadata.private = true;
}
const rules = [{
metadata,
content: content.trim()
}];
return {
format: "zed",
filePath,
rules,
raw: content
};
}
function importCodex(filePath) {
const content = readFileSync(filePath, "utf-8");
const format = basename(filePath) === "AGENTS.md" || basename(filePath) === "AGENTS.local.md" ? "codex" : "unknown";
const isPrivateFile = isPrivateRule(filePath);
const metadata = {
id: format === "codex" ? "codex-agents" : "claude-rules",
alwaysApply: true,
description: format === "codex" ? "OpenAI Codex agent instructions" : "Claude AI instructions"
};
if (isPrivateFile) {
metadata.private = true;
}
const rules = [{
metadata,
content: content.trim()
}];
return {
format,
filePath,
rules,
raw: content
};
}
function importAider(filePath) {
const content = readFileSync(filePath, "utf-8");
const isPrivateFile = isPrivateRule(filePath);
const metadata = {
id: "aider-conventions",
alwaysApply: true,
description: "Aider CLI conventions"
};
if (isPrivateFile) {
metadata.private = true;
}
const rules = [{
metadata,
content: content.trim()
}];
return {
format: "aider",
filePath,
rules,
raw: content
};
}
function importClaudeCode(filePath) {
const content = readFileSync(filePath, "utf-8");
const isPrivateFile = isPrivateRule(filePath);
const metadata = {
id: "claude-code-instructions",
alwaysApply: true,
description: "Claude Code context and instructions"
};
if (isPrivateFile) {
metadata.private = true;
}
const rules = [{
metadata,
content: content.trim()
}];
return {
format: "claude",
filePath,
rules,
raw: content
};
}
function importOpenCode(filePath) {
const content = readFileSync(filePath, "utf-8");
const isPrivateFile = isPrivateRule(filePath);
const metadata = {
id: "opencode-agents",
alwaysApply: true,
description: "OpenCode agents and instructions"
};
if (isPrivateFile) {
metadata.private = true;
}
const rules = [{
metadata,
content: content.trim()
}];
return {
format: "opencode",
filePath,
rules,
raw: content
};
}
function importGemini(filePath) {
const content = readFileSync(filePath, "utf-8");
const isPrivateFile = isPrivateRule(filePath);
const metadata = {
id: "gemini-instructions",
alwaysApply: true,
description: "Gemini CLI context and instructions"
};
if (isPrivateFile) {
metadata.private = true;
}
const rules = [{
metadata,
content: content.trim()
}];
return {
format: "gemini",
filePath,
rules,
raw: content
};
}
function importQodo(filePath) {
const content = readFileSync(filePath, "utf-8");
const rules = [{
metadata: {
id: "qodo-best-practices",
alwaysApply: true,
description: "Qodo best practices and coding standards",
scope: "**/*",
priority: "high"
},
content: content.trim()
}];
return {
format: "qodo",
filePath,
rules,
raw: content
};
}
function importAmazonQ(rulesDir) {
const rules = [];
function findMdFiles(dir, relativePath = "") {
const entries = readdirSync(dir, { withFileTypes: true });
entries.sort((a, b) => {
if (a.isDirectory() && !b.isDirectory()) return -1;
if (!a.isDirectory() && b.isDirectory()) return 1;
return a.name.localeCompare(b.name);
});
for (const entry of entries) {
const fullPath = join(dir, entry.name);
const relPath = relativePath ? join(relativePath, entry.name) : entry.name;
if (entry.isDirectory()) {
findMdFiles(fullPath, relPath);
} else if (entry.isFile() && entry.name.endsWith(".md")) {
const content = readFileSync(fullPath, "utf-8");
const isPrivateFile = isPrivateRule(fullPath);
let segments = relPath.replace(/\.md$/, "").replace(/\\/g, "/").split("/").map((s) => s.replace(/^\d{2,}-/, "").replace(/\.local$/, ""));
if (segments[0] === "private") segments = segments.slice(1);
const defaultId = segments.join("/");
const metadata = {
id: `amazonq-${defaultId}`,
alwaysApply: true,
description: `Amazon Q rules from ${relPath}`
};
if (isPrivateFile) {
metadata.private = true;
}
rules.push({
metadata,
content: content.trim()
});
}
}
}
findMdFiles(rulesDir);
return {
format: "amazonq",
filePath: rulesDir,
rules
};
}
function importRoo(rulesDir) {
const rules = [];
function findMdFiles(dir, relativePath = "") {
const entries = readdirSync(dir, { withFileTypes: true });
entries.sort((a, b) => {
if (a.isDirectory() && !b.isDirectory()) return -1;
if (!a.isDirectory() && b.isDirectory()) return 1;
return a.name.localeCompare(b.name);
});
for (const entry of entries) {
const fullPath = join(dir, entry.name);
const relPath = relativePath ? join(relativePath, entry.name) : entry.name;
if (entry.isDirectory()) {
findMdFiles(fullPath, relPath);
} else if (entry.isFile() && entry.name.endsWith(".md")) {
const content = readFileSync(fullPath, "utf-8");
const { data, content: body } = matter(content, grayMatterOptions);
let segments = relPath.replace(/\.md$/, "").replace(/\\/g, "/").split("/").map((s) => s.replace(/^\d{2,}-/, "").replace(/\.local$/, ""));
if (segments[0] === "private") segments = segments.slice(1);
const defaultId = segments.join("/");
const isPrivateFile = isPrivateRule(fullPath);
const metadata = {
id: data.id || defaultId,
...data
};
if (metadata.alwaysApply === void 0) {
metadata.alwaysApply = false;
}
if (data.private === true || data.private === void 0 && isPrivateFile) {
metadata.private = true;
}
rules.push({
metadata,
content: body.trim()
});
}
}
}
findMdFiles(rulesDir);
return {
format: "roo",
filePath: rulesDir,
rules
};
}
function importJunie(filePath) {
const content = readFileSync(filePath, "utf-8");
const isPrivateFile = isPrivateRule(filePath);
const metadata = {
id: "junie-guidelines",
alwaysApply: true,
description: "JetBrains Junie guidelines and instructions"
};
if (isPrivateFile) {
metadata.private = true;
}
const rules = [{
metadata,
content: content.trim()
}];
return {
format: "junie",
filePath,
rules,
raw: content
};
}
var init_importers = __esm({
"src/importers.ts"() {
"use strict";
init_esm_shims();
init_yaml_parser();
}
});
// src/cli.ts
init_esm_shims();
import { existsSync as existsSync3, readFileSync as readFileSync2, writeFileSync as writeFileSync2, appendFileSync, rmSync } from "fs";
import { join as join3, resolve, dirname as dirname2 } from "path";
import { parseArgs } from "util";
// src/index.ts
init_esm_shims();
// src/parser.ts
init_esm_shims();
import { unified } from "unified";
import remarkParse from "remark-parse";
import { toMarkdown } from "mdast-util-to-markdown";
import yaml from "js-yaml";
// src/index.ts
init_importers();
// src/exporters.ts
init_esm_shims();
init_yaml_parser();
import { writeFileSync, mkdirSync, existsSync as existsSync2 } from "fs";
import { join as join2, dirname } from "path";
import yaml3 from "js-yaml";
import matter2 from "gray-matter";
function generateConditionalRulesSection(rules, repoPath) {
const sections = [];
const alwaysApplyRules = rules.filter((r) => r.metadata.alwaysApply !== false);
const conditionalRules = rules.filter((r) => r.metadata.alwaysApply === false);
if (conditionalRules.length === 0) {
return "";
}
const rulesByFolder = {};
const rulesWithScope = [];
const rulesWithDescription = [];
conditionalRules.forEach((rule) => {
if (rule.metadata.id && rule.metadata.id.includes("/")) {
const folder = rule.metadata.id.split("/")[0];
if (!rulesByFolder[folder]) {
rulesByFolder[folder] = [];
}
rulesByFolder[folder].push(rule);
}
if (rule.metadata.scope) {
rulesWithScope.push(rule);
} else if (rule.metadata.description && !rule.metadata.scope && !rule.metadata.id?.includes("/")) {
rulesWithDescription.push(rule);
}
});
sections.push("## Context-Specific Rules");
sections.push("");
if (rulesWithScope.length > 0) {
rulesWithScope.forEach((rule) => {
const scopes = Array.isArray(rule.metadata.scope) ? rule.metadata.scope : [rule.metadata.scope];
scopes.forEach((scope) => {
const rulePath = `.agent/${rule.metadata.id}.md`;
const description = rule.metadata.description ? ` - ${rule.metadata.description}` : "";
sections.push(`When working with files matching \`${scope}\`, also apply:`);
sections.push(`\u2192 [${rule.metadata.id}](${rulePath})${description}`);
sections.push("");
});
});
}
if (rulesWithDescription.length > 0) {
rulesWithDescription.forEach((rule) => {
const rulePath = `.agent/${rule.metadata.id}.md`;
sections.push(`When working with ${rule.metadata.description}, also apply:`);
sections.push(`\u2192 [${rule.metadata.id}](${rulePath})`);
sections.push("");
});
}
Object.entries(rulesByFolder).forEach(([folder, folderRules]) => {
const unhandledRules = folderRules.filter(
(r) => !rulesWithScope.includes(r) && !rulesWithDescription.includes(r)
);
if (unhandledRules.length > 0) {
const sectionTitle = folder.charAt(0).toUpperCase() + folder.slice(1);
sections.push(`## ${sectionTitle}`);
sections.push("");
unhandledRules.forEach((rule) => {
const rulePath = `.agent/${rule.metadata.id}.md`;
const description = rule.metadata.description ? ` - ${rule.metadata.description}` : "";
sections.push(`\u2192 [${rule.metadata.id}](${rulePath})${description}`);
});
sections.push("");
}
});
return sections.join("\n");
}
function exportToCopilot(rules, outputPath, options) {
const filteredRules = rules.filter((rule) => !rule.metadata.private || options?.includePrivate);
const alwaysApplyRules = filteredRules.filter((r) => r.metadata.alwaysApply !== false);
const conditionalSection = generateConditionalRulesSection(filteredRules, dirname(outputPath));
const mainContent = alwaysApplyRules.map((rule) => rule.content).join("\n\n---\n\n");
const fullContent = conditionalSection ? `${mainContent}
---
${conditionalSection}` : mainContent;
ensureDirectoryExists(outputPath);
writeFileSync(outputPath, fullContent, "utf-8");
}
function exportToAgent(rules, outputDir, options) {
const agentDir = join2(outputDir, ".agent");
mkdirSync(agentDir, { recursive: true });
let topIndex = 1;
rules.forEach((rule) => {
let filename;
let filePath;
if (rule.metadata.id && rule.metadata.id.includes("/")) {
const parts = rule.metadata.id.split("/");
const fileName = parts.pop() + ".md";
const subDir = join2(agentDir, ...parts);
mkdirSync(subDir, { recursive: true });
filePath = join2(subDir, fileName);
} else {
if (rule.metadata.private) {
const prefix = String(topIndex).padStart(3, "0") + "-";
topIndex++;
filename = `${prefix}${rule.metadata.id || "rule"}.md`;
const privDir = join2(agentDir, "private");
mkdirSync(privDir, { recursive: true });
filePath = join2(privDir, filename);
} else {
filename = `${rule.metadata.id || "rule"}.md`;
filePath = join2(agentDir, filename);
}
}
const frontMatterBase = {};
if (rule.metadata.description !== void 0 && rule.metadata.description !== null) frontMatterBase.description = rule.metadata.description;
if (rule.metadata.alwaysApply !== void 0) frontMatterBase.alwaysApply = rule.metadata.alwaysApply;
if (rule.metadata.globs !== void 0 && rule.metadata.globs !== null) frontMatterBase.globs = rule.metadata.globs;
if (rule.metadata.manual !== void 0 && rule.metadata.manual !== null) frontMatterBase.manual = rule.metadata.manual;
if (rule.metadata.scope !== void 0 && rule.metadata.scope !== null) frontMatterBase.scope = rule.metadata.scope;
if (rule.metadata.priority !== void 0 && rule.metadata.priority !== null) frontMatterBase.priority = rule.metadata.priority;
if (rule.metadata.triggers !== void 0 && rule.metadata.triggers !== null) frontMatterBase.triggers = rule.metadata.triggers;
for (const [key, value] of Object.entries(rule.metadata)) {
if (!["id", "description", "alwaysApply", "globs", "manual", "scope", "priority", "triggers"].includes(key) && value !== void 0 && value !== null) {
if (key === "private" && value === false) continue;
frontMatterBase[key] = value;
}
}
const frontMatter = frontMatterBase;
const mdContent = matter2.stringify(rule.content, frontMatter, grayMatterOptions);
writeFileSync(filePath, mdContent, "utf-8");
});
}
function exportToCursor(rules, outputDir, options) {
const rulesDir = join2(outputDir, ".cursor", "rules");
mkdirSync(rulesDir, { recursive: true });
const filteredRules = rules.filter((rule) => !rule.metadata.private || options?.includePrivate);
for (const rule of filteredRules) {
let filePath;
if (rule.metadata.id && rule.metadata.id.includes("/")) {
const parts = rule.metadata.id.split("/");
const fileName = parts.pop() + ".mdc";
const subDir = join2(rulesDir, ...parts);
mkdirSync(subDir, { recursive: true });
filePath = join2(subDir, fileName);
} else {
const filename = `${rule.metadata.id || "rule"}.mdc`;
filePath = join2(rulesDir, filename);
}
const frontMatterBase = {};
if (rule.metadata.description !== void 0 && rule.metadata.description !== null) frontMatterBase.description = rule.metadata.description;
if (rule.metadata.alwaysApply !== void 0) frontMatterBase.alwaysApply = rule.metadata.alwaysApply;
if (rule.metadata.globs !== void 0 && rule.metadata.globs !== null) frontMatterBase.globs = rule.metadata.globs;
if (rule.metadata.manual !== void 0 && rule.metadata.manual !== null) frontMatterBase.manual = rule.metadata.manual;
if (rule.metadata.scope !== void 0 && rule.metadata.scope !== null) frontMatterBase.scope = rule.metadata.scope;
if (rule.metadata.priority !== void 0 && rule.metadata.priority !== null) frontMatterBase.priority = rule.metadata.priority;
if (rule.metadata.triggers !== void 0 && rule.metadata.triggers !== null) frontMatterBase.triggers = rule.metadata.triggers;
for (const [key, value] of Object.entries(rule.metadata)) {
if (!["id", "description", "alwaysApply", "globs", "manual", "scope", "priority", "triggers"].includes(key) && value !== void 0 && value !== null) {
if (key === "private" && value === false) continue;
frontMatterBase[key] = value;
}
}
const frontMatter = frontMatterBase;
const mdcContent = matter2.stringify(rule.content, frontMatter, grayMatterOptions);
writeFileSync(filePath, mdcContent, "utf-8");
}
}
function exportToCline(rules, outputPath, options) {
const filteredRules = rules.filter((rule) => !rule.metadata.private || options?.includePrivate);
if (outputPath.endsWith(".clinerules")) {
const alwaysApplyRules = filteredRules.filter((r) => r.metadata.alwaysApply !== false);
const conditionalSection = generateConditionalRulesSection(filteredRules, dirname(outputPath));
const mainContent = alwaysApplyRules.map((rule) => {
const header2 = rule.metadata.description ? `## ${rule.metadata.description}
` : "";
return header2 + rule.content;
}).join("\n\n");
const fullContent = conditionalSection ? `${mainContent}
${conditionalSection}` : mainContent;
ensureDirectoryExists(outputPath);
writeFileSync(outputPath, fullContent, "utf-8");
} else {
const rulesDir = join2(outputPath, ".clinerules");
mkdirSync(rulesDir, { recursive: true });
filteredRules.forEach((rule, index) => {
const filename = `${String(index + 1).padStart(2, "0")}-${rule.metadata.id || "rule"}.md`;
const filePath = join2(rulesDir, filename);
writeFileSync(filePath, rule.content, "utf-8");
});
}
}
function exportToWindsurf(rules, outputPath, options) {
const filteredRules = rules.filter((rule) => !rule.metadata.private || options?.includePrivate);
const alwaysApplyRules = filteredRules.filter((r) => r.metadata.alwaysApply !== false);
const conditionalSection = generateConditionalRulesSection(filteredRules, dirname(outputPath));
const mainContent = alwaysApplyRules.map((rule) => rule.content).join("\n\n");
const fullContent = conditionalSection ? `${mainContent}
${conditionalSection}` : mainContent;
ensureDirectoryExists(outputPath);
writeFileSync(outputPath, fullContent, "utf-8");
}
function exportToZed(rules, outputPath, options) {
const filteredRules = rules.filter((rule) => !rule.metadata.private || options?.includePrivate);
const alwaysApplyRules = filteredRules.filter((r) => r.metadata.alwaysApply !== false);
const conditionalSection = generateConditionalRulesSection(filteredRules, dirname(outputPath));
const mainContent = alwaysApplyRules.map((rule) => rule.content).join("\n\n");
const fullContent = conditionalSection ? `${mainContent}
${conditionalSection}` : mainContent;
ensureDirectoryExists(outputPath);
writeFileSync(outputPath, fullContent, "utf-8");
}
function exportSingleFileWithHeaders(rules, outputPath, options) {
const filteredRules = rules.filter((rule) => !rule.metadata.private || options?.includePrivate);
const alwaysApplyRules = filteredRules.filter((r) => r.metadata.alwaysApply !== false);
const conditionalSection = generateConditionalRulesSection(filteredRules, dirname(outputPath));
const mainContent = alwaysApplyRules.map((rule) => {
const header2 = rule.metadata.description ? `# ${rule.metadata.description}
` : "";
return header2 + rule.content;
}).join("\n\n");
const fullContent = conditionalSection ? `${mainContent}
${conditionalSection}` : mainContent;
ensureDirectoryExists(outputPath);
writeFileSync(outputPath, fullContent, "utf-8");
}
function exportToCodex(rules, outputPath, options) {
exportSingleFileWithHeaders(rules, outputPath, options);
}
function exportToAider(rules, outputPath, options) {
const filteredRules = rules.filter((rule) => !rule.metadata.private || options?.includePrivate);
const alwaysApplyRules = filteredRules.filter((r) => r.metadata.alwaysApply !== false);
const conditionalSection = generateConditionalRulesSection(filteredRules, dirname(outputPath));
const mainContent = alwaysApplyRules.map((rule) => rule.content).join("\n\n");
const fullContent = conditionalSection ? `${mainContent}
${conditionalSection}` : mainContent;
ensureDirectoryExists(outputPath);
writeFileSync(outputPath, fullContent, "utf-8");
}
function exportToClaudeCode(rules, outputPath, options) {
exportSingleFileWithHeaders(rules, outputPath, options);
}
function exportToOpenCode(rules, outputPath, options) {
exportSingleFileWithHeaders(rules, outputPath, options);
}
function exportToGemini(rules, outputPath, options) {
const filteredRules = rules.filter((rule) => !rule.metadata.private || options?.includePrivate);
const content = filteredRules.map((rule) => {
const header2 = rule.metadata.description ? `# ${rule.metadata.description}
` : "";
return header2 + rule.content;
}).join("\n\n");
ensureDirectoryExists(outputPath);
writeFileSync(outputPath, content, "utf-8");
}
function exportToQodo(rules, outputPath, options) {
const filteredRules = rules.filter((rule) => !rule.metadata.private || options?.includePrivate);
const alwaysApplyRules = filteredRules.filter((r) => r.metadata.alwaysApply !== false);
const conditionalSection = generateConditionalRulesSection(filteredRules, dirname(outputPath));
const mainContent = alwaysApplyRules.map((rule) => {
const header2 = rule.metadata.description ? `# ${rule.metadata.description}
` : "";
return header2 + rule.content;
}).join("\n\n---\n\n");
const fullContent = conditionalSection ? `${mainContent}
---
${conditionalSection}` : mainContent;
ensureDirectoryExists(outputPath);
writeFileSync(outputPath, fullContent, "utf-8");
}
function exportToAmazonQ(rules, outputDir, options) {
const rulesDir = join2(outputDir, ".amazonq", "rules");
mkdirSync(rulesDir, { recursive: true });
const filteredRules = rules.filter((rule) => !rule.metadata.private || options?.includePrivate);
for (const rule of filteredRules) {
let filePath;
if (rule.metadata.id && rule.metadata.id.includes("/")) {
const parts = rule.metadata.id.split("/");
const fileName = parts.pop() + ".md";
const subDir = join2(rulesDir, ...parts);
mkdirSync(subDir, { recursive: true });
filePath = join2(subDir, fileName);
} else {
const cleanId = rule.metadata.id?.startsWith("amazonq-") ? rule.metadata.id.substring(8) : rule.metadata.id || "rule";
const filename = `${cleanId}.md`;
filePath = join2(rulesDir, filename);
}
writeFileSync(filePath, rule.content, "utf-8");
}
}
function exportToRoo(rules, outputDir, options) {
const rulesDir = join2(outputDir, ".roo", "rules");
mkdirSync(rulesDir, { recursive: true });
const filteredRules = rules.filter((rule) => !rule.metadata.private || options?.includePrivate);
for (const rule of filteredRules) {
let filePath;
if (rule.metadata.id && rule.metadata.id.includes("/")) {
const parts = rule.metadata.id.split("/");
const fileName = parts.pop() + ".md";
const subDir = join2(rulesDir, ...parts);
mkdirSync(subDir, { recursive: true });
filePath = join2(subDir, fileName);
} else {
const filename = `${rule.metadata.id || "rule"}.md`;
filePath = join2(rulesDir, filename);
}
const frontMatterBase = {};
if (rule.metadata.alwaysApply !== void 0) frontMatterBase.alwaysApply = rule.metadata.alwaysApply;
if (rule.metadata.description !== void 0 && rule.metadata.description !== null) frontMatterBase.description = rule.metadata.description;
if (rule.metadata.scope !== void 0 && rule.metadata.scope !== null) frontMatterBase.scope = rule.metadata.scope;
if (rule.metadata.globs !== void 0 && rule.metadata.globs !== null) frontMatterBase.globs = rule.metadata.globs;
if (rule.metadata.manual !== void 0 && rule.metadata.manual !== null) frontMatterBase.manual = rule.metadata.manual;
if (rule.metadata.priority !== void 0 && rule.metadata.priority !== null) frontMatterBase.priority = rule.metadata.priority;
if (rule.metadata.triggers !== void 0 && rule.metadata.triggers !== null) frontMatterBase.triggers = rule.metadata.triggers;
for (const [key, value] of Object.entries(rule.metadata)) {
if (!["id", "alwaysApply", "description", "scope", "globs", "manual", "priority", "triggers"].includes(key) && value !== void 0 && value !== null) {
if (key === "private" && value === false) continue;
frontMatterBase[key] = value;
}
}
const frontMatter = frontMatterBase;
const content = rule.content.startsWith("\n") ? rule.content : "\n" + rule.content;
const mdContent = matter2.stringify(content, frontMatter, grayMatterOptions);
writeFileSync(filePath, mdContent, "utf-8");
}
}
function exportToJunie(rules, outputDir, options) {
const junieDir = join2(outputDir, ".junie");
mkdirSync(junieDir, { recursive: true });
const filteredRules = rules.filter((rule) => !rule.metadata.private || options?.includePrivate);
const alwaysApplyRules = filteredRules.filter((r) => r.metadata.alwaysApply !== false);
const conditionalSection = generateConditionalRulesSection(filteredRules, outputDir);
const mainContent = alwaysApplyRules.map((rule) => {
const header2 = rule.metadata.description ? `## ${rule.metadata.description}
` : "";
return header2 + rule.content;
}).join("\n\n");
const fullContent = conditionalSection ? `${mainContent}
${conditionalSection}` : mainContent;
const filePath = join2(junieDir, "guidelines.md");
writeFileSync(filePath, fullContent, "utf-8");
}
function exportAll(rules, repoPath, dryRun = false, options = { includePrivate: false }) {
if (!dryRun) {
exportToAgent(rules, repoPath, options);
exportToCopilot(rules, join2(repoPath, ".github", "copilot-instructions.md"), options);
exportToCursor(rules, repoPath, options);
exportToCline(rules, join2(repoPath, ".clinerules"), options);
exportToWindsurf(rules, join2(repoPath, ".windsurfrules"), options);
exportToZed(rules, join2(repoPath, ".rules"), options);
exportToCodex(rules, join2(repoPath, "AGENTS.md"), options);
exportToAider(rules, join2(repoPath, "CONVENTIONS.md"), options);
exportToClaudeCode(rules, join2(repoPath, "CLAUDE.md"), options);
exportToOpenCode(rules, join2(repoPath, "AGENTS.md"), options);
exportToGemini(rules, join2(repoPath, "GEMINI.md"), options);
exportToQodo(rules, join2(repoPath, "best_practices.md"), options);
exportToAmazonQ(rules, repoPath, options);
exportToRoo(rules, repoPath, options);
exportToJunie(rules, repoPath, options);
}
}
function ensureDirectoryExists(filePath) {
const dir = dirname(filePath);
if (!existsSync2(dir)) {
mkdirSync(dir, { recursive: true });
}
}
// src/utils/colors.ts
init_esm_shims();
var colors = {
reset: "\x1B[0m",
bright: "\x1B[1m",
dim: "\x1B[2m",
// Foreground colors
red: "\x1B[31m",
green: "\x1B[32m",
yellow: "\x1B[33m",
blue: "\x1B[34m",
magenta: "\x1B[35m",
cyan: "\x1B[36m",
gray: "\x1B[90m",
// Background colors
bgRed: "\x1B[41m",
bgGreen: "\x1B[42m",
bgYellow: "\x1B[43m"
};
function supportsColor() {
if (process.env.NO_COLOR) return false;
if (process.env.TERM === "dumb") return false;
if (process.env.COLORTERM) return true;
if (process.env.TERM?.includes("color")) return true;
return false;
}
function colorize(text, color2) {
if (!supportsColor()) return text;
return `${colors[color2]}${text}${colors.reset}`;
}
var color = {
// Status messages
success: (text) => colorize(`\u2713 ${text}`, "green"),
error: (text) => colorize(`\u2717 ${text}`, "red"),
warning: (text) => colorize(`\u26A0 ${text}`, "yellow"),
info: (text) => colorize(`\u2139 ${text}`, "blue"),
// Text formatting
bold: (text) => colorize(text, "bright"),
dim: (text) => colorize(text, "dim"),
// Semantic colors
path: (text) => colorize(text, "cyan"),
command: (text) => colorize(text, "magenta"),
number: (text) => colorize(text, "yellow"),
format: (text) => colorize(text, "blue"),
// Raw colors
red: (text) => colorize(text, "red"),
green: (text) => colorize(text, "green"),
yellow: (text) => colorize(text, "yellow"),
blue: (text) => colorize(text, "blue"),
gray: (text) => colorize(text, "gray")
};
function formatList(items, prefix = " ") {
return items.map((item) => `${prefix}${color.dim("\u2022")} ${item}`).join("\n");
}
function header(text) {
const line = color.dim("\u2500".repeat(text.length + 4));
return `
${line}
${color.bold(` ${text} `)}
${line}
`;
}
// src/utils/prompt.ts
init_esm_shims();
import { select as inquirerSelect, confirm as inquirerConfirm } from "@inquirer/prompts";
async function confirm(question, defaultValue = false) {
if (!process.stdin.isTTY || process.env.NODE_ENV === "test") {
return defaultValue;
}
return await inquirerConfirm({
message: question,
default: defaultValue
});
}
async function select(message, choices, defaultIndex = 0) {
const inquirerChoices = choices.map((choice, index) => ({
name: choice.name,
value: choice.value,
// Set the default based on index
...index === defaultIndex ? { default: true } : {}
}));
return await inquirerSelect({
message,
choices: inquirerChoices
});
}
// src/cli.ts
var { values, positionals } = parseArgs({
args: process.argv.slice(2),
options: {
help: { type: "boolean", short: "h" },
output: { type: "string", short: "o" },
format: { type: "string", short: "f" },
formats: { type: "string" },
overwrite: { type: "boolean", short: "w" },
"dry-run": { type: "boolean", short: "d" },
"include-private": { type: "boolean" },
"skip-private": { type: "boolean" },
"gitignore": { type: "boolean" },
"no-gitignore": { type: "boolean" }
},
allowPositionals: true
});
if (values["gitignore"] && values["no-gitignore"]) {
console.error(color.error("Cannot use both --gitignore and --no-gitignore flags together"));
process.exit(1);
}
function showHelp() {
console.log(`
${color.bold("dotagent")} - Multi-file AI agent configuration manager
${color.bold("Usage:")}
${color.command("dotagent import")} ${color.dim("<repo-path>")} Import all rule files from a repository
${color.command("dotagent export")} ${color.dim("[repo-path]")} Export .agent/ directory to all supported formats
${color.command("dotagent convert")} ${color.dim("<file>")} Convert a specific rule file
${color.bold("Options:")}
${color.yellow("-h, --help")} Show this help message
${color.yellow("-o, --output")} Output file path (for convert command)
${color.yellow("-f, --format")} Specify format (copilot|cursor|cline|windsurf|zed|codex|aider|claude|gemini|qodo|roo|junie|opencode)
${color.yellow("--formats")} Specify multiple formats (comma-separated)
${color.yellow("-w, --overwrite")} Overwrite existing files
${color.yellow("-d, --dry-run")} Preview operations without making changes
${color.yellow("--gitignore")} Auto-update gitignore (skip prompt)
${color.yellow("--no-gitignore")} Skip gitignore prompt
${color.bold("Examples:")}
${color.dim("# Import all rules from current directory (creates .agent/)")}
${color.command("dotagent import .")}
${color.dim("# Export .agent/ directory to all formats")}
${color.command("dotagent export")}
${color.dim("# Export from specific directory")}
${color.command("dotagent export /path/to/repo")}
${color.dim("# Preview what would be imported without creating files")}
${color.command("dotagent import . --dry-run")}
`);
}
async function main() {
if (values.help || positionals.length === 0) {
showHelp();
process.exit(0);
}
const command = positionals[0];
const target = positionals[1];
const isDryRun = values["dry-run"];
if (isDryRun) {
console.log(color.info("Running in dry-run mode - no files will be modified"));
}
switch (command) {
case "import": {
const importTarget = target || ".";
const repoPath = resolve(importTarget);
if (!existsSync3(repoPath)) {
console.error(color.error(`Path does not exist: ${color.path(repoPath)}`));
console.error(color.dim('Hint: Check if the path is correct or use "." for current directory'));
process.exit(1);
}
console.log(header("Importing Rules"));
console.log(`Scanning: ${color.path(repoPath)}`);
const { results, errors } = await importAll(repoPath);
if (results.length === 0) {
console.log(color.warning("No rule files found"));
console.log(color.dim("Hint: DotAgent looks for:"));
console.log(formatList([
".agent/**/*.md",
".github/copilot-instructions.md",
".cursor/**/*.{mdc,md}",
".clinerules",
".windsurfrules",
".rules",
"AGENTS.md",
"CLAUDE.md",
"GEMINI.md",
"best_practices.md"
]));
} else {
console.log(color.success(`Found ${color.number(results.length.toString())} rule file(s):`));
for (const result of results) {
const ruleCount = color.number(`${result.rules.length} rule(s)`);
console.log(` ${color.format(result.format)}: ${color.path(result.filePath)} ${color.dim(`(${ruleCount})`)}`);
}
const allRules = results.flatMap((r) => r.rules);
const agentDir = join3(repoPath, ".agent");
if (existsSync3(agentDir)) {
const existingAgent = importAgent(agentDir);
console.log(color.info(`Found existing .agent/ directory with ${color.number(existingAgent.rules.length.toString())} rule(s)`));
}
if (isDryRun) {
console.log(color.info(`Would export to: ${color.path(agentDir)}`));
console.log(color.dim(`Total rules: ${allRules.length}`));
} else {
const outputDir = values.output || repoPath;
exportToAgent(allRules, outputDir);
console.log(col