Skip to content
6 changes: 6 additions & 0 deletions .changeset/append-system-prompt-cli.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
"@kilocode/cli": patch
"kilo-code": patch
---

Add `--append-system-prompt` CLI option to append custom instructions to the system prompt
109 changes: 109 additions & 0 deletions cli/src/__tests__/append-system-prompt.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
import { describe, it, expect } from "vitest"
import type { CLIOptions } from "../types/cli.js"

describe("Append System Prompt CLI Option", () => {
describe("CLIOptions type", () => {
it("should accept appendSystemPrompt as a string option", () => {
const options: CLIOptions = {
mode: "code",
workspace: "/test/workspace",
appendSystemPrompt: "Custom instructions here",
}

expect(options.appendSystemPrompt).toBe("Custom instructions here")
})

it("should allow appendSystemPrompt to be undefined", () => {
const options: CLIOptions = {
mode: "code",
workspace: "/test/workspace",
}

expect(options.appendSystemPrompt).toBeUndefined()
})

it("should handle empty string for appendSystemPrompt", () => {
const options: CLIOptions = {
mode: "code",
workspace: "/test/workspace",
appendSystemPrompt: "",
}

expect(options.appendSystemPrompt).toBe("")
})

it("should handle multi-line appendSystemPrompt", () => {
const multiLinePrompt = `Line 1
Line 2
Line 3`
const options: CLIOptions = {
mode: "code",
workspace: "/test/workspace",
appendSystemPrompt: multiLinePrompt,
}

expect(options.appendSystemPrompt).toBe(multiLinePrompt)
})
})

describe("CLI flag parsing", () => {
it("should parse --append-system-prompt flag with value", () => {
// This test validates the expected behavior when the flag is parsed
const mockArgs = ["--append-system-prompt", "Custom instructions"]
const expectedValue = "Custom instructions"

// Simulate what commander.js would do
const parsedValue = mockArgs[1]
expect(parsedValue).toBe(expectedValue)
})

it("should handle --append-system-prompt with quoted multi-word value", () => {
const mockArgs = ["--append-system-prompt", "Always use TypeScript strict mode"]
const expectedValue = "Always use TypeScript strict mode"

const parsedValue = mockArgs[1]
expect(parsedValue).toBe(expectedValue)
})
})

describe("System prompt integration", () => {
it("should append custom text to system prompt when provided", () => {
const basePrompt = "You are Kilo Code, an AI assistant."
const appendText = "Always write tests first."
const expectedPrompt = `${basePrompt}

${appendText}`

const result = `${basePrompt}\n\n${appendText}`
expect(result).toBe(expectedPrompt)
})

it("should not modify system prompt when appendSystemPrompt is undefined", () => {
const basePrompt = "You are Kilo Code, an AI assistant."
const appendText = undefined

const result = appendText ? `${basePrompt}\n\n${appendText}` : basePrompt
expect(result).toBe(basePrompt)
})

it("should not modify system prompt when appendSystemPrompt is empty string", () => {
const basePrompt = "You are Kilo Code, an AI assistant."
const appendText = ""

const result = appendText ? `${basePrompt}\n\n${appendText}` : basePrompt
expect(result).toBe(basePrompt)
})

it("should properly format appended text with newlines", () => {
const basePrompt = "You are Kilo Code."
const appendText = "Rule 1: Test first\nRule 2: Keep it simple"
const expectedPrompt = `You are Kilo Code.

Rule 1: Test first
Rule 2: Keep it simple`

const result = `${basePrompt}\n\n${appendText}`
expect(result).toBe(expectedPrompt)
})
})
})
4 changes: 4 additions & 0 deletions cli/src/cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,10 @@ export class CLI {
serviceOptions.customModes = this.options.customModes
}

if (this.options.appendSystemPrompt) {
serviceOptions.appendSystemPrompt = this.options.appendSystemPrompt
}

this.service = createExtensionService(serviceOptions)
logs.debug("ExtensionService created with identity", "CLI", {
hasIdentity: !!identity,
Expand Down
17 changes: 17 additions & 0 deletions cli/src/host/ExtensionHost.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ export interface ExtensionHostOptions {
extensionRootPath: string // Root path for extension assets
identity?: IdentityInfo // Identity information for VSCode environment
customModes?: ModeConfig[] // Custom modes configuration
appendSystemPrompt?: string // Custom text to append to system prompt
}

// Extension module interface
Expand Down Expand Up @@ -786,6 +787,8 @@ export class ExtensionHost extends EventEmitter {
imageGeneration: false,
runSlashCommand: false,
},
// Add appendSystemPrompt from CLI options
...(this.options.appendSystemPrompt && { appendSystemPrompt: this.options.appendSystemPrompt }),
}

// The CLI will inject the actual configuration through updateState
Expand Down Expand Up @@ -1037,6 +1040,20 @@ export class ExtensionHost extends EventEmitter {
settings: Object.keys(autoApprovalSettings),
})
}

// Sync appendSystemPrompt to extension
// This setting is passed from CLI options and needs to be stored in the extension's
// contextProxy so it's available when generating the system prompt
const appendSystemPrompt = configState.appendSystemPrompt || this.options.appendSystemPrompt
if (appendSystemPrompt) {
await this.sendWebviewMessage({
type: "updateSettings",
updatedSettings: { appendSystemPrompt },
})
logs.debug("appendSystemPrompt synchronized to extension", "ExtensionHost", {
length: appendSystemPrompt.length,
})
}
}

/**
Expand Down
2 changes: 2 additions & 0 deletions cli/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ program
.option("-s, --session <sessionId>", "Restore a session by ID")
.option("-f, --fork <shareId>", "Fork a session by ID")
.option("--nosplash", "Disable the welcome message and update notifications", false)
.option("--append-system-prompt <text>", "Append custom instructions to the system prompt")
.argument("[prompt]", "The prompt or command to execute")
.action(async (prompt, options) => {
// Validate that --existing-branch requires --parallel
Expand Down Expand Up @@ -228,6 +229,7 @@ program
session: options.session,
fork: options.fork,
noSplash: options.nosplash,
appendSystemPrompt: options.appendSystemPrompt,
})
await cli.start()
await cli.dispose()
Expand Down
9 changes: 8 additions & 1 deletion cli/src/services/extension.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ export interface ExtensionServiceOptions {
extensionRootPath?: string
/** Identity information for VSCode environment */
identity?: IdentityInfo
/** Custom text to append to system prompt */
appendSystemPrompt?: string
}

/**
Expand Down Expand Up @@ -73,9 +75,10 @@ export interface ExtensionServiceEvents {
export class ExtensionService extends EventEmitter {
private extensionHost: ExtensionHost
private messageBridge: MessageBridge
private options: Required<Omit<ExtensionServiceOptions, "identity" | "customModes">> & {
private options: Required<Omit<ExtensionServiceOptions, "identity" | "customModes" | "appendSystemPrompt">> & {
identity?: IdentityInfo
customModes?: ModeConfig[]
appendSystemPrompt?: string
}
private isInitialized = false
private isDisposed = false
Expand All @@ -95,6 +98,7 @@ export class ExtensionService extends EventEmitter {
extensionRootPath: options.extensionRootPath || extensionPaths.extensionRootPath,
...(options.identity && { identity: options.identity }),
...(options.customModes && { customModes: options.customModes }),
...(options.appendSystemPrompt && { appendSystemPrompt: options.appendSystemPrompt }),
}

// Create extension host
Expand All @@ -109,6 +113,9 @@ export class ExtensionService extends EventEmitter {
if (this.options.customModes) {
hostOptions.customModes = this.options.customModes
}
if (this.options.appendSystemPrompt) {
hostOptions.appendSystemPrompt = this.options.appendSystemPrompt
}
this.extensionHost = createExtensionHost(hostOptions)

// Create message bridge
Expand Down
1 change: 1 addition & 0 deletions cli/src/types/cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,4 +43,5 @@ export interface CLIOptions {
session?: string
fork?: string
noSplash?: boolean
appendSystemPrompt?: string
}
1 change: 1 addition & 0 deletions cli/src/types/messages.ts
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,7 @@ export interface ExtensionState {
cwd?: string
organizationAllowList?: OrganizationAllowList
routerModels?: RouterModels
appendSystemPrompt?: string // Custom text to append to system prompt (CLI only)
[key: string]: unknown
}

Expand Down
1 change: 1 addition & 0 deletions packages/types/src/global-settings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -235,6 +235,7 @@ export const globalSettingsSchema = z.object({
hasOpenedModeSelector: z.boolean().optional(),
lastModeExportPath: z.string().optional(),
lastModeImportPath: z.string().optional(),
appendSystemPrompt: z.string().optional(), // kilocode_change: Custom text to append to system prompt (CLI only)
})

export type GlobalSettings = z.infer<typeof globalSettingsSchema>
Expand Down
7 changes: 7 additions & 0 deletions src/core/prompts/system.ts
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,13 @@ ${await addCustomInstructions(baseInstructions, globalCustomInstructions || "",
settings,
})}`

// kilocode_change start: Append custom system prompt from CLI if provided
const appendSystemPrompt = clineProviderState?.appendSystemPrompt
if (appendSystemPrompt) {
return `${basePrompt}\n\n${appendSystemPrompt}`
}
// kilocode_change end

return basePrompt
}

Expand Down
1 change: 1 addition & 0 deletions src/core/webview/ClineProvider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2705,6 +2705,7 @@ export class ClineProvider
return false
}
})(),
appendSystemPrompt: stateValues.appendSystemPrompt, // kilocode_change: CLI append system prompt
}
}

Expand Down
1 change: 1 addition & 0 deletions src/shared/ExtensionMessage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -543,6 +543,7 @@ export type ExtensionState = Pick<
showTimestamps?: boolean // kilocode_change: Show timestamps in chat messages
debug?: boolean
speechToTextStatus?: { available: boolean; reason?: "openaiKeyMissing" | "ffmpegNotInstalled" } // kilocode_change: Speech-to-text availability status with failure reason
appendSystemPrompt?: string // kilocode_change: Custom text to append to system prompt (CLI only)
}

export interface ClineSayTool {
Expand Down