ima-claude 2.10.0 → 2.14.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 +29 -22
- package/dist/cli.js +736 -17
- package/package.json +2 -2
- 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/gh-copilot/adapter.ts +437 -0
- package/platforms/gh-copilot/hook-translations.md +91 -0
- package/platforms/gh-copilot/hooks-translator.py +66 -0
- package/platforms/shared/detector.ts +7 -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/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/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.14.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,731 @@ 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
|
+
|
|
1054
|
+
// platforms/gh-copilot/adapter.ts
|
|
1055
|
+
import { join as join5, dirname as dirname3 } from "path";
|
|
1056
|
+
import { homedir as homedir4 } from "os";
|
|
1057
|
+
import { existsSync as existsSync5, readdirSync as readdirSync5, statSync as statSync5, readFileSync as readFileSync4, writeFileSync as writeFileSync4, copyFileSync as copyFileSync4 } from "fs";
|
|
1058
|
+
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
1059
|
+
var __filename2 = fileURLToPath2(import.meta.url);
|
|
1060
|
+
var __dirname3 = dirname3(__filename2);
|
|
1061
|
+
var COPILOT_DIR = join5(homedir4(), ".copilot");
|
|
1062
|
+
var COPILOT_SKILLS_DIR = join5(COPILOT_DIR, "skills");
|
|
1063
|
+
var COPILOT_AGENTS_DIR = join5(COPILOT_DIR, "agents");
|
|
1064
|
+
var COPILOT_HOOKS_DIR = join5(COPILOT_DIR, "hooks");
|
|
1065
|
+
var COPILOT_GUIDELINES_FILE = join5(COPILOT_DIR, "copilot-instructions.md");
|
|
1066
|
+
var TOOL_MAP2 = {
|
|
1067
|
+
Bash: "run_terminal_command",
|
|
1068
|
+
Read: "read_file",
|
|
1069
|
+
Edit: "edit_file",
|
|
1070
|
+
Write: "write_file",
|
|
1071
|
+
Glob: "find_files",
|
|
1072
|
+
Grep: "search_code",
|
|
1073
|
+
LS: "list_directory",
|
|
1074
|
+
WebSearch: "web_search",
|
|
1075
|
+
WebFetch: "fetch_url",
|
|
1076
|
+
ExitPlanMode: "ExitPlanMode"
|
|
1077
|
+
};
|
|
1078
|
+
var EVENT_MAP2 = {
|
|
1079
|
+
PreToolUse: "preToolUse",
|
|
1080
|
+
PostToolUse: "postToolUse",
|
|
1081
|
+
UserPromptSubmit: "userPromptSubmitted",
|
|
1082
|
+
SessionStart: "sessionStart"
|
|
1083
|
+
};
|
|
1084
|
+
function parseFrontmatter3(content) {
|
|
1085
|
+
const match = content.match(/^---\n([\s\S]*?)\n---\n?([\s\S]*)$/);
|
|
1086
|
+
if (!match) return { frontmatter: {}, body: content };
|
|
1087
|
+
const frontmatter = {};
|
|
1088
|
+
for (const line of match[1].split("\n")) {
|
|
1089
|
+
const colonIdx = line.indexOf(":");
|
|
1090
|
+
if (colonIdx === -1) continue;
|
|
1091
|
+
const key = line.slice(0, colonIdx).trim();
|
|
1092
|
+
const value = line.slice(colonIdx + 1).trim();
|
|
1093
|
+
if (key) frontmatter[key] = value;
|
|
1094
|
+
}
|
|
1095
|
+
return { frontmatter, body: match[2] };
|
|
1096
|
+
}
|
|
1097
|
+
function serializeFrontmatter3(frontmatter, body) {
|
|
1098
|
+
const lines = Object.entries(frontmatter).map(([k, v]) => `${k}: ${v}`);
|
|
1099
|
+
return `---
|
|
1100
|
+
${lines.join("\n")}
|
|
1101
|
+
---
|
|
1102
|
+
${body}`;
|
|
1103
|
+
}
|
|
1104
|
+
function mapToolName2(claudeName) {
|
|
1105
|
+
return TOOL_MAP2[claudeName] ?? claudeName;
|
|
1106
|
+
}
|
|
1107
|
+
function transformAgentForCopilot(content) {
|
|
1108
|
+
const { frontmatter, body } = parseFrontmatter3(content);
|
|
1109
|
+
const { permissionMode: _perm, model: _model, ...kept } = frontmatter;
|
|
1110
|
+
if (kept.tools) {
|
|
1111
|
+
const mapped = kept.tools.split(",").map((t) => t.trim()).map(mapToolName2).join(", ");
|
|
1112
|
+
kept.tools = mapped;
|
|
1113
|
+
}
|
|
1114
|
+
return serializeFrontmatter3(kept, body);
|
|
1115
|
+
}
|
|
1116
|
+
function translateMatcher2(matcher) {
|
|
1117
|
+
return TOOL_MAP2[matcher] ?? matcher;
|
|
1118
|
+
}
|
|
1119
|
+
function translateHookCommand2(command2) {
|
|
1120
|
+
const hooksDir = COPILOT_HOOKS_DIR;
|
|
1121
|
+
const translatorPath = join5(hooksDir, "hooks-translator.py");
|
|
1122
|
+
const match = command2.match(/python3\s+.*\/([^/\s]+\.py)(\s.*)?$/);
|
|
1123
|
+
if (!match) return command2;
|
|
1124
|
+
const scriptName = match[1];
|
|
1125
|
+
const trailingArgs = match[2] ?? "";
|
|
1126
|
+
return `python3 ${translatorPath} ${join5(hooksDir, scriptName)}${trailingArgs}`;
|
|
1127
|
+
}
|
|
1128
|
+
function generateCopilotHooksConfig() {
|
|
1129
|
+
const copilotHooks = {};
|
|
1130
|
+
for (const [claudeEvent, hookEntries] of Object.entries(HOOKS_CONFIG.hooks)) {
|
|
1131
|
+
const copilotEvent = EVENT_MAP2[claudeEvent] ?? claudeEvent;
|
|
1132
|
+
const flatEntries = [];
|
|
1133
|
+
for (const entry of hookEntries) {
|
|
1134
|
+
const translatedMatcher = entry.matcher ? translateMatcher2(entry.matcher) : void 0;
|
|
1135
|
+
for (const hook of entry.hooks) {
|
|
1136
|
+
const flatEntry = {};
|
|
1137
|
+
if (translatedMatcher) {
|
|
1138
|
+
flatEntry.matcher = translatedMatcher;
|
|
1139
|
+
}
|
|
1140
|
+
flatEntry.type = hook.type;
|
|
1141
|
+
flatEntry.bash = translateHookCommand2(hook.command);
|
|
1142
|
+
flatEntries.push(flatEntry);
|
|
1143
|
+
}
|
|
1144
|
+
}
|
|
1145
|
+
copilotHooks[copilotEvent] = flatEntries;
|
|
1146
|
+
}
|
|
1147
|
+
return { version: 1, hooks: copilotHooks };
|
|
1148
|
+
}
|
|
1149
|
+
function generateCopilotInstructionsMd() {
|
|
1150
|
+
return `# ima-claude: AI Coding Agent Guidelines
|
|
1151
|
+
|
|
1152
|
+
> Generated by ima-claude v${VERSION} for GitHub Copilot.
|
|
1153
|
+
> Source: https://github.com/Soabirw/ima-claude
|
|
1154
|
+
|
|
1155
|
+
## Default Persona: The Practitioner
|
|
1156
|
+
|
|
1157
|
+
A 25-year software development veteran. FP-first, composition-minded, anti-over-engineering.
|
|
1158
|
+
Uses "we" not "I" \u2014 collaborative, humble, light-hearted. "Slow is smooth, smooth is fast."
|
|
1159
|
+
|
|
1160
|
+
**Philosophy**: Simple > Complex | Evidence > Assumptions | Native > Utilities | MVP > Enterprise
|
|
1161
|
+
|
|
1162
|
+
---
|
|
1163
|
+
|
|
1164
|
+
## Memory Routing
|
|
1165
|
+
|
|
1166
|
+
| Store what | Where | Why |
|
|
1167
|
+
|---|---|---|
|
|
1168
|
+
| Decisions, preferences, patterns, bugs | Vestige \`smart_ingest\` | Fades naturally if not referenced |
|
|
1169
|
+
| Reference material (docs, standards, PRDs) | Qdrant \`qdrant-store\` | Permanent library |
|
|
1170
|
+
| Session state, task progress | Serena \`write_memory\` | Project-scoped workbench |
|
|
1171
|
+
| Future reminders | Vestige \`intention\` | Surfaces at next session |
|
|
1172
|
+
|
|
1173
|
+
At session start, check memory before asking questions:
|
|
1174
|
+
- Vestige: search for user preferences and project context
|
|
1175
|
+
- Vestige: check for pending reminders/intentions
|
|
1176
|
+
- Serena: list memories if in a Serena-activated project
|
|
1177
|
+
|
|
1178
|
+
Auto-store: "I prefer..." \u2192 Vestige preference. "Let's go with X because..." \u2192 Vestige decision. "The reason this failed..." \u2192 Vestige bug.
|
|
1179
|
+
|
|
1180
|
+
After completing work: store outcome in Vestige, reference material in Qdrant, session state in Serena.
|
|
1181
|
+
|
|
1182
|
+
---
|
|
1183
|
+
|
|
1184
|
+
## Orchestrator Protocol
|
|
1185
|
+
|
|
1186
|
+
You are the Orchestrator. Plan and delegate. Do NOT implement directly.
|
|
1187
|
+
- Non-trivial work \u2192 task-planner (decompose) \u2192 task-runner (delegate)
|
|
1188
|
+
- Trivial = single file, < 5 lines, no judgment calls
|
|
1189
|
+
|
|
1190
|
+
---
|
|
1191
|
+
|
|
1192
|
+
## Available Agents
|
|
1193
|
+
|
|
1194
|
+
Delegate to named agents \u2014 they enforce tools and permissions automatically.
|
|
1195
|
+
|
|
1196
|
+
| Agent | Use For |
|
|
1197
|
+
|---|---|
|
|
1198
|
+
| \`explorer\` | File discovery, codebase exploration |
|
|
1199
|
+
| \`implementer\` | Feature dev, bug fixes, refactoring |
|
|
1200
|
+
| \`reviewer\` | Code review, security audit, FP checks |
|
|
1201
|
+
| \`wp-developer\` | WordPress plugins, themes, WP-CLI, forms |
|
|
1202
|
+
| \`memory\` | Memory search, storage, consolidation |
|
|
1203
|
+
|
|
1204
|
+
---
|
|
1205
|
+
|
|
1206
|
+
## Code Navigation (Serena)
|
|
1207
|
+
|
|
1208
|
+
When Serena MCP is available, **prefer Serena over read_file/search_code for code investigation.** 40-70% token savings.
|
|
1209
|
+
|
|
1210
|
+
| Instead of | Use |
|
|
1211
|
+
|---|---|
|
|
1212
|
+
| Read file to understand structure | Serena get_symbols_overview |
|
|
1213
|
+
| search_code for class/function definition | Serena find_symbol |
|
|
1214
|
+
| search_code for callers/references | Serena find_referencing_symbols |
|
|
1215
|
+
|
|
1216
|
+
Use read_file only when you need the actual implementation body of a known, specific symbol.
|
|
1217
|
+
|
|
1218
|
+
---
|
|
1219
|
+
|
|
1220
|
+
## Complex Reasoning
|
|
1221
|
+
|
|
1222
|
+
Use sequential thinking before acting on:
|
|
1223
|
+
- Debugging / root cause analysis / "why is this failing"
|
|
1224
|
+
- Trade-off evaluation / "which approach"
|
|
1225
|
+
- Architectural decisions / design choices
|
|
1226
|
+
- Multi-step investigations where approach may change
|
|
1227
|
+
|
|
1228
|
+
---
|
|
1229
|
+
|
|
1230
|
+
## MCP Tool Routing
|
|
1231
|
+
|
|
1232
|
+
| Signal | Preferred Tool |
|
|
1233
|
+
|---|---|
|
|
1234
|
+
| "latest", "2025/2026", "what's new" | Tavily search |
|
|
1235
|
+
| Library/framework API question | Context7 |
|
|
1236
|
+
| URL content extraction | Tavily extract (use advanced for complex pages) |
|
|
1237
|
+
|
|
1238
|
+
Before web tools: check internal knowledge \u2192 Context7 \u2192 then Tavily.
|
|
1239
|
+
Before external lookups: check Vestige memory first.
|
|
1240
|
+
|
|
1241
|
+
---
|
|
1242
|
+
|
|
1243
|
+
## Search Preference
|
|
1244
|
+
|
|
1245
|
+
Always prefer \`rg\` (ripgrep) over grep/find. Faster, respects .gitignore, recursive by default.
|
|
1246
|
+
|
|
1247
|
+
---
|
|
1248
|
+
|
|
1249
|
+
## Security
|
|
1250
|
+
|
|
1251
|
+
- Verify nonce usage and input sanitization in WordPress PHP code
|
|
1252
|
+
- Never concatenate user input directly into SQL \u2014 use parameterized queries
|
|
1253
|
+
- Check for XSS, CSRF, and OWASP top 10 vulnerabilities in written code
|
|
1254
|
+
|
|
1255
|
+
---
|
|
1256
|
+
|
|
1257
|
+
## Code Style
|
|
1258
|
+
|
|
1259
|
+
- Don't create custom FP utility functions (pipe, compose, curry) \u2014 use language-native patterns or established libraries
|
|
1260
|
+
- In WordPress JavaScript context, use jQuery patterns when jQuery is already loaded
|
|
1261
|
+
- Prefer Bootstrap utility classes over custom CSS overrides
|
|
1262
|
+
- Run \`composer dump-autoload\` after creating new PHP files
|
|
1263
|
+
|
|
1264
|
+
---
|
|
1265
|
+
|
|
1266
|
+
## Documentation
|
|
1267
|
+
|
|
1268
|
+
Follow the three-tier documentation system:
|
|
1269
|
+
- **Active** \u2014 Living docs, kept current (README, API docs, architecture)
|
|
1270
|
+
- **Archive** \u2014 Historical reference, rarely updated (decisions, post-mortems)
|
|
1271
|
+
- **Transient** \u2014 Ephemeral, git-ignored (session notes, scratch)
|
|
1272
|
+
`;
|
|
1273
|
+
}
|
|
1274
|
+
var GhCopilotAdapter = class {
|
|
1275
|
+
name = "gh-copilot";
|
|
1276
|
+
displayName = "GitHub Copilot";
|
|
1277
|
+
configDir = COPILOT_DIR;
|
|
1278
|
+
detect() {
|
|
1279
|
+
return existsSync5(COPILOT_DIR);
|
|
1280
|
+
}
|
|
1281
|
+
preview(sourceDir) {
|
|
1282
|
+
const skillItems = SKILLS_TO_INSTALL.map((skill) => ({
|
|
1283
|
+
name: skill,
|
|
1284
|
+
category: "skill",
|
|
1285
|
+
destPath: join5(COPILOT_SKILLS_DIR, skill),
|
|
1286
|
+
exists: existsSync5(join5(COPILOT_SKILLS_DIR, skill))
|
|
1287
|
+
})).filter((item) => existsSync5(join5(sourceDir, "skills", item.name)));
|
|
1288
|
+
const agentsDir = join5(sourceDir, "agents");
|
|
1289
|
+
const agentItems = existsSync5(agentsDir) ? readdirSync5(agentsDir).filter((f) => f.endsWith(".md")).map((file) => ({
|
|
1290
|
+
name: file.replace(/\.md$/, ""),
|
|
1291
|
+
category: "agent",
|
|
1292
|
+
destPath: join5(COPILOT_AGENTS_DIR, file.replace(/\.md$/, ".agent.md")),
|
|
1293
|
+
exists: existsSync5(join5(COPILOT_AGENTS_DIR, file.replace(/\.md$/, ".agent.md")))
|
|
1294
|
+
})) : [];
|
|
1295
|
+
const hookItems = HOOKS_TO_INSTALL.map((file) => ({
|
|
1296
|
+
name: file,
|
|
1297
|
+
category: "hook",
|
|
1298
|
+
destPath: join5(COPILOT_HOOKS_DIR, file),
|
|
1299
|
+
exists: existsSync5(join5(COPILOT_HOOKS_DIR, file))
|
|
1300
|
+
}));
|
|
1301
|
+
const translatorItem = {
|
|
1302
|
+
name: "hooks-translator.py",
|
|
1303
|
+
category: "hook",
|
|
1304
|
+
destPath: join5(COPILOT_HOOKS_DIR, "hooks-translator.py"),
|
|
1305
|
+
exists: existsSync5(join5(COPILOT_HOOKS_DIR, "hooks-translator.py"))
|
|
1306
|
+
};
|
|
1307
|
+
const guidelineItem = {
|
|
1308
|
+
name: "copilot-instructions.md",
|
|
1309
|
+
category: "guideline",
|
|
1310
|
+
destPath: COPILOT_GUIDELINES_FILE,
|
|
1311
|
+
exists: existsSync5(COPILOT_GUIDELINES_FILE)
|
|
1312
|
+
};
|
|
1313
|
+
return {
|
|
1314
|
+
platform: this.name,
|
|
1315
|
+
targetDir: COPILOT_DIR,
|
|
1316
|
+
items: [...skillItems, ...agentItems, ...hookItems, translatorItem, guidelineItem]
|
|
1317
|
+
};
|
|
1318
|
+
}
|
|
1319
|
+
installSkills(sourceDir, exclude) {
|
|
1320
|
+
ensureDir(COPILOT_SKILLS_DIR);
|
|
1321
|
+
const skills = exclude?.length ? SKILLS_TO_INSTALL.filter((s) => !exclude.includes(s)) : SKILLS_TO_INSTALL;
|
|
1322
|
+
for (const skill of skills) {
|
|
1323
|
+
const src = join5(sourceDir, skill);
|
|
1324
|
+
if (existsSync5(src) && statSync5(src).isDirectory()) {
|
|
1325
|
+
copyDirRecursive(src, join5(COPILOT_SKILLS_DIR, skill));
|
|
1326
|
+
log.step(`skill: ${skill}`);
|
|
1327
|
+
}
|
|
1328
|
+
}
|
|
1329
|
+
}
|
|
1330
|
+
installAgents(sourceDir, exclude) {
|
|
1331
|
+
ensureDir(COPILOT_AGENTS_DIR);
|
|
1332
|
+
const entries = readdirSync5(sourceDir).filter((f) => f.endsWith(".md")).filter((f) => !exclude?.includes(f.replace(/\.md$/, "")));
|
|
1333
|
+
for (const file of entries) {
|
|
1334
|
+
const content = readFileSync4(join5(sourceDir, file), "utf8");
|
|
1335
|
+
const transformed = transformAgentForCopilot(content);
|
|
1336
|
+
const destFile = file.replace(/\.md$/, ".agent.md");
|
|
1337
|
+
writeFileSync4(join5(COPILOT_AGENTS_DIR, destFile), transformed);
|
|
1338
|
+
log.step(`agent: ${destFile}`);
|
|
1339
|
+
}
|
|
1340
|
+
}
|
|
1341
|
+
installGuidelines(_pluginRoot) {
|
|
1342
|
+
ensureDir(COPILOT_DIR);
|
|
1343
|
+
writeFileSync4(COPILOT_GUIDELINES_FILE, generateCopilotInstructionsMd());
|
|
1344
|
+
log.step(`guidelines: ${COPILOT_GUIDELINES_FILE}`);
|
|
1345
|
+
}
|
|
1346
|
+
installHooks(sourceDir, exclude) {
|
|
1347
|
+
ensureDir(COPILOT_HOOKS_DIR);
|
|
1348
|
+
const hooks = exclude?.length ? HOOKS_TO_INSTALL.filter((f) => !exclude.includes(f)) : HOOKS_TO_INSTALL;
|
|
1349
|
+
for (const file of hooks) {
|
|
1350
|
+
const src = join5(sourceDir, file);
|
|
1351
|
+
if (existsSync5(src)) {
|
|
1352
|
+
copyFileSync4(src, join5(COPILOT_HOOKS_DIR, file));
|
|
1353
|
+
log.step(`hook: ${file}`);
|
|
1354
|
+
}
|
|
1355
|
+
}
|
|
1356
|
+
const shimSrc = join5(__dirname3, "hooks-translator.py");
|
|
1357
|
+
if (!existsSync5(shimSrc)) {
|
|
1358
|
+
throw new Error(`hooks-translator.py not found at ${shimSrc} \u2014 packaging error`);
|
|
1359
|
+
}
|
|
1360
|
+
copyFileSync4(shimSrc, join5(COPILOT_HOOKS_DIR, "hooks-translator.py"));
|
|
1361
|
+
log.step("hook: hooks-translator.py (shim)");
|
|
1362
|
+
const hooksConfig = generateCopilotHooksConfig();
|
|
1363
|
+
const hooksConfigPath = join5(COPILOT_HOOKS_DIR, "hooks.json");
|
|
1364
|
+
mergeCopilotHooksConfig(hooksConfigPath, hooksConfig);
|
|
1365
|
+
log.step("hook: hooks.json (generated for GitHub Copilot)");
|
|
1366
|
+
}
|
|
1367
|
+
postInstall() {
|
|
1368
|
+
log.info("GitHub Copilot install complete. Verify:");
|
|
1369
|
+
log.info(` Skills: ${COPILOT_SKILLS_DIR}`);
|
|
1370
|
+
log.info(` Agents: ${COPILOT_AGENTS_DIR}`);
|
|
1371
|
+
log.info(` Hooks: ${COPILOT_HOOKS_DIR}`);
|
|
1372
|
+
log.info(` Guidelines: ${COPILOT_GUIDELINES_FILE}`);
|
|
1373
|
+
}
|
|
1374
|
+
};
|
|
1375
|
+
function mergeCopilotHooksConfig(configPath, newConfig) {
|
|
1376
|
+
let existing = {};
|
|
1377
|
+
if (existsSync5(configPath)) {
|
|
1378
|
+
try {
|
|
1379
|
+
const content = readFileSync4(configPath, "utf8");
|
|
1380
|
+
existing = JSON.parse(content);
|
|
1381
|
+
} catch {
|
|
1382
|
+
existing = {};
|
|
1383
|
+
}
|
|
1384
|
+
}
|
|
1385
|
+
existing.version = newConfig.version ?? 1;
|
|
1386
|
+
if (!existing.hooks) {
|
|
1387
|
+
existing.hooks = {};
|
|
1388
|
+
}
|
|
1389
|
+
const existingHooks = existing.hooks;
|
|
1390
|
+
const incomingHooks = newConfig.hooks;
|
|
1391
|
+
for (const [event, entries] of Object.entries(incomingHooks)) {
|
|
1392
|
+
if (!existingHooks[event]) {
|
|
1393
|
+
existingHooks[event] = entries;
|
|
1394
|
+
continue;
|
|
1395
|
+
}
|
|
1396
|
+
const userHooks = existingHooks[event].filter(
|
|
1397
|
+
(h) => !h.bash?.includes("hooks-translator.py")
|
|
1398
|
+
);
|
|
1399
|
+
existingHooks[event] = [...userHooks, ...entries];
|
|
1400
|
+
}
|
|
1401
|
+
writeFileSync4(configPath, JSON.stringify(existing, null, 2) + "\n");
|
|
1402
|
+
}
|
|
1403
|
+
|
|
687
1404
|
// platforms/shared/detector.ts
|
|
688
1405
|
var ADAPTERS = [
|
|
689
1406
|
new ClaudeAdapter(),
|
|
690
|
-
new JunieAdapter()
|
|
1407
|
+
new JunieAdapter(),
|
|
1408
|
+
new GeminiAdapter(),
|
|
1409
|
+
new GhCopilotAdapter()
|
|
691
1410
|
];
|
|
692
1411
|
function detectPlatforms() {
|
|
693
1412
|
return ADAPTERS.map((adapter) => {
|
|
694
1413
|
const detected = adapter.detect();
|
|
695
|
-
const note = adapter.name === "claude" && detected ? "Recommended: install via plugin marketplace instead" : void 0;
|
|
1414
|
+
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
1415
|
return { adapter, detected, note };
|
|
697
1416
|
});
|
|
698
1417
|
}
|
|
@@ -701,28 +1420,28 @@ function getAdapter(name) {
|
|
|
701
1420
|
}
|
|
702
1421
|
|
|
703
1422
|
// platforms/shared/installer.ts
|
|
704
|
-
import { join as
|
|
1423
|
+
import { join as join7 } from "path";
|
|
705
1424
|
|
|
706
1425
|
// platforms/shared/types.ts
|
|
707
|
-
import { join as
|
|
708
|
-
import { existsSync as
|
|
709
|
-
import { fileURLToPath } from "url";
|
|
1426
|
+
import { join as join6, resolve, dirname as dirname4 } from "path";
|
|
1427
|
+
import { existsSync as existsSync6 } from "fs";
|
|
1428
|
+
import { fileURLToPath as fileURLToPath3 } from "url";
|
|
710
1429
|
function findPackageRoot() {
|
|
711
1430
|
let dir;
|
|
712
1431
|
try {
|
|
713
|
-
dir =
|
|
1432
|
+
dir = dirname4(fileURLToPath3(import.meta.url));
|
|
714
1433
|
} catch {
|
|
715
1434
|
dir = typeof __dirname !== "undefined" ? __dirname : resolve(".");
|
|
716
1435
|
}
|
|
717
1436
|
for (let i = 0; i < 10; i++) {
|
|
718
|
-
if (
|
|
719
|
-
const parent =
|
|
1437
|
+
if (existsSync6(join6(dir, "package.json"))) return dir;
|
|
1438
|
+
const parent = join6(dir, "..");
|
|
720
1439
|
if (parent === dir) break;
|
|
721
1440
|
dir = parent;
|
|
722
1441
|
}
|
|
723
1442
|
return process.cwd();
|
|
724
1443
|
}
|
|
725
|
-
var PLUGIN_SOURCE =
|
|
1444
|
+
var PLUGIN_SOURCE = join6(
|
|
726
1445
|
findPackageRoot(),
|
|
727
1446
|
"plugins",
|
|
728
1447
|
"ima-claude"
|
|
@@ -838,9 +1557,9 @@ ${colors.bright}Hooks${colors.reset} (${hooks.length} total)`
|
|
|
838
1557
|
return filter;
|
|
839
1558
|
}
|
|
840
1559
|
async function installForPlatform(adapter, options = {}) {
|
|
841
|
-
const skillsSource =
|
|
842
|
-
const agentsSource =
|
|
843
|
-
const hooksSource =
|
|
1560
|
+
const skillsSource = join7(PLUGIN_SOURCE, "skills");
|
|
1561
|
+
const agentsSource = join7(PLUGIN_SOURCE, "agents");
|
|
1562
|
+
const hooksSource = join7(PLUGIN_SOURCE, "hooks");
|
|
844
1563
|
const preview = adapter.preview(PLUGIN_SOURCE);
|
|
845
1564
|
showPreview(adapter, preview.items);
|
|
846
1565
|
const filter = await promptExclusions(preview.items);
|
|
@@ -908,7 +1627,7 @@ ${colors.cyan}Usage:${colors.reset}
|
|
|
908
1627
|
|
|
909
1628
|
${colors.cyan}Commands:${colors.reset}
|
|
910
1629
|
install Interactive install (auto-detects platforms)
|
|
911
|
-
install --target X Install for specific platform (claude, junie)
|
|
1630
|
+
install --target X Install for specific platform (claude, junie, gemini, gh-copilot)
|
|
912
1631
|
upgrade Upgrade installed skills to latest version
|
|
913
1632
|
detect Show detected platforms
|
|
914
1633
|
help Show this help message
|
|
@@ -966,7 +1685,7 @@ ${colors.bright}ima-claude v${VERSION} \u2014 Multi-Platform Installer${colors.r
|
|
|
966
1685
|
`
|
|
967
1686
|
${colors.yellow}No supported platforms detected.${colors.reset}`
|
|
968
1687
|
);
|
|
969
|
-
console.log("Install Claude Code
|
|
1688
|
+
console.log("Install Claude Code, Junie CLI, Gemini CLI, or GitHub Copilot first, then run this installer again.\n");
|
|
970
1689
|
return;
|
|
971
1690
|
}
|
|
972
1691
|
console.log("");
|
|
@@ -998,7 +1717,7 @@ async function targetedInstall(targetName) {
|
|
|
998
1717
|
const adapter = getAdapter(targetName);
|
|
999
1718
|
if (!adapter) {
|
|
1000
1719
|
log.error(`Unknown target: ${targetName}`);
|
|
1001
|
-
console.log("Available targets: claude, junie");
|
|
1720
|
+
console.log("Available targets: claude, junie, gemini, gh-copilot");
|
|
1002
1721
|
process.exit(1);
|
|
1003
1722
|
}
|
|
1004
1723
|
if (!adapter.detect()) {
|