55// Zero dependencies. Requires Node.js 18+.
66
77import { writeFileSync , appendFileSync , mkdirSync , existsSync , readFileSync , readdirSync , unlinkSync , rmdirSync , statSync } from 'fs' ;
8- import { join , dirname } from 'path' ;
8+ import { basename , join , dirname } from 'path' ;
99import { homedir } from 'os' ;
1010import { execSync } from 'child_process' ;
1111
@@ -546,7 +546,7 @@ async function textInput({ message, defaultValue = '', mask = false }) {
546546// ─── Content (fetched from orm.st at runtime) ───────────────────────────────
547547
548548const SKILLS_BASE_URL = 'https://orm.st/skills' ;
549- const STORM_SKILL_MARKER = '<!-- storm-managed:' ;
549+ const STORM_SKILL_MARKER = '<!-- storm-managed: storm-docs --> ' ;
550550
551551async function fetchRules ( ) {
552552 try {
@@ -575,7 +575,7 @@ async function fetchSkill(name) {
575575 const res = await fetch ( url ) ;
576576 if ( ! res . ok ) throw new Error ( `${ res . status } ` ) ;
577577 const content = await res . text ( ) ;
578- return ` ${ STORM_SKILL_MARKER } ${ name } -->\n ${ content } ` ;
578+ return content . trimEnd ( ) + '\n\n' + STORM_SKILL_MARKER + '\n' ;
579579 } catch {
580580 return null ;
581581 }
@@ -584,6 +584,7 @@ async function fetchSkill(name) {
584584function installSkill ( name , content , toolConfig , created ) {
585585 const cwd = process . cwd ( ) ;
586586 const fullPath = join ( cwd , toolConfig . skillPath ( name ) ) ;
587+ if ( existsSync ( fullPath ) && readFileSync ( fullPath , 'utf-8' ) === content ) return ;
587588 mkdirSync ( dirname ( fullPath ) , { recursive : true } ) ;
588589 writeFileSync ( fullPath , content ) ;
589590 created . push ( toolConfig . skillPath ( name ) ) ;
@@ -621,8 +622,15 @@ function cleanStaleSkills(toolConfigs, installedSkillNames, skipped) {
621622 for ( const candidate of candidates ) {
622623 try {
623624 const content = readFileSync ( candidate , 'utf-8' ) ;
624- const match = content . match ( / ^ < ! - - s t o r m - m a n a g e d : ( \S + ) - - > / ) ;
625- if ( match && ! installed . has ( match [ 1 ] ) ) {
625+ const isStormManaged = content . trimEnd ( ) . endsWith ( STORM_SKILL_MARKER )
626+ || / ^ < ! - - s t o r m - m a n a g e d : \S + - - > / . test ( content ) ;
627+ if ( ! isStormManaged ) continue ;
628+
629+ // Derive skill name from path.
630+ const name = candidate . endsWith ( 'SKILL.md' )
631+ ? basename ( dirname ( candidate ) )
632+ : basename ( candidate ) . replace ( / \. ( i n s t r u c t i o n s \. ) ? m d $ / , '' ) ;
633+ if ( ! installed . has ( name ) ) {
626634 unlinkSync ( candidate ) ;
627635 // Remove empty parent directory for nested layout.
628636 const parentDir = dirname ( candidate ) ;
@@ -632,7 +640,7 @@ function cleanStaleSkills(toolConfigs, installedSkillNames, skipped) {
632640 if ( remaining . length === 0 ) rmdirSync ( parentDir ) ;
633641 } catch { }
634642 }
635- skipped . push ( `${ match [ 1 ] } (removed, no longer available)` ) ;
643+ skipped . push ( `${ name } (removed, no longer available)` ) ;
636644 }
637645 } catch { }
638646 }
@@ -1334,7 +1342,7 @@ async function update() {
13341342 if ( ! existing . includes ( 'Database Schema Access' ) ) {
13351343 const endMarker = existing . indexOf ( MARKER_END ) ;
13361344 if ( endMarker !== - 1 ) {
1337- const updated = existing . substring ( 0 , endMarker ) + '\n' + schemaRules . replace ( / ^ < ! - - s t o r m - m a n a g e d : \S + - - > \n / , '' ) + '\n' + existing . substring ( endMarker ) ;
1345+ const updated = existing . substring ( 0 , endMarker ) + '\n' + schemaRules . replace ( '\n' + STORM_SKILL_MARKER , '' ) + '\n' + existing . substring ( endMarker ) ;
13381346 writeFileSync ( rulesPath , updated ) ;
13391347 if ( ! appended . includes ( config . rulesFile ) ) appended . push ( config . rulesFile ) ;
13401348 }
@@ -1420,8 +1428,13 @@ async function updateMcp() {
14201428 console . log ( boltYellow ( ' Updated:' ) ) ;
14211429 appended . forEach ( f => console . log ( boltYellow ( ` ~ ${ f } ` ) ) ) ;
14221430 }
1423- console . log ( ) ;
1424- console . log ( bold ( ' MCP configuration updated.' ) ) ;
1431+ if ( created . length > 0 || appended . length > 0 ) {
1432+ console . log ( ) ;
1433+ console . log ( bold ( ' MCP configuration updated.' ) ) ;
1434+ } else {
1435+ const toolNames = tools . map ( t => TOOL_CONFIGS [ t ] ?. name ) . filter ( Boolean ) . join ( ', ' ) ;
1436+ console . log ( dimText ( ` MCP already configured for ${ toolNames } . No changes needed.` ) ) ;
1437+ }
14251438 console . log ( ) ;
14261439}
14271440
@@ -1570,7 +1583,7 @@ async function setup() {
15701583 if ( ! existing . includes ( 'Database Schema Access' ) ) {
15711584 const endMarker = existing . indexOf ( MARKER_END ) ;
15721585 if ( endMarker !== - 1 ) {
1573- const updated = existing . substring ( 0 , endMarker ) + '\n' + schemaRules . replace ( / ^ < ! - - s t o r m - m a n a g e d : \S + - - > \n / , '' ) + '\n' + existing . substring ( endMarker ) ;
1586+ const updated = existing . substring ( 0 , endMarker ) + '\n' + schemaRules . replace ( '\n' + STORM_SKILL_MARKER , '' ) + '\n' + existing . substring ( endMarker ) ;
15741587 writeFileSync ( rulesPath , updated ) ;
15751588 appended . push ( config . rulesFile ) ;
15761589 }
0 commit comments