@@ -11,13 +11,109 @@ import { Install, InstalledRule } from "../services/install/index.js";
1111import { colorize } from "../utils.js" ;
1212import { UnsupportedToolError } from "../errors.js" ;
1313
14+ type InstallTool = "agent" | "claude" | "cursor" | "vscode" | "windsurf" ;
15+
16+ const EFFECT_AGENT_START = "<!-- EFFECT_SKILLS_AGENT_START -->" ;
17+ const EFFECT_AGENT_END = "<!-- EFFECT_SKILLS_AGENT_END -->" ;
18+ const EFFECT_CLAUDE_START = "<!-- EFFECT_SKILLS_CLAUDE_START -->" ;
19+ const EFFECT_CLAUDE_END = "<!-- EFFECT_SKILLS_CLAUDE_END -->" ;
20+
21+ const normalizeTool = ( tool : string ) : InstallTool | null => {
22+ if ( tool === "agents" ) return "agent" ;
23+ if ( tool === "agent" || tool === "claude" || tool === "cursor" || tool === "vscode" || tool === "windsurf" ) {
24+ return tool ;
25+ }
26+ return null ;
27+ } ;
28+
29+ const installTargetByTool : Record < InstallTool , string > = {
30+ agent : "AGENTS.md" ,
31+ claude : "CLAUDE.md" ,
32+ cursor : ".cursor/rules.md" ,
33+ vscode : ".vscode/rules.md" ,
34+ windsurf : ".windsurf/rules.md" ,
35+ } ;
36+
37+ const markerByTool : Partial < Record < InstallTool , { start : string ; end : string } > > = {
38+ agent : { start : EFFECT_AGENT_START , end : EFFECT_AGENT_END } ,
39+ claude : { start : EFFECT_CLAUDE_START , end : EFFECT_CLAUDE_END } ,
40+ } ;
41+
42+ const escapeRegex = ( value : string ) => value . replace ( / [ . * + ? ^ $ { } ( ) | [ \] \\ ] / g, "\\$&" ) ;
43+
44+ const upsertManagedSection = (
45+ current : string ,
46+ startMarker : string ,
47+ endMarker : string ,
48+ sectionBody : string
49+ ) : string => {
50+ const managedSection = `${ startMarker } \n${ sectionBody } \n${ endMarker } \n` ;
51+ const replacePattern = new RegExp (
52+ `${ escapeRegex ( startMarker ) } [\\s\\S]*?${ escapeRegex ( endMarker ) } \\n?` ,
53+ "m"
54+ ) ;
55+
56+ if ( current . includes ( startMarker ) && current . includes ( endMarker ) ) {
57+ return current . replace ( replacePattern , managedSection ) ;
58+ }
59+
60+ if ( current . trim ( ) . length === 0 ) {
61+ return managedSection ;
62+ }
63+
64+ const separator = current . endsWith ( "\n" ) ? "\n" : "\n\n" ;
65+ return `${ current } ${ separator } ${ managedSection } ` ;
66+ } ;
67+
68+ const buildAgentSection = (
69+ rules : ReadonlyArray < Pick < InstalledRule , "id" | "title" | "skillLevel" | "useCase" | "description" > > ,
70+ generatedAtIso : string
71+ ) : string =>
72+ [
73+ "## Effect Skills" ,
74+ "" ,
75+ `Generated by ep on ${ generatedAtIso } .` ,
76+ "" ,
77+ ...rules . flatMap ( ( rule ) => [
78+ `### ${ rule . title } ` ,
79+ `- ID: ${ rule . id } ` ,
80+ `- Skill Level: ${ rule . skillLevel ?? "general" } ` ,
81+ `- Use Cases: ${ ( rule . useCase ?? [ ] ) . join ( ", " ) || "none" } ` ,
82+ "" ,
83+ rule . description ,
84+ "" ,
85+ ] ) ,
86+ ] . join ( "\n" ) ;
87+
88+ const buildClaudeSection = (
89+ rules : ReadonlyArray < Pick < InstalledRule , "title" | "skillLevel" | "useCase" | "description" > > ,
90+ generatedAtIso : string
91+ ) : string =>
92+ [
93+ "## Coding Standards (Effect Patterns)" ,
94+ "" ,
95+ `Updated: ${ generatedAtIso } ` ,
96+ "" ,
97+ "### Guiding Principles" ,
98+ "" ,
99+ ...rules . map ( ( rule ) => {
100+ const level = rule . skillLevel ?? "general" ;
101+ const useCases = ( rule . useCase ?? [ ] ) . slice ( 0 , 3 ) . join ( ", " ) ;
102+ const useCaseSuffix = useCases . length > 0 ? ` [${ useCases } ]` : "" ;
103+ return `- **${ rule . title } ** (${ level } )${ useCaseSuffix } : ${ rule . description } ` ;
104+ } ) ,
105+ "" ,
106+ "Use these as defaults when making implementation decisions in this repository." ,
107+ ] . join ( "\n" ) ;
108+
14109/**
15110 * install:add - Add rules to AI tool configuration
16111 */
17112export const installAddCommand = Command . make ( "add" , {
18113 options : {
19114 tool : Options . text ( "tool" ) . pipe (
20- Options . withDescription ( "The AI tool to install rules for (cursor, agents, vscode, windsurf)" )
115+ Options . withDescription ( "Target tool format (agent, claude, cursor, vscode, windsurf)" ) ,
116+ Options . withDefault ( "agent" )
21117 ) ,
22118 skillLevel : Options . optional (
23119 Options . text ( "skill-level" ) . pipe (
@@ -42,25 +138,20 @@ export const installAddCommand = Command.make("add", {
42138 const { loadInstalledRules, saveInstalledRules, searchRules } = yield * Install ;
43139 const fs = yield * FileSystem . FileSystem ;
44140
45- const installTargetByTool : Record < string , string > = {
46- agents : "AGENTS.md" ,
47- cursor : ".cursor/rules.md" ,
48- vscode : ".vscode/rules.md" ,
49- windsurf : ".windsurf/rules.md" ,
50- } ;
51-
52- const targetPath = installTargetByTool [ options . tool ] ;
53- if ( ! targetPath ) {
141+ const normalizedTool = normalizeTool ( options . tool ) ;
142+ if ( ! normalizedTool ) {
143+ const supportedTools = [ "agent" , "claude" , "cursor" , "vscode" , "windsurf" ] ;
54144 yield * Display . showError (
55- `Tool '${ options . tool } ' is not supported by local file injection yet. Supported: ${ Object . keys ( installTargetByTool ) . join ( ", " ) } `
145+ `Tool '${ options . tool } ' is not supported by local file injection yet. Supported: ${ supportedTools . join ( ", " ) } `
56146 ) ;
57147 return yield * Effect . fail ( new UnsupportedToolError ( {
58148 tool : options . tool ,
59- supported : Object . keys ( installTargetByTool ) ,
149+ supported : supportedTools ,
60150 } ) ) ;
61151 }
152+ const targetPath = installTargetByTool [ normalizedTool ] ;
62153
63- yield * Console . log ( colorize ( "\nInstalling rules for " + options . tool + "...\n" , "CYAN" ) ) ;
154+ yield * Console . log ( colorize ( "\nInstalling rules for " + normalizedTool + "...\n" , "CYAN" ) ) ;
64155
65156 let rulesToInstall = yield * searchRules ( {
66157 skillLevel : Option . getOrUndefined ( options . skillLevel ) ,
@@ -85,10 +176,11 @@ export const installAddCommand = Command.make("add", {
85176 return ;
86177 }
87178
179+ const generatedAt = new Date ( ) . toISOString ( ) ;
88180 const rulesMarkdown = [
89181 "# Effect Patterns Rules" ,
90182 "" ,
91- `Generated by ep on ${ new Date ( ) . toISOString ( ) } .` ,
183+ `Generated by ep on ${ generatedAt } .` ,
92184 "Source: Effect Patterns Database" ,
93185 "" ,
94186 ...rulesToInstall . flatMap ( ( rule ) => [
@@ -105,27 +197,24 @@ export const installAddCommand = Command.make("add", {
105197 ] ) ,
106198 ] . join ( "\n" ) ;
107199
108- if ( options . tool === "agents" ) {
109- const startMarker = "<!-- EP_RULES_START -->" ;
110- const endMarker = "<!-- EP_RULES_END -->" ;
111- const canonicalPath = path . join ( process . cwd ( ) , "docs" , "Effect-Patterns-Rules.md" ) ;
112- const docsDir = path . dirname ( canonicalPath ) ;
113-
114- yield * fs . makeDirectory ( docsDir , { recursive : true } ) ;
115- yield * fs . writeFileString ( canonicalPath , rulesMarkdown ) ;
116-
117- const pointerLine = "For Effect Patterns rules, see [docs/Effect-Patterns-Rules.md](docs/Effect-Patterns-Rules.md)." ;
118- const managedSection = `${ startMarker } \n\n${ pointerLine } \n\n${ endMarker } \n` ;
200+ if ( normalizedTool === "agent" || normalizedTool === "claude" ) {
201+ const markers = markerByTool [ normalizedTool ] ;
202+ if ( ! markers ) {
203+ return yield * Effect . fail ( new Error ( `Missing marker configuration for tool: ${ normalizedTool } ` ) ) ;
204+ }
119205
206+ const managedBody =
207+ normalizedTool === "agent"
208+ ? buildAgentSection ( rulesToInstall , generatedAt )
209+ : buildClaudeSection ( rulesToInstall , generatedAt ) ;
120210 const exists = yield * fs . exists ( targetPath ) ;
121211 const current = exists ? yield * fs . readFileString ( targetPath ) : "" ;
122- const nextContent =
123- current . includes ( startMarker ) && current . includes ( endMarker )
124- ? current . replace (
125- / < ! - - E P _ R U L E S _ S T A R T - - > [ \s \S ] * < ! - - E P _ R U L E S _ E N D - - > \n ? / m,
126- managedSection
127- )
128- : `${ current } ${ current . endsWith ( "\n" ) ? "" : "\n" } \n${ managedSection } ` ;
212+ const nextContent = upsertManagedSection (
213+ current ,
214+ markers . start ,
215+ markers . end ,
216+ managedBody
217+ ) ;
129218
130219 yield * fs . writeFileString ( targetPath , nextContent ) ;
131220 } else {
@@ -138,7 +227,7 @@ export const installAddCommand = Command.make("add", {
138227 const newRules : InstalledRule [ ] = rulesToInstall . map ( ( r ) => ( {
139228 ...r ,
140229 installedAt : new Date ( ) . toISOString ( ) ,
141- tool : options . tool ,
230+ tool : normalizedTool ,
142231 version : "1.0.0" ,
143232 } ) ) ;
144233 const merged = [
@@ -147,10 +236,8 @@ export const installAddCommand = Command.make("add", {
147236 ] ;
148237 yield * saveInstalledRules ( merged ) ;
149238
150- if ( options . tool === "agents" ) {
151- yield * Display . showSuccess (
152- `Installed ${ rulesToInstall . length } rule(s) to docs/Effect-Patterns-Rules.md and updated ${ targetPath } `
153- ) ;
239+ if ( normalizedTool === "agent" || normalizedTool === "claude" ) {
240+ yield * Console . log ( `✅ Installed Effect Skills to ${ targetPath } ` ) ;
154241 } else {
155242 yield * Display . showSuccess ( `Installed ${ rulesToInstall . length } rule(s) to ${ targetPath } ` ) ;
156243 }
@@ -186,7 +273,8 @@ const displayInstalledRules = (
186273const displaySupportedTools = ( ) : Effect . Effect < void , unknown > =>
187274 Effect . gen ( function * ( ) {
188275 yield * Console . log ( colorize ( "\n📋 Supported AI Tools\n" , "BRIGHT" ) ) ;
189- yield * Console . log ( " • agents" ) ;
276+ yield * Console . log ( " • agent" ) ;
277+ yield * Console . log ( " • claude" ) ;
190278 yield * Console . log ( " • cursor" ) ;
191279 yield * Console . log ( " • vscode" ) ;
192280 yield * Console . log ( " • windsurf" ) ;
@@ -211,7 +299,7 @@ export const installListCommand = Command.make("list", {
211299 Command . withHandler ( ( { options } ) =>
212300 Effect . gen ( function * ( ) {
213301 const { loadInstalledRules } = yield * Install ;
214- const supportedTools = [ "agents " , "cursor" , "vscode" , "windsurf" ] as const ;
302+ const supportedTools = [ "agent" , "claude ", "cursor" , "vscode" , "windsurf" ] as const ;
215303
216304 if ( options . json ) {
217305 if ( options . installed ) {
0 commit comments