Skip to content

Commit 8213fe3

Browse files
committed
symlinks now working
1 parent 1ad2a1b commit 8213fe3

2 files changed

Lines changed: 73 additions & 25 deletions

File tree

‎changelog.md‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
2525

2626
### Fixed
2727

28+
- **Symlink creation method resolution** — Replaced `java.nio.file.Paths.get()` with `java.io.File.toPath()` in `AgentRegistry.createSkillSymlinks()` and `removeSkillSymlinks()` to fix "No matching Method for get(string) found" errors on some JVM/CFML engine combinations
2829
- Invalid scope on `FUNCTION_PATTERN` variable in Agent Registry
2930
- `coldbox ai install` now registers the correct MCP server URL (`https://boxlang.ortusbooks.com/~gitbook/mcp`) for the BoxLang core language documentation entry instead of the BoxLang AI module URL
3031

‎models/AgentRegistry.cfc‎

Lines changed: 72 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,8 @@ component singleton {
7373
FUNCTION_PATTERN = createObject( "java", "java.util.regex.Pattern" ).compile(
7474
"(?i)(?:^|\s)(?:public\s+)?(?:\w+\s+)?function\s+(\w+)\s*\("
7575
)
76+
Files = createObject( "java", "java.nio.file.Files" )
77+
Paths = createObject( "java", "java.nio.file.Paths" )
7678
}
7779

7880
// Expose them as instance properties for easier access in commands
@@ -192,15 +194,16 @@ component singleton {
192194
* @directory The project directory
193195
* @agent The agent name (claude, copilot, cursor, codex, gemini, opencode)
194196
*
195-
* @return Absolute path string, or null if the agent has no dedicated skills directory
197+
* @return Absolute path string, or empty
196198
*/
197199
function getAgentSkillsDirectory(
198200
required string directory,
199201
required string agent
200202
){
201-
var relPath = static.AGENT_SKILLS_DIRS[ arguments.agent ] ?: ""
202-
if ( !relPath.len() ) {
203-
return javacast( "null", "" )
203+
var relPath = static.AGENT_SKILLS_DIRS[ arguments.agent ]
204+
// If empty, then return it as it means the agent has no dedicated skills directory
205+
if ( relPath.isEmpty() ) {
206+
return relPath
204207
}
205208
// Normalize trailing separator
206209
var dir = arguments.directory
@@ -228,39 +231,39 @@ component singleton {
228231
required string skillName,
229232
required array agents
230233
){
231-
var canonicalSkillDir = "#arguments.directory#/.agents/skills/#arguments.skillName#"
232-
var Files = createObject( "java", "java.nio.file.Files" )
233-
var Paths = createObject( "java", "java.nio.file.Paths" )
234-
// Store loop variables outside closure to avoid scope issues
235-
var dir = arguments.directory
236-
var skill = arguments.skillName
237-
var canonical = canonicalSkillDir
234+
var fromSkillDirectory = "#arguments.directory#/.agents/skills/#arguments.skillName#"
238235

239236
for ( var agent in arguments.agents ) {
240-
var agentSkillsDir = getAgentSkillsDirectory( dir, agent )
241-
if ( isNull( agentSkillsDir ) ) {
237+
var agentSkillsDir = getAgentSkillsDirectory( arguments.directory, agent )
238+
239+
// Skip if agent has no dedicated skills directory, it uses the canonical one instead
240+
if ( agentSkillsDir.isEmpty() ) {
242241
continue;
243242
}
244243

245-
var linkPath = "#agentSkillsDir#/#skill#"
244+
// Create the symlink path for the agent's skills directory
245+
var linkPath = "#agentSkillsDir#/#arguments.skillName#"
246246

247247
// Skip if link/directory already exists
248-
if ( directoryExists( linkPath ) || fileExists( linkPath ) ) {
248+
if ( directoryExists( linkPath ) ) {
249249
continue;
250250
}
251251

252252
try {
253-
// Create parent directory if needed
254-
if ( !directoryExists( agentSkillsDir ) ) {
255-
directoryCreate( agentSkillsDir, true )
256-
}
253+
// Create parent directories if needed
254+
directoryCreate( agentSkillsDir, true, true )
257255

258256
// Compute a relative path from the link's parent dir → canonical dir
259-
var agentDirPath = Paths.get( agentSkillsDir )
260-
var targetDirPath = Paths.get( canonical )
261-
var relativePath = agentDirPath.relativize( targetDirPath )
262-
263-
Files.createSymbolicLink( Paths.get( linkPath ), relativePath )
257+
var relativeTarget = relativize(
258+
getDirectoryFromPath( linkPath ),
259+
fromSkillDirectory
260+
)
261+
262+
static.Files.createSymbolicLink(
263+
Paths.get( linkPath, [] ),
264+
Paths.get( relativeTarget, [] ),
265+
[]
266+
)
264267
} catch ( any e ) {
265268
variables.print
266269
.yellowLine( " ⚠️ Could not create symlink for agent '#agent#': #e.message#" )
@@ -297,7 +300,7 @@ component singleton {
297300
var linkPath = "#agentSkillsDir#/#skill#"
298301

299302
try {
300-
var path = Paths.get( linkPath )
303+
var path = Paths.get( linkPath, [] )
301304
if ( Files.isSymbolicLink( path ) ) {
302305
Files.delete( path )
303306
}
@@ -1328,4 +1331,48 @@ component singleton {
13281331
return lines.toList( chr( 10 ) )
13291332
}
13301333

1334+
/**
1335+
* Relativize a path from one location to another, we use this
1336+
* since Lucee's reflection fails, BoxLang works.
1337+
* TODO: Remove once we are in BoxLang CLI full.
1338+
*
1339+
* @fromPath The source path (absolute or relative)
1340+
* @toPath The target path (absolute or relative)
1341+
*
1342+
* @return Relative path from the source to the target
1343+
*/
1344+
function relativize( required string fromPath, required string toPath ) {
1345+
// Normalize separators
1346+
arguments.fromPath = replace( arguments.fromPath, "\", "/", "all" )
1347+
arguments.toPath = replace( arguments.toPath, "\", "/", "all" )
1348+
1349+
// Split into path segments
1350+
var fromParts = listToArray( arguments.fromPath, "/" )
1351+
var toParts = listToArray( arguments.toPath, "/" )
1352+
1353+
// Find common prefix
1354+
var i = 1
1355+
while (
1356+
i <= arrayLen( fromParts ) &&
1357+
i <= arrayLen( toParts ) &&
1358+
fromParts[ i ] == toParts[ i ]
1359+
) {
1360+
i++
1361+
}
1362+
1363+
var relative = []
1364+
1365+
// Go up from the source
1366+
for ( var x = i; x <= arrayLen( fromParts ); x++ ) {
1367+
relative.append( ".." )
1368+
}
1369+
1370+
// Go down to the target
1371+
for ( var x = i; x <= arrayLen( toParts ); x++ ) {
1372+
relative.append( toParts[ x ] )
1373+
}
1374+
1375+
return arrayToList( relative, "/" )
1376+
}
1377+
13311378
}

0 commit comments

Comments
 (0)