ima-claude 2.9.0 → 2.13.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +20 -15
- package/dist/cli.js +385 -17
- package/package.json +1 -1
- package/platforms/gemini/adapter.ts +443 -0
- package/platforms/gemini/gemini-extension.json +17 -0
- package/platforms/gemini/hooks-translator.py +66 -0
- package/platforms/shared/detector.ts +5 -1
- package/plugins/ima-claude/.claude-plugin/plugin.json +2 -2
- package/plugins/ima-claude/skills/gh-cli/SKILL.md +286 -0
- package/plugins/ima-claude/skills/ima-doc2pdf/SKILL.md +242 -0
- package/plugins/ima-claude/skills/ima-doc2pdf/references/formatting-spec.md +88 -0
- package/plugins/ima-claude/skills/ima-doc2pdf/scripts/docx_utils.py +21 -0
- package/plugins/ima-claude/skills/ima-doc2pdf/scripts/extract_docx.py +384 -0
- package/plugins/ima-claude/skills/ima-doc2pdf/scripts/generate_pdf.py +663 -0
- package/plugins/ima-claude/skills/mcp-gitea/SKILL.md +358 -0
- package/plugins/ima-claude/skills/mcp-github/SKILL.md +200 -0
- package/plugins/ima-claude/skills/mcp-qdrant/SKILL.md +21 -10
package/README.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
FP patterns, architecture guidance, and team standards for AI coding agents.
|
|
4
4
|
|
|
5
|
-
**Supports Claude Code and
|
|
5
|
+
**Supports Claude Code, Junie CLI, and Gemini CLI** — with an extensible adapter architecture ready for more platforms.
|
|
6
6
|
|
|
7
7
|
Built by [Independent Medical Alliance](https://imahealth.org) (formerly FLCCC)
|
|
8
8
|
|
|
@@ -38,9 +38,9 @@ claude plugin update ima-claude
|
|
|
38
38
|
|
|
39
39
|
Or use `/plugin` inside Claude Code to manage updates interactively via the **Installed** tab.
|
|
40
40
|
|
|
41
|
-
### Junie CLI — Multi-Platform Installer
|
|
41
|
+
### Junie CLI / Gemini CLI — Multi-Platform Installer
|
|
42
42
|
|
|
43
|
-
For Junie
|
|
43
|
+
For Junie, Gemini, or any non-plugin platform, use the interactive CLI installer:
|
|
44
44
|
|
|
45
45
|
```bash
|
|
46
46
|
npx ima-claude install
|
|
@@ -48,7 +48,7 @@ npx ima-claude install
|
|
|
48
48
|
|
|
49
49
|
The installer auto-detects which platforms are available and walks you through installation:
|
|
50
50
|
|
|
51
|
-
1. **Detects platforms** — scans for Claude Code (`~/.claude`)
|
|
51
|
+
1. **Detects platforms** — scans for Claude Code (`~/.claude`), Junie CLI (`~/.junie`), and Gemini CLI (`~/.gemini`)
|
|
52
52
|
2. **Shows preview** — lists all skills, agents, and platform-specific items to install
|
|
53
53
|
3. **Allows exclusions** — skip specific skills or agents you don't need
|
|
54
54
|
4. **Installs with feedback** — step-by-step progress for each item
|
|
@@ -57,20 +57,21 @@ You can also target a specific platform directly:
|
|
|
57
57
|
|
|
58
58
|
```bash
|
|
59
59
|
npx ima-claude install --target junie # Junie only
|
|
60
|
+
npx ima-claude install --target gemini # Gemini CLI only
|
|
60
61
|
npx ima-claude install --target claude # Claude Code only (plugin recommended instead)
|
|
61
62
|
npx ima-claude detect # Show detected platforms
|
|
62
63
|
```
|
|
63
64
|
|
|
64
|
-
**What's different
|
|
65
|
+
**What's different per platform?**
|
|
65
66
|
|
|
66
|
-
| | Claude Code | Junie CLI |
|
|
67
|
-
|
|
68
|
-
| **Skills** | Plugin system (auto) | Copied to `~/.junie/skills/` |
|
|
69
|
-
| **Agents** | Plugin system (auto) |
|
|
70
|
-
| **Hooks** |
|
|
71
|
-
| **Guidelines** | Plugin's `CLAUDE.md` injection | Generated `AGENTS.md`
|
|
67
|
+
| | Claude Code | Junie CLI | Gemini CLI |
|
|
68
|
+
|---|---|---|---|
|
|
69
|
+
| **Skills** | Plugin system (auto) | Copied to `~/.junie/skills/` | Copied to `~/.gemini/skills/` |
|
|
70
|
+
| **Agents** | Plugin system (auto) | Strips `permissionMode` | Strips `model` + `permissionMode`, maps tool names |
|
|
71
|
+
| **Hooks** | 24 Python hook scripts | No hook system — behavioral guidelines | Translated events + tool names, translator shim |
|
|
72
|
+
| **Guidelines** | Plugin's `CLAUDE.md` injection | Generated `AGENTS.md` | Generated `GEMINI.md` |
|
|
72
73
|
|
|
73
|
-
Junie doesn't support hooks, so
|
|
74
|
+
Junie doesn't support hooks, so behaviors become guidelines in `AGENTS.md`. Gemini has hooks but uses different event names (`BeforeTool`/`AfterTool`) and tool names (`run_shell_command`, `replace`, etc.) — a translator shim normalizes input so all existing hooks work unmodified.
|
|
74
75
|
|
|
75
76
|
### Adding New Platforms
|
|
76
77
|
|
|
@@ -80,7 +81,8 @@ The installer uses an adapter pattern. Adding support for a new platform (e.g.,
|
|
|
80
81
|
platforms/
|
|
81
82
|
├── shared/types.ts # PlatformAdapter interface
|
|
82
83
|
├── claude/adapter.ts # Claude Code adapter
|
|
83
|
-
|
|
84
|
+
├── junie/adapter.ts # Junie CLI adapter
|
|
85
|
+
└── gemini/adapter.ts # Gemini CLI adapter
|
|
84
86
|
```
|
|
85
87
|
|
|
86
88
|
See [platforms/shared/types.ts](platforms/shared/types.ts) for the interface contract.
|
|
@@ -89,7 +91,7 @@ See [platforms/shared/types.ts](platforms/shared/types.ts) for the interface con
|
|
|
89
91
|
|
|
90
92
|
## What's Included
|
|
91
93
|
|
|
92
|
-
- **Multi-Platform Installer**: Interactive CLI with auto-detection, install preview, and per-item exclusion — supports Claude Code
|
|
94
|
+
- **Multi-Platform Installer**: Interactive CLI with auto-detection, install preview, and per-item exclusion — supports Claude Code, Junie CLI, and Gemini CLI
|
|
93
95
|
- **48 Skills**: Foundational + FP implementation + domain expert + integration + meta-skills
|
|
94
96
|
- **6 Named Agents**: Explorer (haiku), Implementer (sonnet), Reviewer (sonnet), Tester (sonnet), WP Developer (sonnet), Memory (sonnet) — enforced constraints
|
|
95
97
|
- **23 Hooks**: Automatic behavioral enforcement (security, memory, workflow, Serena, code quality) — translated to guidelines for platforms without hook support
|
|
@@ -102,7 +104,7 @@ See [platforms/shared/types.ts](platforms/shared/types.ts) for the interface con
|
|
|
102
104
|
|
|
103
105
|
## Prerequisites
|
|
104
106
|
|
|
105
|
-
- [Claude Code](https://claude.ai/code)
|
|
107
|
+
- [Claude Code](https://claude.ai/code), [Junie CLI](https://www.jetbrains.com/help/idea/junie.html), or [Gemini CLI](https://github.com/google-gemini/gemini-cli) installed
|
|
106
108
|
|
|
107
109
|
## MCP Servers (Highly Recommended)
|
|
108
110
|
|
|
@@ -200,6 +202,9 @@ ima-claude includes skills that teach Claude how to use each MCP server effectiv
|
|
|
200
202
|
| **mcp-context7** | Library documentation lookup strategies |
|
|
201
203
|
| **mcp-sequential** | Structured reasoning workflows |
|
|
202
204
|
| **mcp-atlassian** | Jira & Confluence operations (Claude's bundled integration) |
|
|
205
|
+
| **mcp-gitea** | Internal Git management: PRs, issues, releases, branches, tags, wikis, CI/CD actions |
|
|
206
|
+
| **mcp-github** | FOSS/public repo management: PRs, issues, code review, repo search (`gh` CLI fallback) |
|
|
207
|
+
| **gh-cli** | GitHub CLI (`gh`) for PRs, issues, releases, Actions, code review, search, and raw API access |
|
|
203
208
|
| ~~**compound-bridge**~~ | Compound Engineering integration — **deprecated**, only useful if your team actively uses Compound Engineering |
|
|
204
209
|
|
|
205
210
|
### Session Management Skills
|
package/dist/cli.js
CHANGED
|
@@ -11,7 +11,7 @@ var HOOKS_DIR = join(CLAUDE_DIR, "hooks");
|
|
|
11
11
|
var COMMANDS_DIR = join(CLAUDE_DIR, "commands");
|
|
12
12
|
var RULES_DIR = join(CLAUDE_DIR, "rules");
|
|
13
13
|
var SETTINGS_FILE = join(CLAUDE_DIR, "settings.json");
|
|
14
|
-
var VERSION = "2.
|
|
14
|
+
var VERSION = "2.13.0";
|
|
15
15
|
var colors = {
|
|
16
16
|
reset: "\x1B[0m",
|
|
17
17
|
bright: "\x1B[1m",
|
|
@@ -107,6 +107,9 @@ var SKILLS_TO_INSTALL = [
|
|
|
107
107
|
"compound-bridge",
|
|
108
108
|
// MCP integration skills
|
|
109
109
|
"mcp-atlassian",
|
|
110
|
+
"mcp-gitea",
|
|
111
|
+
"mcp-github",
|
|
112
|
+
"gh-cli",
|
|
110
113
|
"mcp-tavily",
|
|
111
114
|
"mcp-context7",
|
|
112
115
|
"mcp-serena",
|
|
@@ -684,15 +687,380 @@ var JunieAdapter = class {
|
|
|
684
687
|
}
|
|
685
688
|
};
|
|
686
689
|
|
|
690
|
+
// platforms/gemini/adapter.ts
|
|
691
|
+
import { join as join4, dirname as dirname2 } from "path";
|
|
692
|
+
import { homedir as homedir3 } from "os";
|
|
693
|
+
import { existsSync as existsSync4, readdirSync as readdirSync4, statSync as statSync4, readFileSync as readFileSync3, writeFileSync as writeFileSync3, copyFileSync as copyFileSync3 } from "fs";
|
|
694
|
+
import { fileURLToPath } from "url";
|
|
695
|
+
var __filename = fileURLToPath(import.meta.url);
|
|
696
|
+
var __dirname2 = dirname2(__filename);
|
|
697
|
+
var GEMINI_DIR = join4(homedir3(), ".gemini");
|
|
698
|
+
var GEMINI_SKILLS_DIR = join4(GEMINI_DIR, "skills");
|
|
699
|
+
var GEMINI_AGENTS_DIR = join4(GEMINI_DIR, "agents");
|
|
700
|
+
var GEMINI_HOOKS_DIR = join4(GEMINI_DIR, "hooks");
|
|
701
|
+
var GEMINI_SETTINGS_FILE = join4(GEMINI_DIR, "settings.json");
|
|
702
|
+
var GEMINI_GUIDELINES_FILE = join4(GEMINI_DIR, "GEMINI.md");
|
|
703
|
+
var TOOL_MAP = {
|
|
704
|
+
Bash: "run_shell_command",
|
|
705
|
+
Read: "read_file",
|
|
706
|
+
Edit: "replace",
|
|
707
|
+
Write: "write_file",
|
|
708
|
+
Glob: "glob",
|
|
709
|
+
Grep: "grep_search",
|
|
710
|
+
LS: "list_directory",
|
|
711
|
+
WebSearch: "google_web_search",
|
|
712
|
+
WebFetch: "web_fetch",
|
|
713
|
+
ExitPlanMode: "exit_plan_mode"
|
|
714
|
+
};
|
|
715
|
+
var EVENT_MAP = {
|
|
716
|
+
PreToolUse: "BeforeTool",
|
|
717
|
+
PostToolUse: "AfterTool",
|
|
718
|
+
UserPromptSubmit: "BeforeAgent",
|
|
719
|
+
SessionStart: "SessionStart"
|
|
720
|
+
};
|
|
721
|
+
function parseFrontmatter2(content) {
|
|
722
|
+
const match = content.match(/^---\n([\s\S]*?)\n---\n?([\s\S]*)$/);
|
|
723
|
+
if (!match) return { frontmatter: {}, body: content };
|
|
724
|
+
const frontmatter = {};
|
|
725
|
+
for (const line of match[1].split("\n")) {
|
|
726
|
+
const colonIdx = line.indexOf(":");
|
|
727
|
+
if (colonIdx === -1) continue;
|
|
728
|
+
const key = line.slice(0, colonIdx).trim();
|
|
729
|
+
const value = line.slice(colonIdx + 1).trim();
|
|
730
|
+
if (key) frontmatter[key] = value;
|
|
731
|
+
}
|
|
732
|
+
return { frontmatter, body: match[2] };
|
|
733
|
+
}
|
|
734
|
+
function serializeFrontmatter2(frontmatter, body) {
|
|
735
|
+
const lines = Object.entries(frontmatter).map(([k, v]) => `${k}: ${v}`);
|
|
736
|
+
return `---
|
|
737
|
+
${lines.join("\n")}
|
|
738
|
+
---
|
|
739
|
+
${body}`;
|
|
740
|
+
}
|
|
741
|
+
function mapToolName(claudeName) {
|
|
742
|
+
return TOOL_MAP[claudeName] ?? claudeName;
|
|
743
|
+
}
|
|
744
|
+
function transformAgentForGemini(content) {
|
|
745
|
+
const { frontmatter, body } = parseFrontmatter2(content);
|
|
746
|
+
const { permissionMode: _perm, model: _model, ...kept } = frontmatter;
|
|
747
|
+
if (kept.tools) {
|
|
748
|
+
const mapped = kept.tools.split(",").map((t) => t.trim()).map(mapToolName).join(", ");
|
|
749
|
+
kept.tools = mapped;
|
|
750
|
+
}
|
|
751
|
+
return serializeFrontmatter2(kept, body);
|
|
752
|
+
}
|
|
753
|
+
function translateMatcher(matcher) {
|
|
754
|
+
return TOOL_MAP[matcher] ?? matcher;
|
|
755
|
+
}
|
|
756
|
+
function translateHookCommand(command2) {
|
|
757
|
+
const hooksDir = GEMINI_HOOKS_DIR;
|
|
758
|
+
const translatorPath = join4(hooksDir, "hooks-translator.py");
|
|
759
|
+
const match = command2.match(/python3\s+.*\/([^/\s]+\.py)(\s.*)?$/);
|
|
760
|
+
if (!match) return command2;
|
|
761
|
+
const scriptName = match[1];
|
|
762
|
+
const trailingArgs = match[2] ?? "";
|
|
763
|
+
return `python3 ${translatorPath} ${join4(hooksDir, scriptName)}${trailingArgs}`;
|
|
764
|
+
}
|
|
765
|
+
function generateGeminiHooksConfig() {
|
|
766
|
+
const geminiHooks = {};
|
|
767
|
+
for (const [claudeEvent, hookEntries] of Object.entries(HOOKS_CONFIG.hooks)) {
|
|
768
|
+
const geminiEvent = EVENT_MAP[claudeEvent] ?? claudeEvent;
|
|
769
|
+
geminiHooks[geminiEvent] = hookEntries.map(
|
|
770
|
+
(entry) => {
|
|
771
|
+
const translated = {};
|
|
772
|
+
if (entry.matcher) {
|
|
773
|
+
translated.matcher = translateMatcher(entry.matcher);
|
|
774
|
+
}
|
|
775
|
+
translated.hooks = entry.hooks.map((h) => ({
|
|
776
|
+
type: h.type,
|
|
777
|
+
command: translateHookCommand(h.command)
|
|
778
|
+
}));
|
|
779
|
+
return translated;
|
|
780
|
+
}
|
|
781
|
+
);
|
|
782
|
+
}
|
|
783
|
+
return { hooks: geminiHooks };
|
|
784
|
+
}
|
|
785
|
+
function generateGeminiMd() {
|
|
786
|
+
return `# ima-claude: AI Coding Agent Guidelines
|
|
787
|
+
|
|
788
|
+
> Generated by ima-claude v${VERSION} for Gemini CLI.
|
|
789
|
+
> Source: https://github.com/Soabirw/ima-claude
|
|
790
|
+
|
|
791
|
+
## Default Persona: The Practitioner
|
|
792
|
+
|
|
793
|
+
A 25-year software development veteran. FP-first, composition-minded, anti-over-engineering.
|
|
794
|
+
Uses "we" not "I" \u2014 collaborative, humble, light-hearted. "Slow is smooth, smooth is fast."
|
|
795
|
+
|
|
796
|
+
**Philosophy**: Simple > Complex | Evidence > Assumptions | Native > Utilities | MVP > Enterprise
|
|
797
|
+
|
|
798
|
+
---
|
|
799
|
+
|
|
800
|
+
## Memory Routing
|
|
801
|
+
|
|
802
|
+
| Store what | Where | Why |
|
|
803
|
+
|---|---|---|
|
|
804
|
+
| Decisions, preferences, patterns, bugs | Vestige \`smart_ingest\` | Fades naturally if not referenced |
|
|
805
|
+
| Reference material (docs, standards, PRDs) | Qdrant \`qdrant-store\` | Permanent library |
|
|
806
|
+
| Session state, task progress | Serena \`write_memory\` | Project-scoped workbench |
|
|
807
|
+
| Future reminders | Vestige \`intention\` | Surfaces at next session |
|
|
808
|
+
|
|
809
|
+
At session start, check memory before asking questions:
|
|
810
|
+
- Vestige: search for user preferences and project context
|
|
811
|
+
- Vestige: check for pending reminders/intentions
|
|
812
|
+
- Serena: list memories if in a Serena-activated project
|
|
813
|
+
|
|
814
|
+
Auto-store: "I prefer..." \u2192 Vestige preference. "Let's go with X because..." \u2192 Vestige decision. "The reason this failed..." \u2192 Vestige bug.
|
|
815
|
+
|
|
816
|
+
After completing work: store outcome in Vestige, reference material in Qdrant, session state in Serena.
|
|
817
|
+
|
|
818
|
+
---
|
|
819
|
+
|
|
820
|
+
## Orchestrator Protocol
|
|
821
|
+
|
|
822
|
+
You are the Orchestrator. Plan and delegate. Do NOT implement directly.
|
|
823
|
+
- Non-trivial work \u2192 task-planner (decompose) \u2192 task-runner (delegate)
|
|
824
|
+
- Trivial = single file, < 5 lines, no judgment calls
|
|
825
|
+
|
|
826
|
+
---
|
|
827
|
+
|
|
828
|
+
## Available Agents
|
|
829
|
+
|
|
830
|
+
Delegate to named agents \u2014 they enforce tools and permissions automatically.
|
|
831
|
+
|
|
832
|
+
| Agent | Use For |
|
|
833
|
+
|---|---|
|
|
834
|
+
| \`explorer\` | File discovery, codebase exploration |
|
|
835
|
+
| \`implementer\` | Feature dev, bug fixes, refactoring |
|
|
836
|
+
| \`reviewer\` | Code review, security audit, FP checks |
|
|
837
|
+
| \`wp-developer\` | WordPress plugins, themes, WP-CLI, forms |
|
|
838
|
+
| \`memory\` | Memory search, storage, consolidation |
|
|
839
|
+
|
|
840
|
+
---
|
|
841
|
+
|
|
842
|
+
## Code Navigation (Serena)
|
|
843
|
+
|
|
844
|
+
When Serena MCP is available, **prefer Serena over read_file/grep_search for code investigation.** 40-70% token savings.
|
|
845
|
+
|
|
846
|
+
| Instead of | Use |
|
|
847
|
+
|---|---|
|
|
848
|
+
| Read file to understand structure | Serena get_symbols_overview |
|
|
849
|
+
| grep_search for class/function definition | Serena find_symbol |
|
|
850
|
+
| grep_search for callers/references | Serena find_referencing_symbols |
|
|
851
|
+
|
|
852
|
+
Use read_file only when you need the actual implementation body of a known, specific symbol.
|
|
853
|
+
|
|
854
|
+
---
|
|
855
|
+
|
|
856
|
+
## Complex Reasoning
|
|
857
|
+
|
|
858
|
+
Use sequential thinking before acting on:
|
|
859
|
+
- Debugging / root cause analysis / "why is this failing"
|
|
860
|
+
- Trade-off evaluation / "which approach"
|
|
861
|
+
- Architectural decisions / design choices
|
|
862
|
+
- Multi-step investigations where approach may change
|
|
863
|
+
|
|
864
|
+
---
|
|
865
|
+
|
|
866
|
+
## MCP Tool Routing
|
|
867
|
+
|
|
868
|
+
| Signal | Preferred Tool |
|
|
869
|
+
|---|---|
|
|
870
|
+
| "latest", "2025/2026", "what's new" | Tavily search |
|
|
871
|
+
| Library/framework API question | Context7 |
|
|
872
|
+
| URL content extraction | Tavily extract (use advanced for complex pages) |
|
|
873
|
+
|
|
874
|
+
Before web tools: check internal knowledge \u2192 Context7 \u2192 then Tavily.
|
|
875
|
+
Before external lookups: check Vestige memory first.
|
|
876
|
+
|
|
877
|
+
---
|
|
878
|
+
|
|
879
|
+
## Search Preference
|
|
880
|
+
|
|
881
|
+
Always prefer \`rg\` (ripgrep) over grep/find. Faster, respects .gitignore, recursive by default.
|
|
882
|
+
|
|
883
|
+
---
|
|
884
|
+
|
|
885
|
+
## Security
|
|
886
|
+
|
|
887
|
+
- Verify nonce usage and input sanitization in WordPress PHP code
|
|
888
|
+
- Never concatenate user input directly into SQL \u2014 use parameterized queries
|
|
889
|
+
- Check for XSS, CSRF, and OWASP top 10 vulnerabilities in written code
|
|
890
|
+
|
|
891
|
+
---
|
|
892
|
+
|
|
893
|
+
## Code Style
|
|
894
|
+
|
|
895
|
+
- Don't create custom FP utility functions (pipe, compose, curry) \u2014 use language-native patterns or established libraries
|
|
896
|
+
- In WordPress JavaScript context, use jQuery patterns when jQuery is already loaded
|
|
897
|
+
- Prefer Bootstrap utility classes over custom CSS overrides
|
|
898
|
+
- Run \`composer dump-autoload\` after creating new PHP files
|
|
899
|
+
|
|
900
|
+
---
|
|
901
|
+
|
|
902
|
+
## Documentation
|
|
903
|
+
|
|
904
|
+
Follow the three-tier documentation system:
|
|
905
|
+
- **Active** \u2014 Living docs, kept current (README, API docs, architecture)
|
|
906
|
+
- **Archive** \u2014 Historical reference, rarely updated (decisions, post-mortems)
|
|
907
|
+
- **Transient** \u2014 Ephemeral, git-ignored (session notes, scratch)
|
|
908
|
+
`;
|
|
909
|
+
}
|
|
910
|
+
var GeminiAdapter = class {
|
|
911
|
+
name = "gemini";
|
|
912
|
+
displayName = "Gemini CLI";
|
|
913
|
+
configDir = GEMINI_DIR;
|
|
914
|
+
detect() {
|
|
915
|
+
return existsSync4(GEMINI_DIR);
|
|
916
|
+
}
|
|
917
|
+
preview(sourceDir) {
|
|
918
|
+
const skillItems = SKILLS_TO_INSTALL.map((skill) => ({
|
|
919
|
+
name: skill,
|
|
920
|
+
category: "skill",
|
|
921
|
+
destPath: join4(GEMINI_SKILLS_DIR, skill),
|
|
922
|
+
exists: existsSync4(join4(GEMINI_SKILLS_DIR, skill))
|
|
923
|
+
})).filter((item) => existsSync4(join4(sourceDir, "skills", item.name)));
|
|
924
|
+
const agentsDir = join4(sourceDir, "agents");
|
|
925
|
+
const agentItems = existsSync4(agentsDir) ? readdirSync4(agentsDir).filter((f) => f.endsWith(".md")).map((file) => ({
|
|
926
|
+
name: file.replace(/\.md$/, ""),
|
|
927
|
+
category: "agent",
|
|
928
|
+
destPath: join4(GEMINI_AGENTS_DIR, file),
|
|
929
|
+
exists: existsSync4(join4(GEMINI_AGENTS_DIR, file))
|
|
930
|
+
})) : [];
|
|
931
|
+
const hookItems = HOOKS_TO_INSTALL.map((file) => ({
|
|
932
|
+
name: file,
|
|
933
|
+
category: "hook",
|
|
934
|
+
destPath: join4(GEMINI_HOOKS_DIR, file),
|
|
935
|
+
exists: existsSync4(join4(GEMINI_HOOKS_DIR, file))
|
|
936
|
+
}));
|
|
937
|
+
const translatorItem = {
|
|
938
|
+
name: "hooks-translator.py",
|
|
939
|
+
category: "hook",
|
|
940
|
+
destPath: join4(GEMINI_HOOKS_DIR, "hooks-translator.py"),
|
|
941
|
+
exists: existsSync4(join4(GEMINI_HOOKS_DIR, "hooks-translator.py"))
|
|
942
|
+
};
|
|
943
|
+
const guidelineItem = {
|
|
944
|
+
name: "GEMINI.md",
|
|
945
|
+
category: "guideline",
|
|
946
|
+
destPath: GEMINI_GUIDELINES_FILE,
|
|
947
|
+
exists: existsSync4(GEMINI_GUIDELINES_FILE)
|
|
948
|
+
};
|
|
949
|
+
return {
|
|
950
|
+
platform: this.name,
|
|
951
|
+
targetDir: GEMINI_DIR,
|
|
952
|
+
items: [...skillItems, ...agentItems, ...hookItems, translatorItem, guidelineItem]
|
|
953
|
+
};
|
|
954
|
+
}
|
|
955
|
+
installSkills(sourceDir, exclude) {
|
|
956
|
+
ensureDir(GEMINI_SKILLS_DIR);
|
|
957
|
+
const skills = exclude?.length ? SKILLS_TO_INSTALL.filter((s) => !exclude.includes(s)) : SKILLS_TO_INSTALL;
|
|
958
|
+
for (const skill of skills) {
|
|
959
|
+
const src = join4(sourceDir, skill);
|
|
960
|
+
if (existsSync4(src) && statSync4(src).isDirectory()) {
|
|
961
|
+
copyDirRecursive(src, join4(GEMINI_SKILLS_DIR, skill));
|
|
962
|
+
log.step(`skill: ${skill}`);
|
|
963
|
+
}
|
|
964
|
+
}
|
|
965
|
+
}
|
|
966
|
+
installAgents(sourceDir, exclude) {
|
|
967
|
+
ensureDir(GEMINI_AGENTS_DIR);
|
|
968
|
+
const entries = readdirSync4(sourceDir).filter((f) => f.endsWith(".md")).filter((f) => !exclude?.includes(f.replace(/\.md$/, "")));
|
|
969
|
+
for (const file of entries) {
|
|
970
|
+
const content = readFileSync3(join4(sourceDir, file), "utf8");
|
|
971
|
+
const transformed = transformAgentForGemini(content);
|
|
972
|
+
writeFileSync3(join4(GEMINI_AGENTS_DIR, file), transformed);
|
|
973
|
+
log.step(`agent: ${file}`);
|
|
974
|
+
}
|
|
975
|
+
}
|
|
976
|
+
installGuidelines(_pluginRoot) {
|
|
977
|
+
ensureDir(GEMINI_DIR);
|
|
978
|
+
writeFileSync3(GEMINI_GUIDELINES_FILE, generateGeminiMd());
|
|
979
|
+
log.step(`guidelines: ${GEMINI_GUIDELINES_FILE}`);
|
|
980
|
+
}
|
|
981
|
+
installHooks(sourceDir, exclude) {
|
|
982
|
+
ensureDir(GEMINI_HOOKS_DIR);
|
|
983
|
+
const hooks = exclude?.length ? HOOKS_TO_INSTALL.filter((f) => !exclude.includes(f)) : HOOKS_TO_INSTALL;
|
|
984
|
+
for (const file of hooks) {
|
|
985
|
+
const src = join4(sourceDir, file);
|
|
986
|
+
if (existsSync4(src)) {
|
|
987
|
+
copyFileSync3(src, join4(GEMINI_HOOKS_DIR, file));
|
|
988
|
+
log.step(`hook: ${file}`);
|
|
989
|
+
}
|
|
990
|
+
}
|
|
991
|
+
const shimSrc = join4(__dirname2, "hooks-translator.py");
|
|
992
|
+
if (!existsSync4(shimSrc)) {
|
|
993
|
+
throw new Error(`hooks-translator.py not found at ${shimSrc} \u2014 packaging error`);
|
|
994
|
+
}
|
|
995
|
+
copyFileSync3(shimSrc, join4(GEMINI_HOOKS_DIR, "hooks-translator.py"));
|
|
996
|
+
log.step("hook: hooks-translator.py (shim)");
|
|
997
|
+
const hooksConfig = generateGeminiHooksConfig();
|
|
998
|
+
writeFileSync3(join4(GEMINI_HOOKS_DIR, "hooks.json"), JSON.stringify(hooksConfig, null, 2) + "\n");
|
|
999
|
+
log.step("hook: hooks.json (generated for Gemini)");
|
|
1000
|
+
mergeGeminiHooksIntoSettings(hooksConfig);
|
|
1001
|
+
}
|
|
1002
|
+
postInstall() {
|
|
1003
|
+
log.info("Gemini CLI install complete. Verify:");
|
|
1004
|
+
log.info(` Skills: ${GEMINI_SKILLS_DIR}`);
|
|
1005
|
+
log.info(` Agents: ${GEMINI_AGENTS_DIR}`);
|
|
1006
|
+
log.info(` Hooks: ${GEMINI_HOOKS_DIR}`);
|
|
1007
|
+
log.info(` Guidelines: ${GEMINI_GUIDELINES_FILE}`);
|
|
1008
|
+
log.info("Also available as a Gemini extension \u2014 see gemini-extension.json in the repo.");
|
|
1009
|
+
}
|
|
1010
|
+
};
|
|
1011
|
+
function mergeGeminiHooksIntoSettings(hooksConfig) {
|
|
1012
|
+
let settings = {};
|
|
1013
|
+
if (existsSync4(GEMINI_SETTINGS_FILE)) {
|
|
1014
|
+
try {
|
|
1015
|
+
const content = readFileSync3(GEMINI_SETTINGS_FILE, "utf8");
|
|
1016
|
+
settings = JSON.parse(content);
|
|
1017
|
+
} catch {
|
|
1018
|
+
settings = {};
|
|
1019
|
+
}
|
|
1020
|
+
}
|
|
1021
|
+
if (!settings.hooks) {
|
|
1022
|
+
settings.hooks = {};
|
|
1023
|
+
}
|
|
1024
|
+
const settingsHooks = settings.hooks;
|
|
1025
|
+
const newHooks = hooksConfig.hooks;
|
|
1026
|
+
for (const [event, entries] of Object.entries(newHooks)) {
|
|
1027
|
+
if (!settingsHooks[event]) {
|
|
1028
|
+
settingsHooks[event] = entries;
|
|
1029
|
+
continue;
|
|
1030
|
+
}
|
|
1031
|
+
const existing = settingsHooks[event];
|
|
1032
|
+
for (const entry of entries) {
|
|
1033
|
+
if (entry.matcher) {
|
|
1034
|
+
const idx = existing.findIndex((h) => h.matcher === entry.matcher);
|
|
1035
|
+
if (idx >= 0) {
|
|
1036
|
+
existing[idx] = entry;
|
|
1037
|
+
} else {
|
|
1038
|
+
existing.push(entry);
|
|
1039
|
+
}
|
|
1040
|
+
} else {
|
|
1041
|
+
const matcherlessIdx = existing.findIndex((h) => !h.matcher);
|
|
1042
|
+
if (matcherlessIdx >= 0) {
|
|
1043
|
+
existing[matcherlessIdx] = entry;
|
|
1044
|
+
} else {
|
|
1045
|
+
existing.push(entry);
|
|
1046
|
+
}
|
|
1047
|
+
}
|
|
1048
|
+
}
|
|
1049
|
+
}
|
|
1050
|
+
writeFileSync3(GEMINI_SETTINGS_FILE, JSON.stringify(settings, null, 2) + "\n");
|
|
1051
|
+
log.info("Merged hooks into ~/.gemini/settings.json");
|
|
1052
|
+
}
|
|
1053
|
+
|
|
687
1054
|
// platforms/shared/detector.ts
|
|
688
1055
|
var ADAPTERS = [
|
|
689
1056
|
new ClaudeAdapter(),
|
|
690
|
-
new JunieAdapter()
|
|
1057
|
+
new JunieAdapter(),
|
|
1058
|
+
new GeminiAdapter()
|
|
691
1059
|
];
|
|
692
1060
|
function detectPlatforms() {
|
|
693
1061
|
return ADAPTERS.map((adapter) => {
|
|
694
1062
|
const detected = adapter.detect();
|
|
695
|
-
const note = adapter.name === "claude" && detected ? "Recommended: install via plugin marketplace instead" : void 0;
|
|
1063
|
+
const note = adapter.name === "claude" && detected ? "Recommended: install via plugin marketplace instead" : adapter.name === "gemini" && detected ? "Also available as a Gemini extension" : void 0;
|
|
696
1064
|
return { adapter, detected, note };
|
|
697
1065
|
});
|
|
698
1066
|
}
|
|
@@ -701,28 +1069,28 @@ function getAdapter(name) {
|
|
|
701
1069
|
}
|
|
702
1070
|
|
|
703
1071
|
// platforms/shared/installer.ts
|
|
704
|
-
import { join as
|
|
1072
|
+
import { join as join6 } from "path";
|
|
705
1073
|
|
|
706
1074
|
// platforms/shared/types.ts
|
|
707
|
-
import { join as
|
|
708
|
-
import { existsSync as
|
|
709
|
-
import { fileURLToPath } from "url";
|
|
1075
|
+
import { join as join5, resolve, dirname as dirname3 } from "path";
|
|
1076
|
+
import { existsSync as existsSync5 } from "fs";
|
|
1077
|
+
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
710
1078
|
function findPackageRoot() {
|
|
711
1079
|
let dir;
|
|
712
1080
|
try {
|
|
713
|
-
dir =
|
|
1081
|
+
dir = dirname3(fileURLToPath2(import.meta.url));
|
|
714
1082
|
} catch {
|
|
715
1083
|
dir = typeof __dirname !== "undefined" ? __dirname : resolve(".");
|
|
716
1084
|
}
|
|
717
1085
|
for (let i = 0; i < 10; i++) {
|
|
718
|
-
if (
|
|
719
|
-
const parent =
|
|
1086
|
+
if (existsSync5(join5(dir, "package.json"))) return dir;
|
|
1087
|
+
const parent = join5(dir, "..");
|
|
720
1088
|
if (parent === dir) break;
|
|
721
1089
|
dir = parent;
|
|
722
1090
|
}
|
|
723
1091
|
return process.cwd();
|
|
724
1092
|
}
|
|
725
|
-
var PLUGIN_SOURCE =
|
|
1093
|
+
var PLUGIN_SOURCE = join5(
|
|
726
1094
|
findPackageRoot(),
|
|
727
1095
|
"plugins",
|
|
728
1096
|
"ima-claude"
|
|
@@ -838,9 +1206,9 @@ ${colors.bright}Hooks${colors.reset} (${hooks.length} total)`
|
|
|
838
1206
|
return filter;
|
|
839
1207
|
}
|
|
840
1208
|
async function installForPlatform(adapter, options = {}) {
|
|
841
|
-
const skillsSource =
|
|
842
|
-
const agentsSource =
|
|
843
|
-
const hooksSource =
|
|
1209
|
+
const skillsSource = join6(PLUGIN_SOURCE, "skills");
|
|
1210
|
+
const agentsSource = join6(PLUGIN_SOURCE, "agents");
|
|
1211
|
+
const hooksSource = join6(PLUGIN_SOURCE, "hooks");
|
|
844
1212
|
const preview = adapter.preview(PLUGIN_SOURCE);
|
|
845
1213
|
showPreview(adapter, preview.items);
|
|
846
1214
|
const filter = await promptExclusions(preview.items);
|
|
@@ -908,7 +1276,7 @@ ${colors.cyan}Usage:${colors.reset}
|
|
|
908
1276
|
|
|
909
1277
|
${colors.cyan}Commands:${colors.reset}
|
|
910
1278
|
install Interactive install (auto-detects platforms)
|
|
911
|
-
install --target X Install for specific platform (claude, junie)
|
|
1279
|
+
install --target X Install for specific platform (claude, junie, gemini)
|
|
912
1280
|
upgrade Upgrade installed skills to latest version
|
|
913
1281
|
detect Show detected platforms
|
|
914
1282
|
help Show this help message
|
|
@@ -966,7 +1334,7 @@ ${colors.bright}ima-claude v${VERSION} \u2014 Multi-Platform Installer${colors.r
|
|
|
966
1334
|
`
|
|
967
1335
|
${colors.yellow}No supported platforms detected.${colors.reset}`
|
|
968
1336
|
);
|
|
969
|
-
console.log("Install Claude Code or
|
|
1337
|
+
console.log("Install Claude Code, Junie CLI, or Gemini CLI first, then run this installer again.\n");
|
|
970
1338
|
return;
|
|
971
1339
|
}
|
|
972
1340
|
console.log("");
|
|
@@ -998,7 +1366,7 @@ async function targetedInstall(targetName) {
|
|
|
998
1366
|
const adapter = getAdapter(targetName);
|
|
999
1367
|
if (!adapter) {
|
|
1000
1368
|
log.error(`Unknown target: ${targetName}`);
|
|
1001
|
-
console.log("Available targets: claude, junie");
|
|
1369
|
+
console.log("Available targets: claude, junie, gemini");
|
|
1002
1370
|
process.exit(1);
|
|
1003
1371
|
}
|
|
1004
1372
|
if (!adapter.detect()) {
|
package/package.json
CHANGED