Skip to content

Commit feb8680

Browse files
authored
Merge pull request #310 from syncable-dev/develop
Develop
2 parents 6ebdc11 + d7423ff commit feb8680

13 files changed

Lines changed: 106 additions & 1113 deletions

File tree

.cursor/rules/project-rules.mdc

Lines changed: 0 additions & 960 deletions
This file was deleted.

.cursor/rules/rust-rules.mdc

Lines changed: 0 additions & 56 deletions
This file was deleted.

installer/README.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -23,11 +23,11 @@ One command installs **11 skills** (7 command + 4 workflow) that give AI coding
2323

2424
| Agent | Install Type | Format |
2525
|-------|-------------|--------|
26-
| **Claude Code** | Global (`~/.claude/skills/`) | Native markdown |
27-
| **Codex** | Global (`~/.codex/skills/`) | `SKILL.md` directories |
26+
| **Claude Code** | Plugin (`~/.claude/plugins/cache/syncable/`) | Plugin marketplace with `SKILL.md` directories |
27+
| **Codex** | Global (`~/.agents/skills/`) | `SKILL.md` directories |
2828
| **Cursor** | Per-project (`.cursor/rules/`) | `.mdc` with `alwaysApply` |
2929
| **Windsurf** | Per-project (`.windsurf/rules/`) | `.md` with `trigger: always` |
30-
| **Gemini CLI** | Per-project (`GEMINI.md`) | Concatenated markdown block |
30+
| **Gemini CLI** | Global (`~/.gemini/<profile>/skills/`) | `SKILL.md` directories |
3131

3232
## Quick Start
3333

installer/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "syncable-cli-skills",
3-
"version": "0.1.0",
3+
"version": "0.1.3",
44
"type": "module",
55
"description": "Install Syncable CLI skills for AI coding agents (Claude Code, Cursor, Windsurf, Codex, Gemini CLI)",
66
"license": "GPL-3.0",

installer/src/agents/codex.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ export const codexAgent: AgentConfig = {
1212
return fs.existsSync(path.join(os.homedir(), '.codex')) || await commandExists('codex');
1313
},
1414
getSkillPath: () => {
15-
return path.join(os.homedir(), '.codex', 'skills');
15+
// Codex user-level skills path per docs: $HOME/.agents/skills
16+
return path.join(os.homedir(), '.agents', 'skills');
1617
},
1718
};

installer/src/agents/gemini.ts

Lines changed: 35 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,14 +4,47 @@ import os from 'os';
44
import { AgentConfig } from './types.js';
55
import { commandExists } from '../utils.js';
66

7+
/**
8+
* Find the Gemini CLI skills directory.
9+
* Gemini CLI stores skills under ~/.gemini/<profile>/skills/
10+
* The default profile is 'antigravity'.
11+
*/
12+
function findGeminiSkillsDir(): string {
13+
const geminiDir = path.join(os.homedir(), '.gemini');
14+
15+
// Check for antigravity profile (default)
16+
const antigravitySkills = path.join(geminiDir, 'antigravity', 'skills');
17+
if (fs.existsSync(antigravitySkills)) {
18+
return antigravitySkills;
19+
}
20+
21+
// Check for any profile with a skills directory
22+
if (fs.existsSync(geminiDir)) {
23+
try {
24+
const entries = fs.readdirSync(geminiDir);
25+
for (const entry of entries) {
26+
const skillsPath = path.join(geminiDir, entry, 'skills');
27+
if (fs.existsSync(skillsPath) && fs.statSync(skillsPath).isDirectory()) {
28+
return skillsPath;
29+
}
30+
}
31+
} catch {
32+
// Ignore errors
33+
}
34+
}
35+
36+
// Default to antigravity profile
37+
return antigravitySkills;
38+
}
39+
740
export const geminiAgent: AgentConfig = {
841
name: 'gemini',
942
displayName: 'Gemini CLI',
10-
installType: 'project',
43+
installType: 'global',
1144
detect: async () => {
1245
return fs.existsSync(path.join(os.homedir(), '.gemini')) || await commandExists('gemini');
1346
},
1447
getSkillPath: () => {
15-
return path.join(process.cwd(), 'GEMINI.md');
48+
return findGeminiSkillsDir();
1649
},
1750
};

installer/src/commands/install.ts

Lines changed: 10 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import { transformForCursor } from '../transformers/cursor.js';
77
import { transformForWindsurf } from '../transformers/windsurf.js';
88
import { transformForGemini } from '../transformers/gemini.js';
99
import { SKILL_MARKER_START, SKILL_MARKER_END } from '../constants.js';
10+
import { TransformResult } from '../transformers/types.js';
1011

1112
export function writeSkillsForClaude(skills: Skill[], _destDir: string): void {
1213
// Claude Code uses the plugin marketplace system — destDir is ignored.
@@ -45,27 +46,17 @@ export function writeSkillsForWindsurf(skills: Skill[], destDir: string): void {
4546
}
4647
}
4748

48-
export function writeSkillsForGemini(skills: Skill[], filePath: string): void {
49-
const geminiContent = transformForGemini(skills);
50-
let existing = '';
51-
52-
if (fs.existsSync(filePath)) {
53-
existing = fs.readFileSync(filePath, 'utf-8');
54-
55-
// Replace existing section if present
56-
const startIdx = existing.indexOf(SKILL_MARKER_START);
57-
const endIdx = existing.indexOf(SKILL_MARKER_END);
58-
if (startIdx !== -1 && endIdx !== -1) {
59-
const before = existing.slice(0, startIdx);
60-
const after = existing.slice(endIdx + SKILL_MARKER_END.length);
61-
fs.writeFileSync(filePath, before + geminiContent + after);
62-
return;
49+
export function writeSkillsForGemini(skills: Skill[], destDir: string): void {
50+
// Gemini CLI uses skills/<skill-name>/SKILL.md format
51+
// destDir is ~/.gemini/<profile>/skills/
52+
for (const skill of skills) {
53+
const results = transformForGemini(skill);
54+
for (const { relativePath, content } of results) {
55+
const fullPath = path.join(destDir, relativePath);
56+
fs.mkdirSync(path.dirname(fullPath), { recursive: true });
57+
fs.writeFileSync(fullPath, content);
6358
}
6459
}
65-
66-
// Append to existing or create new
67-
const separator = existing && !existing.endsWith('\n') ? '\n\n' : existing ? '\n' : '';
68-
fs.writeFileSync(filePath, existing + separator + geminiContent + '\n');
6960
}
7061

7162
export interface InstallOptions {

installer/src/commands/status.ts

Lines changed: 4 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -48,16 +48,10 @@ export function countInstalledSkills(dirOrPath: string, agent: AgentName | strin
4848

4949
case 'gemini': {
5050
if (!fs.existsSync(dirOrPath)) return 0;
51-
const content = fs.readFileSync(dirOrPath, 'utf-8');
52-
if (content.includes(SKILL_MARKER_START)) {
53-
const start = content.indexOf(SKILL_MARKER_START);
54-
const end = content.indexOf('<!-- SYNCABLE-CLI-SKILLS-END -->');
55-
if (start !== -1 && end !== -1) {
56-
const section = content.slice(start, end);
57-
return (section.match(/^### /gm) || []).length;
58-
}
59-
}
60-
return 0;
51+
// New format: skills/<name>/SKILL.md directories
52+
return fs.readdirSync(dirOrPath)
53+
.filter((f) => f.startsWith('syncable-') && fs.statSync(path.join(dirOrPath, f)).isDirectory())
54+
.length;
6155
}
6256

6357
default:

installer/src/index.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@
33
import { Command } from 'commander';
44
import inquirer from 'inquirer';
55
import ora from 'ora';
6+
import path from 'path';
7+
import os from 'os';
68
import chalk from 'chalk';
79
import { createRequire } from 'module';
810
import { checkNodeVersion, checkCargo, checkSyncCtl } from './prerequisites/check.js';
@@ -248,6 +250,8 @@ program
248250
break;
249251
case 'codex':
250252
removeSyncableSkills(dest, 'syncable-*');
253+
// Also clean old location (~/.codex/skills/)
254+
removeSyncableSkills(path.join(os.homedir(), '.codex', 'skills'), 'syncable-*');
251255
break;
252256
case 'cursor':
253257
removeSyncableSkills(dest, 'syncable-*.mdc');
@@ -256,7 +260,7 @@ program
256260
removeSyncableSkills(dest, 'syncable-*.md');
257261
break;
258262
case 'gemini':
259-
removeGeminiSection(dest);
263+
removeSyncableSkills(dest, 'syncable-*');
260264
break;
261265
}
262266
spinner.succeed(` Skills removed from ${agent.displayName}`);

installer/src/transformers/gemini.ts

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,23 @@
11
import { Skill } from '../skills.js';
2+
import { TransformResult } from './types.js';
23
import { SKILL_MARKER_START, SKILL_MARKER_END } from '../constants.js';
34

4-
export function transformForGemini(skills: Skill[]): string {
5+
/**
6+
* Transform a skill into Gemini CLI skill format.
7+
* Each skill becomes a directory with SKILL.md inside skills/<skill-name>/
8+
* Format: frontmatter with name + description, then markdown body.
9+
*/
10+
export function transformForGemini(skill: Skill): TransformResult[] {
11+
const skillName = skill.filename.replace(/\.md$/, '');
12+
const content = `---\nname: ${skillName}\ndescription: ${skill.frontmatter.description}\n---\n\n${skill.body}`;
13+
return [{ relativePath: `${skillName}/SKILL.md`, content }];
14+
}
15+
16+
/**
17+
* Legacy: generate a flat GEMINI.md section for older Gemini CLI versions.
18+
* Used as a fallback when the skills directory approach isn't available.
19+
*/
20+
export function transformForGeminiLegacy(skills: Skill[]): string {
521
const sections = skills
622
.map((s) => `### ${s.frontmatter.name}\n\n${s.body}`)
723
.join('\n\n');

0 commit comments

Comments
 (0)