feat(cli): expose command metadata in command-file endpoints

This commit is contained in:
kirillk
2026-08-03 14:48:17 -04:00
parent 76cf476dba
commit c7fd8431b8
12 changed files with 93 additions and 5 deletions
+1 -1
View File
@@ -2,4 +2,4 @@
"@kilocode/cli": minor
---
Add kilocode command-file endpoints so clients can list editable command/workflow files and remove them.
Add kilocode command-file endpoints so clients can list editable command/workflow files, inspect model and reasoning variant metadata, and remove them.
@@ -631,8 +631,12 @@ object KiloCliDataParser {
CommandInfo(
name = obj.str("name") ?: "",
description = obj.str("description"),
agent = obj.str("agent"),
model = obj.str("model"),
variant = obj.str("variant"),
source = obj.str("source"),
hints = obj["hints"]?.jsonArray?.mapNotNull { it.jsonPrimitive.contentOrNull } ?: emptyList(),
subtask = obj.flagOrNull("subtask"),
)
}
@@ -663,9 +667,13 @@ object KiloCliDataParser {
CommandDto(
name = name,
description = obj.str("description"),
agent = obj.str("agent"),
model = obj.str("model"),
variant = obj.str("variant"),
source = obj.str("source"),
hints = obj["hints"].arr()?.mapNotNull { it.jsonPrimitive.contentOrNull } ?: emptyList(),
template = obj.str("template"),
subtask = obj.flagOrNull("subtask"),
)
}
@@ -677,11 +685,15 @@ object KiloCliDataParser {
CommandFileDto(
name = name,
description = obj.str("description"),
agent = obj.str("agent"),
model = obj.str("model"),
variant = obj.str("variant"),
source = obj.str("source"),
builtin = obj.bool("builtin"),
location = location,
editable = obj.bool("editable"),
content = obj.str("content"),
subtask = obj.flagOrNull("subtask"),
hints = obj["hints"].arr()?.mapNotNull { it.jsonPrimitive.contentOrNull } ?: emptyList(),
)
}
@@ -56,8 +56,12 @@ internal object KiloWorkspaceDtoMapper {
fun command(c: CommandInfo) = CommandDto(
name = c.name,
description = c.description,
agent = c.agent,
model = c.model,
variant = c.variant,
source = c.source,
hints = c.hints,
subtask = c.subtask,
)
fun skill(s: SkillInfo) = SkillDto(
@@ -135,8 +135,12 @@ data class AgentInfo(
data class CommandInfo(
val name: String,
val description: String?,
val agent: String?,
val model: String?,
val variant: String?,
val source: String?,
val hints: List<String>,
val subtask: Boolean?,
)
data class SkillInfo(
@@ -1817,7 +1817,7 @@ class KiloCliDataParserTest {
@Test
fun `parseCommands - maps name, description, source, and hints`() {
val raw = """[
{"name":"init","description":"guided AGENTS.md setup","template":"static body","hints":["${'$'}ARGUMENTS"],"source":"command"},
{"name":"init","description":"guided AGENTS.md setup","agent":"reviewer","model":"anthropic/claude-sonnet-4-6","variant":"high","template":"static body","hints":["${'$'}ARGUMENTS"],"source":"command","subtask":true},
{"name":"mcp-tool","template":"","hints":["${'$'}1","${'$'}2"],"source":"mcp"}
]"""
@@ -1826,8 +1826,12 @@ class KiloCliDataParserTest {
assertEquals(2, result.size)
assertEquals("init", result[0].name)
assertEquals("guided AGENTS.md setup", result[0].description)
assertEquals("reviewer", result[0].agent)
assertEquals("anthropic/claude-sonnet-4-6", result[0].model)
assertEquals("high", result[0].variant)
assertEquals("command", result[0].source)
assertEquals(listOf("\$ARGUMENTS"), result[0].hints)
assertEquals(true, result[0].subtask)
assertEquals("mcp", result[1].source)
assertEquals(listOf("\$1", "\$2"), result[1].hints)
}
@@ -125,7 +125,7 @@ class KiloAgentBehaviorRpcApiImplTest {
val file = Files.createDirectories(dir.resolve("command")).resolve("review.md")
Files.writeString(file, "---\ndescription: Review code\n---\n\nReview $" + "ARGUMENTS")
mock.commandFiles = """[
{"name":"review","description":"Review code","source":"command","builtin":false,"location":"$file","editable":true,"content":"Review"},
{"name":"review","description":"Review code","agent":"reviewer","model":"anthropic/claude-sonnet-4-6","variant":"high","source":"command","builtin":false,"location":"$file","editable":true,"content":"Review","subtask":true},
{"name":"init","source":"command","builtin":true,"location":"builtin","editable":false,"content":"Init"}
]""".trimIndent()
val rpc = rpc()
@@ -133,6 +133,10 @@ class KiloAgentBehaviorRpcApiImplTest {
val commands = rpc.commandFiles("/test project")
assertEquals(listOf("review", "init"), commands.map { it.name })
assertEquals(true, commands.single { it.name == "review" }.editable)
assertEquals("reviewer", commands.single { it.name == "review" }.agent)
assertEquals("anthropic/claude-sonnet-4-6", commands.single { it.name == "review" }.model)
assertEquals("high", commands.single { it.name == "review" }.variant)
assertEquals(true, commands.single { it.name == "review" }.subtask)
assertEquals(false, commands.single { it.name == "init" }.editable)
assertTrue(rpc.removeCommand("/test project", file.toString()))
@@ -6,7 +6,11 @@ import kotlinx.serialization.Serializable
data class CommandDto(
val name: String,
val description: String? = null,
val agent: String? = null,
val model: String? = null,
val variant: String? = null,
val source: String? = null,
val hints: List<String> = emptyList(),
val template: String? = null,
val subtask: Boolean? = null,
)
@@ -6,10 +6,14 @@ import kotlinx.serialization.Serializable
data class CommandFileDto(
val name: String,
val description: String? = null,
val agent: String? = null,
val model: String? = null,
val variant: String? = null,
val source: String? = null,
val builtin: Boolean = false,
val location: String,
val editable: Boolean = false,
val content: String? = null,
val subtask: Boolean? = null,
val hints: List<String> = emptyList(),
)
@@ -9,11 +9,15 @@ import { WorkflowsMigrator } from "@/kilocode/workflows-migrator"
export const Info = Schema.Struct({
name: Schema.String,
description: Schema.optional(Schema.String),
agent: Schema.optional(Schema.String),
model: Schema.optional(Schema.String),
variant: Schema.optional(Schema.String),
source: Schema.optional(Schema.String),
builtin: Schema.Boolean,
location: Schema.String,
editable: Schema.Boolean,
content: Schema.optional(Schema.String),
subtask: Schema.optional(Schema.Boolean),
hints: Schema.Array(Schema.String),
}).annotate({ identifier: "CommandFile" })
@@ -85,22 +89,30 @@ export async function discover(input: { commands: readonly Command.Info[]; direc
return {
name: cmd.name,
description: description(cmd, file),
agent: cmd.agent,
model: cmd.model,
variant: cmd.variant,
source: cmd.source,
builtin: false,
location: file.location,
editable: true,
content: file.content,
subtask: cmd.subtask,
hints: cmd.hints,
}
}
return {
name: cmd.name,
description: description(cmd),
agent: cmd.agent,
model: cmd.model,
variant: cmd.variant,
source: cmd.source,
builtin: true,
location: "builtin",
editable: false,
content: literal(cmd),
subtask: cmd.subtask,
hints: cmd.hints,
}
})
@@ -22,8 +22,12 @@ function cmd(input: Partial<Command.Info> & Pick<Command.Info, "name">): Command
return {
name: input.name,
description: input.description,
agent: input.agent,
model: input.model,
variant: input.variant,
source: input.source,
template: input.template ?? "body",
subtask: input.subtask,
hints: input.hints ?? [],
}
}
@@ -38,11 +42,31 @@ describe("CommandFiles", () => {
const items = await CommandFiles.discover({
directory: dir,
directories: [path.join(dir, ".kilo")],
commands: [cmd({ name: "review", source: "command", hints: ["$ARGUMENTS"] }), cmd({ name: "init", source: "command" })],
commands: [
cmd({
name: "review",
source: "command",
agent: "reviewer",
model: "anthropic/claude-sonnet-4-6",
variant: "high",
subtask: true,
hints: ["$ARGUMENTS"],
}),
cmd({ name: "init", source: "command" }),
],
})
expect(items.map((item) => item.name)).toEqual(["review", "init"])
expect(items[0]).toMatchObject({ name: "review", editable: true, builtin: false, location: file })
expect(items[0]).toMatchObject({
name: "review",
editable: true,
builtin: false,
location: file,
agent: "reviewer",
model: "anthropic/claude-sonnet-4-6",
variant: "high",
subtask: true,
})
expect(items[0].content).toContain("Review $ARGUMENTS")
expect(items[1]).toMatchObject({ name: "init", editable: false, builtin: true, location: "builtin" })
})
+4
View File
@@ -3237,11 +3237,15 @@ export type AgentRequirementResult = {
export type CommandFile = {
name: string
description?: string
agent?: string
model?: string
variant?: string
source?: string
builtin: boolean
location: string
editable: boolean
content?: string
subtask?: boolean
hints: Array<string>
}
+12
View File
@@ -34497,6 +34497,15 @@
"description": {
"type": "string"
},
"agent": {
"type": "string"
},
"model": {
"type": "string"
},
"variant": {
"type": "string"
},
"source": {
"type": "string"
},
@@ -34512,6 +34521,9 @@
"content": {
"type": "string"
},
"subtask": {
"type": "boolean"
},
"hints": {
"type": "array",
"items": {