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
65 changes: 58 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,13 +45,14 @@ Built for **DevOps engineers**, **Platform engineers**, **Administrators**, and

### Operations

| Command | Description |
|-----------------|---------------------------------------------------------|
| `execute apply` | Apply pending changes |
| `audit list` | List audit entries (snapshot or full history) |
| `audit fix` | Fix a change's audit state (`APPLIED` or `ROLLED_BACK`) |
| `issue list` | List changes with audit issues |
| `issue get` | Get details and resolution guidance for an issue |
| Command | Description |
|------------------|----------------------------------------------------------------|
| `execute apply` | Apply pending changes |
| `audit list` | List audit entries (snapshot or full history) |
| `audit fix` | Fix a change's audit state (`APPLIED` or `ROLLED_BACK`) |
| `issue list` | List changes with audit issues |
| `issue get` | Get details and resolution guidance for an issue |
| `install-skills` | Install official Flamingock AI skills into the current project |

### Global Options

Expand Down Expand Up @@ -127,6 +128,56 @@ flamingock audit list --jar ./my-app.jar -J -Xmx1g -- --spring.profiles.active=s

---

## 🧠 Installing AI Skills

Use `install-skills` to copy the official Flamingock skills into the directory for the AI assistant you use in the current project.

### Quick path

```bash
# Default project-local install
flamingock install-skills

# Claude project-local install
flamingock install-skills --agent claude

# GitHub Copilot project-local install
flamingock install-skills --agent github

# Cursor project-local install
flamingock install-skills --agent cursor

# OpenCode project-local install
flamingock install-skills --agent opencode

# Google Gemini project-local install
flamingock install-skills --agent gemini

# Windsurf project-local install
flamingock install-skills --agent windsurf

# PI Agent project-local install
flamingock install-skills --agent pi
```

### Current targets

| Command | Destination |
|----------------------------------------------|----------------------|
| `flamingock install-skills` | `./.agents/skills` |
| `flamingock install-skills --agent claude` | `./.claude/skills` |
| `flamingock install-skills --agent github` | `./.github/skills` |
| `flamingock install-skills --agent cursor` | `./.cursor/skills` |
| `flamingock install-skills --agent opencode` | `./.opencode/skills` |
| `flamingock install-skills --agent gemini` | `./.gemini/skills` |
| `flamingock install-skills --agent windsurf` | `./.windsurf/skills` |
| `flamingock install-skills --agent pi` | `./.pi/skills` |

Note:
- Paths are resolved relative to the directory where you run the command.

---

## 📦 Installation

Choose the option that best fits your platform:
Expand Down
2 changes: 1 addition & 1 deletion gradle.properties
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
group=io.flamingock
version=1.2.0
version=1.2.1
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,10 @@ public class InstallSkillsCommand implements Callable<Integer> {
@Option(names = {"-g", "--global"}, description = "Install skills globally (not implemented yet)")
private boolean global;

@Option(names = {"-a", "--agent"},
description = "Target AI assistant: claude, github, cursor, opencode, gemini, windsurf, pi")
private String agent;

private final SkillsInstallationTargetResolver targetResolver;
private final SkillsInstallationPipeline pipeline;
private final Path workingDirectory;
Expand Down Expand Up @@ -69,7 +73,7 @@ public InstallSkillsCommand() {
@Override
public Integer call() {
try {
List<SkillsInstallationTarget> targets = targetResolver.resolveTargets(workingDirectory.toAbsolutePath().normalize(), global);
List<SkillsInstallationTarget> targets = targetResolver.resolveTargets(workingDirectory.toAbsolutePath().normalize(), global, agent);
SkillsInstallationResult result = pipeline.install(targets);
ConsoleFormatter.printInfo(buildSuccessMessage(result));
return 0;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,9 @@ public SkillsInstallationResult install(List<SkillsInstallationTarget> targets)
snapshotRoot,
path -> path.getFileName().toString().startsWith(SKILL_DIRECTORY_PREFIX)
);
for (Path skillDirectory : skillDirectories) {
FileSystemUtils.deleteRecursively(skillDirectory.resolve("evals"));
}
List<String> installedSkills = new ArrayList<>();
for (Path skillDirectory : skillDirectories) {
installedSkills.add(skillDirectory.getFileName().toString());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,4 +40,84 @@ public record SkillsInstallationTarget(String identifier, Path destinationSkills
public static SkillsInstallationTarget local(Path destinationSkillsDir) {
return new SkillsInstallationTarget("local", destinationSkillsDir);
}

/**
* Creates an agent-specific installation target for the <code>agents</code> assistant.
*
* @param destinationSkillsDir agent destination directory
* @return agents installation target
*/
public static SkillsInstallationTarget agents(Path destinationSkillsDir) {
return new SkillsInstallationTarget("local:agents", destinationSkillsDir);
}

/**
* Creates an agent-specific installation target for the <code>claude</code> assistant.
*
* @param destinationSkillsDir claude destination directory
* @return claude installation target
*/
public static SkillsInstallationTarget claude(Path destinationSkillsDir) {
return new SkillsInstallationTarget("local:claude", destinationSkillsDir);
}

/**
* Creates an agent-specific installation target for the <code>github</code> assistant.
*
* @param destinationSkillsDir github destination directory
* @return github installation target
*/
public static SkillsInstallationTarget github(Path destinationSkillsDir) {
return new SkillsInstallationTarget("local:github", destinationSkillsDir);
}

/**
* Creates an agent-specific installation target for the <code>cursor</code> assistant.
*
* @param destinationSkillsDir cursor destination directory
* @return cursor installation target
*/
public static SkillsInstallationTarget cursor(Path destinationSkillsDir) {
return new SkillsInstallationTarget("local:cursor", destinationSkillsDir);
}

/**
* Creates an agent-specific installation target for the <code>opencode</code> assistant.
*
* @param destinationSkillsDir opencode destination directory
* @return opencode installation target
*/
public static SkillsInstallationTarget opencode(Path destinationSkillsDir) {
return new SkillsInstallationTarget("local:opencode", destinationSkillsDir);
}

/**
* Creates an agent-specific installation target for the <code>gemini</code> assistant.
*
* @param destinationSkillsDir gemini destination directory
* @return gemini installation target
*/
public static SkillsInstallationTarget gemini(Path destinationSkillsDir) {
return new SkillsInstallationTarget("local:gemini", destinationSkillsDir);
}

/**
* Creates an agent-specific installation target for the <code>windsurf</code> assistant.
*
* @param destinationSkillsDir windsurf destination directory
* @return windsurf installation target
*/
public static SkillsInstallationTarget windsurf(Path destinationSkillsDir) {
return new SkillsInstallationTarget("local:windsurf", destinationSkillsDir);
}

/**
* Creates an agent-specific installation target for the <code>pi agent</code> assistant.
*
* @param destinationSkillsDir pi agent destination directory
* @return pi agent installation target
*/
public static SkillsInstallationTarget pi(Path destinationSkillsDir) {
return new SkillsInstallationTarget("local:pi", destinationSkillsDir);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,13 @@
public class SkillsInstallationTargetResolver {

private static final String[] LOCAL_SKILLS_PATH = {".agents", "skills"};
private static final String[] CLAUDE_PATH = {".claude", "skills"};
private static final String[] GITHUB_PATH = {".github", "skills"};
private static final String[] CURSOR_PATH = {".cursor", "skills"};
private static final String[] OPENCODE_PATH = {".opencode", "skills"};
private static final String[] GEMINI_PATH = {".gemini", "skills"};
private static final String[] WINDSURF_PATH = {".windsurf", "skills"};
private static final String[] PI_PATH = {".pi", "skills"};
private static final String GLOBAL_MODE_NOT_IMPLEMENTED =
"Global skills installation is not implemented yet. Run 'flamingock install-skills' to install into ./.agents/skills.";

Expand All @@ -40,18 +47,65 @@ public SkillsInstallationTargetResolver() {
}

/**
* Resolves the skills installation targets for the requested mode.
* Resolves the skills installation targets for the requested mode (default agent).
*
* @param workingDirectory current command working directory
* @param global whether global mode was requested
* @return resolved installation targets
*/
public List<SkillsInstallationTarget> resolveTargets(Path workingDirectory, boolean global) {
return resolveTargets(workingDirectory, global, null);
}

/**
* Resolves the skills installation targets for the requested mode and agent.
*
* @param workingDirectory current command working directory
* @param global whether global mode was requested
* @param agent target AI assistant identifier (claude, github, cursor, opencode, gemini, windsurf, pi)
* @return resolved installation targets
*/
public List<SkillsInstallationTarget> resolveTargets(Path workingDirectory, boolean global, String agent) {
if (global) {
throw new IllegalStateException(GLOBAL_MODE_NOT_IMPLEMENTED);
}

Path destination = directoryResolver.resolveDirectory(workingDirectory, LOCAL_SKILLS_PATH);
return List.of(SkillsInstallationTarget.local(destination));
if (agent == null) {
Path destination = directoryResolver.resolveDirectory(workingDirectory, LOCAL_SKILLS_PATH);
return List.of(SkillsInstallationTarget.agents(destination));
}

return switch (agent) {
case "claude" -> {
Path destination = directoryResolver.resolveDirectory(workingDirectory, CLAUDE_PATH);
yield List.of(SkillsInstallationTarget.claude(destination));
}
case "github" -> {
Path destination = directoryResolver.resolveDirectory(workingDirectory, GITHUB_PATH);
yield List.of(SkillsInstallationTarget.github(destination));
}
case "cursor" -> {
Path destination = directoryResolver.resolveDirectory(workingDirectory, CURSOR_PATH);
yield List.of(SkillsInstallationTarget.cursor(destination));
}
case "opencode" -> {
Path destination = directoryResolver.resolveDirectory(workingDirectory, OPENCODE_PATH);
yield List.of(SkillsInstallationTarget.opencode(destination));
}
case "gemini" -> {
Path destination = directoryResolver.resolveDirectory(workingDirectory, GEMINI_PATH);
yield List.of(SkillsInstallationTarget.gemini(destination));
}
case "windsurf" -> {
Path destination = directoryResolver.resolveDirectory(workingDirectory, WINDSURF_PATH);
yield List.of(SkillsInstallationTarget.windsurf(destination));
}
case "pi" -> {
Path destination = directoryResolver.resolveDirectory(workingDirectory, PI_PATH);
yield List.of(SkillsInstallationTarget.pi(destination));
}
default -> throw new IllegalStateException(
"Unsupported agent: '" + agent + "'. Supported values: claude, github, cursor, opencode, gemini, windsurf, pi.");
};
}
}
Loading
Loading