Skip to content

Commit c42a28f

Browse files
committed
lots of updates
1 parent f2a3aeb commit c42a28f

File tree

9 files changed

+143
-116
lines changed

9 files changed

+143
-116
lines changed

commands/coldbox/ai/agents/active.cfc

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ component extends="coldbox-cli.models.BaseAICommand" {
2121
showColdBoxBanner( "Active AI Agent" )
2222

2323
var info = ensureInstalled( arguments.directory )
24-
var manifest = readManifest( arguments.directory )
24+
var manifest = loadManifest( arguments.directory )
2525

2626
print.line()
2727

@@ -64,9 +64,9 @@ component extends="coldbox-cli.models.BaseAICommand" {
6464

6565
// Update manifest
6666
manifest.activeAgent = arguments.agent
67-
writeManifest( arguments.directory, manifest )
67+
saveManifest( arguments.directory, manifest )
6868

69-
showSuccess( "Active agent set to: #arguments.agent#" )
69+
printSuccess( "Active agent set to: #arguments.agent#" )
7070
printInfo( "This setting is informational - all configured agent files remain active." )
7171
printInfo( "Use it to track which AI assistant you're currently working with." )
7272
}

commands/coldbox/ai/agents/add.cfc

Lines changed: 33 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
* Creates the agent-specific instruction file
44
*
55
* Examples:
6+
* coldbox ai agents add (shows interactive selection)
67
* coldbox ai agents add claude
78
* coldbox ai agents add copilot,cursor
89
*/
@@ -14,11 +15,11 @@ component extends="coldbox-cli.models.BaseAICommand" {
1415
/**
1516
* Run the command
1617
*
17-
* @agent The agent name(s) to add (comma-separated: claude,copilot,cursor,codex,gemini,opencode)
18+
* @agent The agent name(s) to add (comma-separated: claude,copilot,cursor,codex,gemini,opencode). If not provided, shows interactive selection.
1819
* @directory The target directory (defaults to current directory)
1920
*/
2021
function run(
21-
required string agent,
22+
string agent = "",
2223
string directory = getCwd()
2324
){
2425
showColdBoxBanner( "Add AI Agent" )
@@ -27,9 +28,29 @@ component extends="coldbox-cli.models.BaseAICommand" {
2728

2829
print.line()
2930

31+
// If no agent provided, show multiselect prompt
32+
if ( !arguments.agent.len() ) {
33+
print.line()
34+
printWarn( "🤖 Agent Selection" )
35+
print.line()
36+
37+
var selectedAgents = multiselect( "Select one or more AI agents to add (use spacebar to select, enter to confirm):" )
38+
.options( variables.agentRegistry.AGENT_OPTIONS )
39+
.multiple()
40+
.required()
41+
.ask()
42+
43+
if ( !selectedAgents.len() ) {
44+
printWarn( "No agents selected." )
45+
return
46+
}
47+
48+
arguments.agent = selectedAgents.toList()
49+
}
50+
3051
// Parse comma-separated agents
3152
var agents = listToArray( arguments.agent )
32-
var validAgents = [ "claude", "copilot", "cursor", "codex", "gemini", "opencode" ]
53+
var validAgents = variables.agentRegistry.SUPPORTED_AGENTS
3354
var toAdd = []
3455
var invalid = []
3556

@@ -41,6 +62,7 @@ component extends="coldbox-cli.models.BaseAICommand" {
4162
}
4263
} )
4364

65+
// Validate agent names
4466
if ( invalid.len() ) {
4567
printError( "Invalid agent name(s): #invalid.toList()#" )
4668
printInfo( "Valid agents: #validAgents.toList()#" )
@@ -69,17 +91,17 @@ component extends="coldbox-cli.models.BaseAICommand" {
6991
}
7092
}
7193

72-
printInfo( "Adding agent(s): #toAdd.toList()#" )
7394
print.line()
95+
printInfo( "Adding agent(s): #toAdd.toList()#" )
7496

7597
// Get project language from manifest
76-
var manifest = readManifest( arguments.directory )
98+
var manifest = loadManifest( arguments.directory )
7799
var language = manifest.language ?: "boxlang"
78100

79101
// Configure each agent
80102
toAdd.each( ( agent ) => {
81103
variables.agentRegistry.configureAgent(
82-
arguments.directory,
104+
directory,
83105
agent,
84106
language
85107
)
@@ -93,20 +115,19 @@ component extends="coldbox-cli.models.BaseAICommand" {
93115
}
94116
} )
95117

96-
writeManifest( arguments.directory, manifest )
97-
98-
showSuccess( "Agent(s) added successfully!" )
118+
saveManifest( arguments.directory, manifest )
119+
print.line()
120+
printSuccess( "Agent(s) added successfully!" )
99121

100122
// Show where files were created
101123
var agentPaths = variables.agentRegistry.getAgentConfigPaths()
102124
printInfo( "Configuration files created:" )
103125
toAdd.each( ( agent ) => {
104-
var path = agentPaths[ agent ] ?: "AI_INSTRUCTIONS.md"
105-
print.indentedLine( " #path#" )
126+
print.indentedLine( " ⊕ #variables.agentRegistry.getAgentConfigPath( directory, agent )#" )
106127
} )
107128

108129
print.line()
109-
printHelp( "Tip: Use 'coldbox ai agents list' to see all configured agents" )
130+
printTip( "Use 'coldbox ai agents list' to see all configured agents" )
110131
}
111132

112133
}

commands/coldbox/ai/agents/list.cfc

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,6 @@ component extends="coldbox-cli.models.BaseAICommand" {
2727

2828
print.line()
2929
printInfo( "Configured AI Agents" )
30-
print.line()
3130

3231
if ( !info.agents.len() ) {
3332
printWarn( "No agents configured yet." )
@@ -36,18 +35,15 @@ component extends="coldbox-cli.models.BaseAICommand" {
3635
return
3736
}
3837

39-
var agentPaths = variables.agentRegistry.getAgentConfigPaths()
40-
4138
// Display each configured agent
4239
info.agents.each( ( agent ) => {
43-
var configPath = agentPaths[ agent ] ?: "AI_INSTRUCTIONS.md"
44-
var fullPath = directory & "/" & configPath
45-
var exists = fileExists( fullPath )
40+
var configPath = variables.agentRegistry.getAgentConfigPath( directory, agent )
41+
var exists = fileExists( configPath )
4642

4743
if ( exists ) {
4844
print.greenLine( " ✓ #agent#" )
4945
} else {
50-
print.redLine( " ✗ #agent# (config file missing)" )
46+
print.redLine( " ✗ #agent# (config file missing - run `coldbox ai refresh` to regenerate)" )
5147
}
5248

5349
if ( verbose ) {

commands/coldbox/ai/agents/remove.cfc

Lines changed: 7 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -48,24 +48,21 @@ component extends="coldbox-cli.models.BaseAICommand" {
4848
}
4949

5050
// Get config file path
51-
var agentPaths = variables.agentRegistry.getAgentConfigPaths()
52-
var configPath = agentPaths[ arguments.agent ] ?: "AI_INSTRUCTIONS.md"
53-
var fullPath = "#arguments.directory#/#configPath#"
51+
var configPath = variables.agentRegistry.getAgentConfigPath( arguments.directory, arguments.agent )
52+
print.line()
5453

5554
// Delete config file
56-
if ( fileExists( fullPath ) ) {
57-
fileDelete( fullPath )
55+
if ( fileExists( configPath ) ) {
56+
fileDelete( configPath )
5857
printSuccess( "✓ Deleted config file: #configPath#" )
5958
} else {
6059
printWarn( "Config file not found (may have been manually deleted)" )
6160
}
6261

6362
// Update manifest
64-
var manifestPath = "#arguments.directory#/.ai/.manifest.json"
65-
var manifest = deserializeJSON( fileRead( manifestPath ) )
66-
manifest.agents = manifest.agents.filter( ( a ) => a != arguments.agent )
67-
manifest.lastSync = dateTimeFormat( now(), "iso" )
68-
fileWrite( manifestPath, serializeJSON( manifest ) )
63+
var manifest = loadManifest( arguments.directory )
64+
manifest.agents = manifest.agents.filter( ( a ) => a != agent )
65+
saveManifest( arguments.directory, manifest )
6966

7067
print.line()
7168
printSuccess( "✓ Agent '#arguments.agent#' removed successfully!" )

models/AIService.cfc

Lines changed: 37 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -236,6 +236,43 @@ component singleton {
236236
return issues;
237237
}
238238

239+
/**
240+
* Load the manifest file
241+
*
242+
* @directory The project directory
243+
*/
244+
struct function loadManifest( required string directory ){
245+
var manifestPath = arguments.directory & "/.ai/.manifest.json";
246+
if ( !fileExists( manifestPath ) ) {
247+
return {};
248+
}
249+
return deserializeJSON( fileRead( manifestPath ) );
250+
}
251+
252+
/**
253+
* Get the manifest file path for a directory
254+
*
255+
* @directory The target directory
256+
*
257+
* @return The full path to the manifest file
258+
*/
259+
string function getManifestPath( required string directory ){
260+
return arguments.directory & "/.ai/.manifest.json";
261+
}
262+
263+
/**
264+
* Save a manifest file
265+
*
266+
* @directory The project directory
267+
* @manifest The manifest struct to save
268+
*/
269+
AIService function saveManifest( required string directory, required struct manifest ){
270+
var manifestPath = getManifestPath( arguments.directory )
271+
arguments.manifest.lastSync = dateTimeFormat( now(), "iso" )
272+
fileWrite( manifestPath, serializeJSON( arguments.manifest, true ) )
273+
return this
274+
}
275+
239276
// ========================================
240277
// Private Helpers
241278
// ========================================
@@ -265,30 +302,6 @@ component singleton {
265302
} )
266303
}
267304

268-
/**
269-
* Save manifest file
270-
*
271-
* @directory The project directory
272-
* @manifest The manifest struct to save
273-
*/
274-
private function saveManifest( required string directory, required struct manifest ){
275-
var manifestPath = arguments.directory & "/.ai/.manifest.json";
276-
fileWrite( manifestPath, serializeJSON( arguments.manifest, true ) );
277-
}
278-
279-
/**
280-
* Load manifest file
281-
*
282-
* @directory The project directory
283-
*/
284-
private function loadManifest( required string directory ){
285-
var manifestPath = arguments.directory & "/.ai/.manifest.json";
286-
if ( !fileExists( manifestPath ) ) {
287-
return {};
288-
}
289-
return deserializeJSON( fileRead( manifestPath ) );
290-
}
291-
292305
/**
293306
* Update box.json with AI configuration
294307
*

models/AgentRegistry.cfc

Lines changed: 40 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,31 @@ component singleton {
1010
property name="wirebox" inject="wirebox";
1111
property name="utility" inject="Utility@coldbox-cli";
1212

13+
static {
14+
SUPPORTED_AGENTS = [ "claude", "copilot", "cursor", "codex", "gemini", "opencode" ]
15+
AGENT_FILES = {
16+
"claude" : "CLAUDE.md",
17+
"copilot" : ".github/copilot-instructions.md",
18+
"cursor" : ".cursorrules",
19+
"codex" : ".codex/instructions.md",
20+
"gemini" : ".gemini/instructions.md",
21+
"opencode" : ".opencode/instructions.md"
22+
}
23+
AGENT_OPTIONS = [
24+
{ display: "Claude (Anthropic) - Recommended for general development", value: "claude" },
25+
{ display: "GitHub Copilot - Integrated with VS Code", value: "copilot" },
26+
{ display: "Cursor AI - AI-first code editor", value: "cursor" },
27+
{ display: "Codex (OpenAI) - GPT-powered coding assistant", value: "codex" },
28+
{ display: "Gemini (Google) - Google's AI assistant", value: "gemini" },
29+
{ display: "OpenCode - Open source AI assistant", value: "opencode" }
30+
]
31+
}
32+
33+
// Expose them as instance properties for easier access in commands
34+
this.SUPPORTED_AGENTS = static.SUPPORTED_AGENTS
35+
this.AGENT_OPTIONS = static.AGENT_OPTIONS
36+
this.AGENT_FILES = static.AGENT_FILES
37+
1338
/**
1439
* Configure agents for a project
1540
*
@@ -30,31 +55,19 @@ component singleton {
3055
}
3156

3257
/**
33-
* Get a specific agent's config file path
58+
* Get the config path mapping for all supported agents or a specific agent if passed
3459
*
35-
* @agent The agent name (claude, copilot, cursor, etc.)
60+
* @agentName Optional agent name to get specific path for (claude, copilot, cursor, codex, gemini, opencode)
3661
*
37-
* @return The expected config file path for the agent
62+
* @return Struct with agent names as keys and config paths as values as per their conventions
3863
*/
39-
function getAgentConfigPath( required string agent ){
40-
var paths = getAgentConfigPaths()
41-
return paths[ arguments.agent ] ?: "AI_INSTRUCTIONS.md"
42-
}
64+
function getAgentConfigPaths( string agentName ){
4365

44-
/**
45-
* Get the config path mapping for all supported agents
46-
*
47-
* @return Struct with agent names as keys and config paths as values
48-
*/
49-
function getAgentConfigPaths(){
50-
return {
51-
"claude" : "CLAUDE.md",
52-
"copilot" : ".github/copilot-instructions.md",
53-
"cursor" : ".cursorrules",
54-
"codex" : ".codex/instructions.md",
55-
"gemini" : ".gemini/instructions.md",
56-
"opencode" : ".opencode/instructions.md"
66+
if( !isNull( arguments.agentName ) ){
67+
return static.AGENT_FILES[ arguments.agentName ] ?: "AI_INSTRUCTIONS.md"
5768
}
69+
70+
return static.AGENT_FILES
5871
}
5972

6073
/**
@@ -113,12 +126,17 @@ component singleton {
113126
}
114127

115128
/**
116-
* Get agent config file path
129+
* Get agent config file path for a specific agent on a specific project directory
117130
*
118131
* @directory The project directory
119132
* @agent The agent name (claude, copilot, cursor, etc.)
120133
*/
121-
private function getAgentConfigPath( required string directory, required string agent ){
134+
function getAgentConfigPath( required string directory, required string agent ){
135+
// Check if directory ends in / or \ and remove it for consistent path building
136+
if ( right( arguments.directory, 1 ) == "/" || right( arguments.directory, 1 ) == "\" ) {
137+
arguments.directory = left( arguments.directory, len( arguments.directory ) - 1 )
138+
}
139+
122140
switch ( arguments.agent ) {
123141
case "claude":
124142
return "#arguments.directory#/CLAUDE.md"

0 commit comments

Comments
 (0)