Skip to content
Merged
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
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,9 @@
## [2.2.0](https://github.com/phuthuycoding/moicle/compare/v2.1.0...v2.2.0) (2026-05-30)

### Features

* **antigravity:** add full skill-based support for Antigravity editor with dedicated installation, architecture, and skill mapping flows

## [2.1.0](https://github.com/phuthuycoding/moicle/compare/v2.0.0...v2.1.0) (2026-05-26)

### Features
Expand Down
9 changes: 7 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ A toolkit to bootstrap and accelerate project development with Claude Code throu

- [x] Claude
- [x] Codex CLI
- [ ] Antigravity
- [x] Antigravity
- [ ] Cursor
- [ ] Windsurf

Expand All @@ -43,8 +43,11 @@ moicle install
# Install for Codex CLI
moicle install --target codex --global

# Install for Antigravity
moicle install --target antigravity --global

# Choose:
# 1. Pick Claude Code or Codex CLI
# 1. Pick Claude Code, Codex CLI, or Antigravity
# 2. Pick global or project scope
```

Expand Down Expand Up @@ -211,6 +214,8 @@ When an agent is invoked, it **reads the architecture file first** before coding

For Codex CLI, MoiCle installs architecture docs into `~/.codex/architecture` or `./.codex/architecture`, and converts MoiCle agents, commands, and existing skills into native Codex skills under `.codex/skills`. Restart Codex after a global install so the new skills are loaded.

For Antigravity, MoiCle installs architecture docs into `~/.gemini/architecture` or `./.gemini/architecture`, and converts MoiCle agents, commands, and existing skills into native Antigravity skills under `.gemini/skills`.

## Usage Examples

### Using Commands
Expand Down
4 changes: 2 additions & 2 deletions bin/cli.js
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ program
.description('List installed agents, commands, and skills')
.option('-g, --global', 'List global installations')
.option('-p, --project', 'List project installations')
.option('-t, --target <editor>', 'Target editor (claude, codex)')
.option('-t, --target <editor>', 'Target editor (claude, codex, antigravity)')
.action(listCommand);

program
Expand Down Expand Up @@ -75,7 +75,7 @@ program
.description('Show enabled/disabled status of all items')
.option('-g, --global', 'Show global status')
.option('-p, --project', 'Show project status')
.option('-t, --target <editor>', 'Target editor (claude, codex)')
.option('-t, --target <editor>', 'Target editor (claude, codex, antigravity)')
.action(statusCommand);

program
Expand Down
120 changes: 120 additions & 0 deletions bun.lock

Large diffs are not rendered by default.

233 changes: 224 additions & 9 deletions src/commands/install.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import {
getArchitectureDir,
getClaudeDir,
getCodexDir,
getAntigravityDir,
getEditorDir,
getEditorConfig,
getFiles,
Expand Down Expand Up @@ -177,10 +178,17 @@ const installScope = async (scope: Scope, useSymlink: boolean): Promise<void> =>
console.log(chalk.green(`✓ ${label} installation complete!`));
};

const rewriteClaudePaths = (content: string, target: 'claude' | 'codex'): string => {
const rewriteClaudePaths = (content: string, target: 'claude' | 'codex' | 'antigravity'): string => {
if (target === 'claude') {
return content;
}
if (target === 'antigravity') {
return content
.replace(/~\/\.claude\//g, '~/.gemini/')
.replace(/\.claude\//g, '.gemini/')
.replace(/Claude Code/g, 'Antigravity')
.replace(/CLAUDE\.md/g, 'GEMINI.md');
}

return content
.replace(/~\/\.claude\//g, '~/.codex/')
Expand Down Expand Up @@ -400,6 +408,198 @@ const installCodexScope = async (scope: Scope): Promise<void> => {
console.log(chalk.green(`✓ ${label} Codex installation complete!`));
};

const ensureAntigravitySkillDir = (baseDir: string, name: string): string => {
const skillDir = path.join(baseDir, name);
ensureDir(skillDir);
return skillDir;
};

const installAntigravitySkillFolder = (
sourceDir: string,
targetSkillsDir: string
): FileResult => {
const skillName = path.basename(sourceDir);
const targetDir = ensureAntigravitySkillDir(targetSkillsDir, skillName);
const sourceFiles = getFiles(sourceDir, 8);

let status: FileResult['status'] = 'created';
for (const file of sourceFiles) {
const relativePath = path.relative(sourceDir, file);
const targetFile = path.join(targetDir, relativePath);
ensureDir(path.dirname(targetFile));

const content = rewriteClaudePaths(fs.readFileSync(file, 'utf-8'), 'antigravity');
const existed = fs.existsSync(targetFile);
if (existed && fs.readFileSync(targetFile, 'utf-8') === content) {
status = status === 'created' ? 'exists' : status;
continue;
}

fs.writeFileSync(targetFile, content);
if (existed) {
status = 'updated';
}
}

return { status, name: skillName };
};

const buildGeneratedAntigravitySkill = (
name: string,
description: string,
body: string
): string => `---
name: ${name}
description: ${description}
---

${body}
`;

const installGeneratedAntigravitySkill = (
targetSkillsDir: string,
name: string,
description: string,
body: string
): FileResult => {
const skillDir = ensureAntigravitySkillDir(targetSkillsDir, name);
const targetFile = path.join(skillDir, 'SKILL.md');
const content = buildGeneratedAntigravitySkill(name, description, rewriteClaudePaths(body, 'antigravity'));

if (fs.existsSync(targetFile)) {
if (fs.readFileSync(targetFile, 'utf-8') === content) {
return { status: 'exists', name };
}
fs.writeFileSync(targetFile, content);
return { status: 'updated', name };
}

fs.writeFileSync(targetFile, content);
return { status: 'created', name };
};

const installDirectAntigravitySkill = (
targetSkillsDir: string,
name: string,
content: string
): FileResult => {
const skillDir = ensureAntigravitySkillDir(targetSkillsDir, name);
const targetFile = path.join(skillDir, 'SKILL.md');
const rewritten = rewriteClaudePaths(content, 'antigravity');

if (fs.existsSync(targetFile)) {
if (fs.readFileSync(targetFile, 'utf-8') === rewritten) {
return { status: 'exists', name };
}
fs.writeFileSync(targetFile, rewritten);
return { status: 'updated', name };
}

fs.writeFileSync(targetFile, rewritten);
return { status: 'created', name };
};

const installAntigravityArchitecture = (targetDir: string): FileResult[] => {
const results: FileResult[] = [];
const archDir = path.join(ASSETS_DIR, 'architecture');
const targetArchDir = path.join(targetDir, 'architecture');

ensureDir(targetArchDir);

if (!fs.existsSync(archDir)) {
return results;
}

for (const file of getFiles(archDir)) {
const targetFile = path.join(targetArchDir, path.basename(file));
const content = rewriteClaudePaths(fs.readFileSync(file, 'utf-8'), 'antigravity');
if (fs.existsSync(targetFile)) {
if (fs.readFileSync(targetFile, 'utf-8') === content) {
results.push({ status: 'exists', name: path.basename(file) });
continue;
}
fs.writeFileSync(targetFile, content);
results.push({ status: 'updated', name: path.basename(file) });
continue;
}

fs.writeFileSync(targetFile, content);
results.push({ status: 'created', name: path.basename(file) });
}

return results;
};

const installAntigravitySkills = (targetDir: string): FileResult[] => {
const results: FileResult[] = [];
const targetSkillsDir = path.join(targetDir, 'skills');
ensureDir(targetSkillsDir);

const skillsDir = path.join(ASSETS_DIR, 'skills');
if (fs.existsSync(skillsDir)) {
for (const dir of getDirs(skillsDir)) {
results.push(installAntigravitySkillFolder(dir, targetSkillsDir));
}
}

const commandsDir = path.join(ASSETS_DIR, 'commands');
if (fs.existsSync(commandsDir)) {
for (const file of getFiles(commandsDir)) {
const name = path.basename(file, '.md');
const content = fs.readFileSync(file, 'utf-8');
results.push(installDirectAntigravitySkill(targetSkillsDir, name, content));
}
}

const agentDirs = ['developers', 'utilities'];
for (const dirName of agentDirs) {
const sourceDir = path.join(ASSETS_DIR, 'agents', dirName);
if (!fs.existsSync(sourceDir)) {
continue;
}

for (const file of getFiles(sourceDir)) {
const name = path.basename(file, '.md');
const rawContent = fs.readFileSync(file, 'utf-8');
const parsed = extractFrontmatter(rawContent);
const description = parsed.description
? parsed.description
: dirName === 'developers'
? `Imported MoiCle developer persona for ${name}. Use when the task matches this stack specialist.`
: `Imported MoiCle utility persona for ${name}. Use when the task matches this specialist.`;

results.push(installGeneratedAntigravitySkill(targetSkillsDir, name, description, parsed.body.trimStart()));
}
}

return results;
};

const installAntigravityScope = async (scope: Scope): Promise<void> => {
const isGlobal = scope === 'global';
const label = isGlobal ? 'Global' : 'Project';
const targetPath = isGlobal ? '~/.gemini/' : `${process.cwd()}/.gemini/`;

console.log('');
console.log(chalk.cyan(`>>> ${label} Antigravity Installation`));
console.log(chalk.gray(` Target: ${targetPath}`));
console.log('');

const antigravityDir = getAntigravityDir(scope);
ensureDir(antigravityDir);

const archResults = installAntigravityArchitecture(antigravityDir);
console.log(chalk.green(` ✓ Architecture installed to ${chalk.cyan(path.join(antigravityDir, 'architecture'))}`));
printSummary(archResults);

const skillResults = installAntigravitySkills(antigravityDir);
console.log(chalk.green(` ✓ Antigravity skills installed to ${chalk.cyan(path.join(antigravityDir, 'skills'))}`));
printSummary(skillResults);

console.log('');
console.log(chalk.green(`✓ ${label} Antigravity installation complete!`));
};

const showTargetMenu = async (): Promise<EditorTarget> => {
const { target } = await inquirer.prompt([
{
Expand All @@ -409,6 +609,7 @@ const showTargetMenu = async (): Promise<EditorTarget> => {
choices: [
{ name: 'Claude Code', value: 'claude' },
{ name: 'Codex CLI', value: 'codex' },
{ name: 'Antigravity', value: 'antigravity' },
],
},
]);
Expand All @@ -417,10 +618,10 @@ const showTargetMenu = async (): Promise<EditorTarget> => {
};

const showInteractiveMenu = async (
target: 'claude' | 'codex'
target: 'claude' | 'codex' | 'antigravity'
): Promise<'global' | 'project' | 'all'> => {
const globalPath = target === 'claude' ? '~/.claude/' : '~/.codex/';
const projectPath = target === 'claude' ? './.claude/' : './.codex/';
const globalPath = target === 'claude' ? '~/.claude/' : target === 'codex' ? '~/.codex/' : '~/.gemini/';
const projectPath = target === 'claude' ? './.claude/' : target === 'codex' ? './.codex/' : './.gemini/';

const { installType } = await inquirer.prompt([
{
Expand Down Expand Up @@ -529,7 +730,7 @@ export const installCommand = async (options: CommandOptions): Promise<void> =>
for (const target of targets) {
addTarget(target);

if (target === 'claude' || target === 'codex') {
if (target === 'claude' || target === 'codex' || target === 'antigravity') {
let installType: 'global' | 'project' | 'all';

if (options.global) {
Expand All @@ -546,24 +747,31 @@ export const installCommand = async (options: CommandOptions): Promise<void> =>
case 'global':
if (target === 'claude') {
await installScope('global', useSymlink);
} else {
} else if (target === 'codex') {
await installCodexScope('global');
} else {
await installAntigravityScope('global');
}
break;
case 'project':
if (target === 'claude') {
await installScope('project', false);
} else {
} else if (target === 'codex') {
await installCodexScope('project');
} else {
await installAntigravityScope('project');
}
break;
case 'all':
if (target === 'claude') {
await installScope('global', useSymlink);
await installScope('project', false);
} else {
} else if (target === 'codex') {
await installCodexScope('global');
await installCodexScope('project');
} else {
await installAntigravityScope('global');
await installAntigravityScope('project');
}
break;
}
Expand Down Expand Up @@ -604,7 +812,14 @@ export const installCommand = async (options: CommandOptions): Promise<void> =>
console.log('');
}

const otherTargets = targets.filter((t) => t !== 'claude' && t !== 'codex');
if (targets.includes('antigravity')) {
console.log(chalk.bold(' Antigravity:'));
console.log(chalk.gray(' Skills installed under ~/.gemini/skills or ./.gemini/skills'));
console.log(chalk.gray(' Architecture docs installed under ~/.gemini/architecture or ./.gemini/architecture'));
console.log('');
}

const otherTargets = targets.filter((t) => t !== 'claude' && t !== 'codex' && t !== 'antigravity');
if (otherTargets.length > 0) {
console.log(chalk.bold(' Other Editors:'));
for (const target of otherTargets) {
Expand Down
Loading
Loading