Skip to content

Commit 7be5d5e

Browse files
authored
Merge pull request #316 from syncable-dev/develop
Develop
2 parents 0fd3212 + c3119d4 commit 7be5d5e

14 files changed

Lines changed: 892 additions & 135 deletions

File tree

.claude-plugin/marketplace.json

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
{
2+
"name": "syncable",
3+
"owner": {
4+
"name": "Syncable",
5+
"email": "support@syncable.dev"
6+
},
7+
"metadata": {
8+
"description": "Syncable CLI skills for AI coding agents — project analysis, security, vulnerabilities, dependencies, IaC validation, and cloud deployment.",
9+
"version": "0.1.0"
10+
},
11+
"plugins": [
12+
{
13+
"name": "syncable-cli-skills",
14+
"source": "./installer/plugins/syncable-cli-skills",
15+
"description": "Syncable CLI skills for project analysis, security scanning, vulnerability detection, dependency auditing, IaC validation, Kubernetes optimization, and cloud deployment.",
16+
"version": "0.1.0",
17+
"author": {
18+
"name": "Syncable",
19+
"email": "support@syncable.dev"
20+
},
21+
"homepage": "https://syncable.dev",
22+
"repository": "https://github.com/syncable-dev/syncable-cli",
23+
"license": "MIT",
24+
"keywords": ["syncable", "devops", "security", "deployment", "kubernetes", "docker", "iac"]
25+
}
26+
]
27+
}

installer-investigation-report.md

Lines changed: 358 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,358 @@
1+
# Syncable CLI Installer Investigation Report
2+
3+
**Date:** March 29, 2026
4+
**Scope:** `npx syncable-cli-skills` installation failures across Claude Code, Gemini CLI, and Codex
5+
6+
---
7+
8+
## Executive Summary
9+
10+
After investigating the installer code against the official documentation for all three agents, I identified **5 critical bugs** and **3 UX problems** that explain the user-reported failures. The root causes fall into three categories:
11+
12+
1. **Claude Code:** The installer uses an undocumented/incorrect plugin registration method. It writes directly to internal JSON files instead of using the official CLI or settings system. Users must manually enable the plugin because the installer never actually registers it correctly.
13+
14+
2. **Gemini CLI:** The installer writes skills to the wrong directory (`~/.gemini/antigravity/skills/`). Gemini CLI discovers skills from `~/.gemini/skills/` (user-level) or `.gemini/skills/` (project-level), not from a profile subdirectory. The "antigravity" profile path is not a documented skill location.
15+
16+
3. **All agents:** `sync-ctl` installs to `~/.cargo/bin/` but agents spawn fresh shells that may not have this directory in PATH. The installer only verifies sync-ctl within its own Node.js process (where it temporarily adds cargo/bin to PATH), creating a false positive.
17+
18+
---
19+
20+
## Bug #1: Claude Code Plugin Registration is Completely Wrong
21+
22+
### What the installer does (WRONG)
23+
24+
The installer (`transformers/claude.ts`) manually writes to three internal files:
25+
26+
```
27+
~/.claude/plugins/cache/syncable/syncable-cli-skills/0.1.0/skills/...
28+
~/.claude/plugins/installed_plugins.json
29+
~/.claude/plugins/known_marketplaces.json
30+
```
31+
32+
This is the `installClaudePlugin()` function at line 143 of `transformers/claude.ts`. It creates a `plugin.json` manifest, writes skill files to a cache directory, then directly manipulates `installed_plugins.json` and `known_marketplaces.json`.
33+
34+
### Why this is wrong
35+
36+
According to the official Claude Code documentation:
37+
38+
- **Plugins are installed via CLI:** `claude plugin install plugin-name@marketplace-name`
39+
- **Plugins are enabled via `enabledPlugins` in `~/.claude/settings.json`:** `{"enabledPlugins": {"syncable-cli-skills@syncable": true}}`
40+
- **Marketplace plugins are cached to `~/.claude/plugins/cache/`** but this is an internal mechanism managed by Claude Code itself, not meant to be written to directly
41+
- **There is no file called `installed_plugins.json`** in the documented plugin system. The installer invented this file. Plugins are tracked through `enabledPlugins` in settings.json
42+
- **`known_marketplaces.json`** is not the documented way to register marketplaces. The correct method is either `claude plugin marketplace add` or `extraKnownMarketplaces` in `.claude/settings.json`
43+
44+
### Why users have to manually enable
45+
46+
Because the installer writes to non-standard files that Claude Code doesn't actually read for plugin activation. The plugin files exist on disk but Claude Code doesn't know they're "enabled" because `enabledPlugins` in `settings.json` was never updated.
47+
48+
### The fix
49+
50+
**Option A (Recommended): Use the CLI for programmatic installation**
51+
52+
```bash
53+
# Register the marketplace
54+
claude plugin marketplace add syncable-dev/syncable-cli
55+
56+
# Install the plugin
57+
claude plugin install syncable-cli-skills@syncable --scope user
58+
```
59+
60+
This is the documented, supported way. It handles caching, registration, and enabling all at once.
61+
62+
**Option B: Write to settings.json directly**
63+
64+
If you need to bypass the CLI (e.g., Claude Code isn't running), write the plugin to the cache AND update `~/.claude/settings.json`:
65+
66+
```json
67+
{
68+
"enabledPlugins": {
69+
"syncable-cli-skills@syncable": true
70+
}
71+
}
72+
```
73+
74+
But you'd still need the marketplace registered properly. Option A is much safer.
75+
76+
**Option C: Use `--plugin-dir` for local plugins**
77+
78+
If the goal is to load a local plugin without a marketplace:
79+
80+
```bash
81+
claude --plugin-dir ~/.local/share/syncable/plugin
82+
```
83+
84+
This works for development/testing but isn't persistent across sessions.
85+
86+
---
87+
88+
## Bug #2: Gemini CLI Skills Go to the Wrong Directory
89+
90+
### What the installer does (WRONG)
91+
92+
The installer (`agents/gemini.ts`, line 12-38) searches for a `~/.gemini/antigravity/skills/` directory, or any profile subdirectory with a `skills/` folder:
93+
94+
```typescript
95+
function findGeminiSkillsDir(): string {
96+
const antigravitySkills = path.join(geminiDir, 'antigravity', 'skills');
97+
if (fs.existsSync(antigravitySkills)) {
98+
return antigravitySkills; // WRONG PATH
99+
}
100+
// Falls back to: ~/.gemini/antigravity/skills/
101+
}
102+
```
103+
104+
### Why this is wrong
105+
106+
According to the official Gemini CLI documentation, skills are discovered from these locations (in precedence order):
107+
108+
1. **Workspace skills:** `.gemini/skills/` or `.agents/skills/` (project-level)
109+
2. **User skills:** `~/.gemini/skills/` or `~/.agents/skills/` (global)
110+
3. **Extension skills:** bundled within installed extensions
111+
112+
There is **no profile subdirectory** in the skill discovery path. `~/.gemini/antigravity/` is not a documented skill location. The correct user-level path is simply `~/.gemini/skills/`.
113+
114+
### Why it works for some users but not others
115+
116+
If a user happens to have configured Gemini with custom profile settings that somehow include the "antigravity" path, it might work. But for default Gemini CLI installations, skills placed in `~/.gemini/antigravity/skills/` are invisible to Gemini because it only scans `~/.gemini/skills/`.
117+
118+
### The fix
119+
120+
Update `agents/gemini.ts`:
121+
122+
```typescript
123+
export const geminiAgent: AgentConfig = {
124+
name: 'gemini',
125+
displayName: 'Gemini CLI',
126+
installType: 'global',
127+
detect: async () => {
128+
return fs.existsSync(path.join(os.homedir(), '.gemini'))
129+
|| await commandExists('gemini');
130+
},
131+
getSkillPath: () => {
132+
// Gemini CLI discovers user skills from ~/.gemini/skills/
133+
// The .agents/skills/ alias also works but .gemini/skills/ is primary
134+
return path.join(os.homedir(), '.gemini', 'skills');
135+
},
136+
};
137+
```
138+
139+
Remove the entire `findGeminiSkillsDir()` function with its profile/antigravity logic.
140+
141+
---
142+
143+
## Bug #3: sync-ctl PATH Not Available to Agents
144+
145+
### The problem
146+
147+
When the installer runs `cargo install syncable-cli`, the binary goes to `~/.cargo/bin/sync-ctl`. The installer then calls `prependCargoToPath()` which does:
148+
149+
```typescript
150+
export function prependCargoToPath(): void {
151+
process.env.PATH = `${cargoBinDir()}${path.delimiter}${process.env.PATH}`;
152+
}
153+
```
154+
155+
This only modifies the **current Node.js process** PATH. When the installer later runs `sync-ctl --version` to verify, it succeeds because the installer's own process has the modified PATH. But when an agent (Claude, Gemini, Codex) spawns a shell to run a skill command, that shell gets its PATH from the user's shell profile (`.bashrc`, `.zshrc`, `.profile`). If Rust was just installed or `~/.cargo/bin` isn't in their shell profile, `sync-ctl: command not found`.
156+
157+
This is exactly the bug users report: `which sync-ctl` works in their terminal (because their terminal has sourced their profile) but the agent says it's not available (because the agent's shell may have a different PATH, or the user opened a new terminal without sourcing the profile after installing Rust).
158+
159+
### The fix
160+
161+
Add a post-install verification AND a PATH setup helper:
162+
163+
```typescript
164+
// After installing sync-ctl, verify it's accessible from a fresh shell
165+
async function verifySyncCtlInPath(): Promise<boolean> {
166+
try {
167+
// Spawn a fresh login shell to check if sync-ctl is in the default PATH
168+
const shell = process.env.SHELL || '/bin/bash';
169+
await execCommand(`${shell} -l -c "which sync-ctl"`);
170+
return true;
171+
} catch {
172+
return false;
173+
}
174+
}
175+
176+
// If not in PATH, offer to create a symlink in /usr/local/bin
177+
async function ensureSyncCtlInPath(): Promise<void> {
178+
const inPath = await verifySyncCtlInPath();
179+
if (inPath) return;
180+
181+
const syncCtlPath = path.join(cargoBinDir(), 'sync-ctl');
182+
if (!fs.existsSync(syncCtlPath)) return;
183+
184+
console.log(chalk.yellow('\n sync-ctl is installed but not in your shell PATH.'));
185+
console.log(chalk.yellow(' AI agents may not be able to find it.\n'));
186+
187+
// Option 1: Symlink to /usr/local/bin
188+
const { fix } = await inquirer.prompt([{
189+
type: 'list',
190+
name: 'fix',
191+
message: 'How would you like to fix this?',
192+
choices: [
193+
{ name: 'Create symlink in /usr/local/bin (recommended)', value: 'symlink' },
194+
{ name: 'Add ~/.cargo/bin to shell profile', value: 'profile' },
195+
{ name: 'Skip (I will fix it manually)', value: 'skip' },
196+
],
197+
}]);
198+
199+
if (fix === 'symlink') {
200+
try {
201+
await execCommand(`sudo ln -sf ${syncCtlPath} /usr/local/bin/sync-ctl`);
202+
console.log(chalk.green(' Symlink created successfully.'));
203+
} catch {
204+
console.log(chalk.red(' Failed to create symlink. Try manually:'));
205+
console.log(chalk.dim(` sudo ln -sf ${syncCtlPath} /usr/local/bin/sync-ctl`));
206+
}
207+
} else if (fix === 'profile') {
208+
const shellProfile = getShellProfile();
209+
const line = 'export PATH="$HOME/.cargo/bin:$PATH"';
210+
try {
211+
fs.appendFileSync(shellProfile, `\n${line}\n`);
212+
console.log(chalk.green(` Added to ${shellProfile}. Restart your terminal.`));
213+
} catch {
214+
console.log(chalk.red(` Failed. Add this to ${shellProfile} manually:`));
215+
console.log(chalk.dim(` ${line}`));
216+
}
217+
}
218+
}
219+
```
220+
221+
Additionally, **each skill's SKILL.md should include a fallback PATH** so the agent tries `~/.cargo/bin` explicitly:
222+
223+
```markdown
224+
## Prerequisites
225+
- sync-ctl binary (check with: `~/.cargo/bin/sync-ctl --version` or `sync-ctl --version`)
226+
227+
If sync-ctl is not found, try: `export PATH="$HOME/.cargo/bin:$PATH"` then retry.
228+
```
229+
230+
---
231+
232+
## Bug #4: Codex Skills Require `--enable skills` Flag
233+
234+
### The problem
235+
236+
The installer places skills in `~/.agents/skills/` which is correct per the Codex documentation. However, Codex requires the user to explicitly run with `--enable skills` for skills to be active:
237+
238+
> "You have to run Codex with the `--enable skills` option."
239+
240+
The installer never tells users this. They install skills, open Codex, and the skills don't work because they're not enabled.
241+
242+
### The fix
243+
244+
Add a post-install message for Codex users:
245+
246+
```typescript
247+
if (agent.name === 'codex') {
248+
console.log(chalk.cyan('\n NOTE: To use skills in Codex, run:'));
249+
console.log(chalk.cyan(' codex --enable skills'));
250+
console.log(chalk.cyan(' Or invoke explicitly with: $syncable-analyze\n'));
251+
}
252+
```
253+
254+
---
255+
256+
## Bug #5: Gemini SKILL.md Format Missing Required Fields
257+
258+
### What the installer produces
259+
260+
The Gemini transformer (`transformers/gemini.ts`) produces:
261+
262+
```markdown
263+
---
264+
name: syncable-analyze
265+
description: Run sync-ctl analyze for project analysis...
266+
---
267+
268+
[skill body]
269+
```
270+
271+
### What Gemini CLI expects
272+
273+
Per the documentation, Gemini CLI loads the **name and description** from frontmatter at startup and injects them into the system prompt. The model then decides whether to activate a skill. This format is actually correct for basic discovery.
274+
275+
However, the skill names use `syncable-` prefix which creates directory names like `syncable-analyze/SKILL.md`. This is fine structurally, but the descriptions in the skills should be more explicit about what triggers them, since Gemini only loads name+description initially and activates the full SKILL.md on demand.
276+
277+
### Minor fix
278+
279+
Ensure descriptions are optimized for Gemini's lazy-loading behavior (name + description only at startup):
280+
281+
```typescript
282+
// In transformers/gemini.ts, ensure description is activation-friendly
283+
const content = `---
284+
name: "${skillName}"
285+
description: "${skill.frontmatter.description}"
286+
---
287+
288+
${skill.body}`;
289+
```
290+
291+
---
292+
293+
## UX Problem #1: No Post-Install Verification
294+
295+
The installer shows "Setup complete!" without verifying that the skills actually work. It should:
296+
297+
1. Verify sync-ctl is accessible from a fresh shell
298+
2. Verify skill files exist in the correct agent directories
299+
3. For Claude Code: verify the plugin appears in `enabledPlugins`
300+
4. Print agent-specific instructions (like Codex's `--enable skills`)
301+
302+
---
303+
304+
## UX Problem #2: No Error Recovery
305+
306+
If the installation partially fails (e.g., skills install for Claude but Gemini path is wrong), there's no way for users to diagnose what went wrong. The `status` command only counts files, it doesn't verify they're in the right place or format.
307+
308+
### Suggested improvement
309+
310+
Add a `syncable-cli-skills doctor` command that:
311+
- Checks if sync-ctl is in PATH (from a fresh shell, not the current process)
312+
- For each agent, verifies skills are in the documented discovery paths
313+
- For Claude Code, checks if the plugin is actually enabled in settings.json
314+
- For Codex, checks if `--enable skills` configuration exists
315+
316+
---
317+
318+
## UX Problem #3: Claude Plugin Requires Manual Enable
319+
320+
Even if the plugin registration is fixed (Bug #1), the current UX flow is:
321+
322+
1. User runs `npx syncable-cli-skills`
323+
2. Installer says "Setup complete!"
324+
3. User opens Claude Code
325+
4. Skills don't work
326+
5. User has to figure out they need to go to `/plugin` and enable it
327+
328+
### The ideal flow
329+
330+
1. User runs `npx syncable-cli-skills`
331+
2. Installer detects Claude Code is installed
332+
3. Installer runs `claude plugin install syncable-cli-skills@syncable` (which auto-enables)
333+
4. Installer confirms: "Skills installed and enabled for Claude Code"
334+
5. User opens Claude Code, skills work immediately
335+
336+
---
337+
338+
## Summary of Required Code Changes
339+
340+
| File | Bug | Change Required |
341+
|------|-----|----------------|
342+
| `transformers/claude.ts` | #1 | Replace `installClaudePlugin()` with `claude plugin install` CLI call or write to `enabledPlugins` in `settings.json` |
343+
| `agents/gemini.ts` | #2 | Change `getSkillPath()` to return `~/.gemini/skills/` (remove `findGeminiSkillsDir` and all antigravity/profile logic) |
344+
| `src/index.ts` | #3 | Add `verifySyncCtlInPath()` post-install check using a fresh login shell |
345+
| `src/index.ts` | #3 | Add PATH fix helper (symlink or profile edit) |
346+
| `src/index.ts` | #4 | Add Codex-specific post-install message about `--enable skills` |
347+
| `skills/*.md` | #3 | Add `~/.cargo/bin/sync-ctl` fallback path in prerequisites |
348+
| New: `doctor` command | UX | Add diagnostic command to verify installation health |
349+
350+
---
351+
352+
## Priority Order
353+
354+
1. **Bug #1 (Claude plugin)** - Highest impact, affects all Claude Code users
355+
2. **Bug #2 (Gemini path)** - Affects all Gemini CLI users
356+
3. **Bug #3 (PATH issue)** - Affects users who freshly install Rust
357+
4. **Bug #4 (Codex enable)** - Simple fix, just needs a message
358+
5. **UX improvements** - Important but not blocking

0 commit comments

Comments
 (0)