or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

arguments.mdconfiguration-sources.mdcore-commands.mdexceptions.mdindex.mdoptions.mdparameter-groups.mdparameter-types.mdshell-completion.mdtesting-utilities.md

shell-completion.mddocs/

0

# Shell Completion

1

2

Generate shell completion scripts for Bash and Fish, with support for dynamic completion candidates including files, hostnames, and custom completions.

3

4

## Capabilities

5

6

### Completion Candidates

7

8

Define what values should be suggested for parameters during shell completion.

9

10

```kotlin { .api }

11

/**

12

* Sealed class representing different types of completion candidates

13

*/

14

sealed class CompletionCandidates {

15

/** No completion candidates */

16

object None : CompletionCandidates()

17

18

/** File and directory path completion */

19

object Path : CompletionCandidates()

20

21

/** Hostname completion */

22

object Hostname : CompletionCandidates()

23

24

/** Username completion */

25

object Username : CompletionCandidates()

26

27

/** Fixed set of completion candidates */

28

data class Fixed(val candidates: Set<String>) : CompletionCandidates()

29

30

/** Custom completion generator */

31

data class Custom(val generator: (ShellType) -> String?) : CompletionCandidates()

32

}

33

34

/**

35

* Shell types supported for completion

36

*/

37

enum class ShellType { BASH, FISH, ZSH }

38

```

39

40

**Usage Examples:**

41

42

```kotlin

43

class MyCommand : CliktCommand() {

44

// File path completion

45

private val configFile by option("--config", help = "Configuration file",

46

completionCandidates = CompletionCandidates.Path)

47

48

// Hostname completion

49

private val server by option("--server", help = "Server hostname",

50

completionCandidates = CompletionCandidates.Hostname)

51

52

// Fixed choices completion

53

private val format by option("--format", help = "Output format",

54

completionCandidates = CompletionCandidates.Fixed(setOf("json", "xml", "yaml")))

55

56

// Custom completion

57

private val service by option("--service", help = "Service name",

58

completionCandidates = CompletionCandidates.Custom { shell ->

59

when (shell) {

60

ShellType.BASH -> "_custom_services_bash"

61

ShellType.FISH -> "__custom_services_fish"

62

ShellType.ZSH -> "_custom_services_zsh"

63

}

64

})

65

66

// Argument with path completion

67

private val inputFile by argument(name = "FILE", help = "Input file",

68

completionCandidates = CompletionCandidates.Path)

69

70

override fun run() {

71

echo("Config: $configFile")

72

echo("Server: $server")

73

echo("Format: $format")

74

echo("Service: $service")

75

echo("Input: $inputFile")

76

}

77

}

78

```

79

80

### Completion Generators

81

82

Generate shell completion scripts for different shell types.

83

84

```kotlin { .api }

85

/**

86

* Base interface for completion script generators

87

*/

88

interface CompletionGenerator {

89

/**

90

* Generate completion script for the given command

91

* @param commandName Name of the command

92

* @param command Root command

93

* @return Generated completion script

94

*/

95

fun generateScript(commandName: String, command: CliktCommand): String

96

}

97

98

/**

99

* Bash completion script generator

100

*/

101

class BashCompletionGenerator : CompletionGenerator {

102

override fun generateScript(commandName: String, command: CliktCommand): String

103

}

104

105

/**

106

* Fish completion script generator

107

*/

108

class FishCompletionGenerator : CompletionGenerator {

109

override fun generateScript(commandName: String, command: CliktCommand): String

110

}

111

```

112

113

**Usage Examples:**

114

115

```kotlin

116

class MyCommand : CliktCommand() {

117

private val generateCompletion by option("--generate-completion",

118

help = "Generate shell completion script")

119

.choice("bash", "fish")

120

121

override fun run() {

122

if (generateCompletion != null) {

123

val generator = when (generateCompletion) {

124

"bash" -> BashCompletionGenerator()

125

"fish" -> FishCompletionGenerator()

126

else -> error("Unsupported shell: $generateCompletion")

127

}

128

129

val script = generator.generateScript("mycommand", this)

130

echo(script)

131

return

132

}

133

134

// Normal command execution

135

echo("Running normal command...")

136

}

137

}

138

139

// Standalone completion script generation

140

fun main() {

141

val command = MyComplexCommand()

142

143

// Generate Bash completion

144

val bashScript = BashCompletionGenerator().generateScript("myapp", command)

145

File("completion/myapp-completion.bash").writeText(bashScript)

146

147

// Generate Fish completion

148

val fishScript = FishCompletionGenerator().generateScript("myapp", command)

149

File("completion/myapp.fish").writeText(fishScript)

150

151

// Run the command normally

152

command.main()

153

}

154

```

155

156

### Built-in Completion Options

157

158

Add completion generation options to your command.

159

160

```kotlin { .api }

161

/**

162

* Add completion generation option to command

163

* @param option Option names for completion generation

164

* @param help Help text for the option

165

*/

166

fun CliktCommand.completionOption(

167

vararg option: String = arrayOf("--generate-completion"),

168

help: String = "Generate shell completion script and exit"

169

): Unit

170

```

171

172

**Usage Examples:**

173

174

```kotlin

175

class MyCommand : CliktCommand() {

176

init {

177

// Add standard completion option

178

completionOption()

179

}

180

181

// Your regular options and arguments

182

private val input by option("--input", help = "Input file",

183

completionCandidates = CompletionCandidates.Path)

184

private val verbose by option("-v", "--verbose", help = "Verbose output").flag()

185

186

override fun run() {

187

echo("Input: $input")

188

if (verbose) echo("Verbose mode enabled")

189

}

190

}

191

192

// Custom completion option names

193

class MyCommand2 : CliktCommand() {

194

init {

195

completionOption("--completion", "--comp", help = "Generate completion script")

196

}

197

198

override fun run() {

199

echo("Running command...")

200

}

201

}

202

```

203

204

### Dynamic Completion

205

206

Create dynamic completion that depends on runtime context or previous arguments.

207

208

```kotlin { .api }

209

/**

210

* Custom completion function type

211

*/

212

typealias CompletionFunction = (context: CompletionContext) -> List<String>

213

214

/**

215

* Context provided to custom completion functions

216

*/

217

data class CompletionContext(

218

val command: CliktCommand,

219

val currentWord: String,

220

val previousWords: List<String>

221

)

222

```

223

224

**Usage Examples:**

225

226

```kotlin

227

class DatabaseCommand : CliktCommand() {

228

// Database names depend on the connection

229

private val database by option("--database", help = "Database name",

230

completionCandidates = CompletionCandidates.Custom { shell ->

231

when (shell) {

232

ShellType.BASH -> """

233

COMPREPLY=($(compgen -W "$(myapp list-databases 2>/dev/null || echo '')" -- "${"$"}{COMP_WORDS[COMP_CWORD]}"))

234

""".trimIndent()

235

ShellType.FISH -> """

236

myapp list-databases 2>/dev/null

237

""".trimIndent()

238

else -> null

239

}

240

})

241

242

// Table names depend on selected database

243

private val table by option("--table", help = "Table name",

244

completionCandidates = CompletionCandidates.Custom { shell ->

245

when (shell) {

246

ShellType.BASH -> """

247

local db_option=""

248

for ((i=1; i<COMP_CWORD; i++)); do

249

if [[ ${"$"}{COMP_WORDS[i]} == "--database" ]]; then

250

db_option="--database ${"$"}{COMP_WORDS[i+1]}"

251

break

252

fi

253

done

254

COMPREPLY=($(compgen -W "$(myapp list-tables ${"$"}db_option 2>/dev/null || echo '')" -- "${"$"}{COMP_WORDS[COMP_CWORD]}"))

255

""".trimIndent()

256

ShellType.FISH -> """

257

set -l db_option ""

258

set -l prev_was_db_flag false

259

for word in (commandline -opc)[2..-1]

260

if test "$prev_was_db_flag" = true

261

set db_option "--database $word"

262

set prev_was_db_flag false

263

else if test "$word" = "--database"

264

set prev_was_db_flag true

265

end

266

end

267

myapp list-tables $db_option 2>/dev/null

268

""".trimIndent()

269

else -> null

270

}

271

})

272

273

override fun run() {

274

echo("Database: $database")

275

echo("Table: $table")

276

}

277

}

278

279

// Environment-aware completion

280

class DeployCommand : CliktCommand() {

281

private val environment by option("--env", help = "Environment",

282

completionCandidates = CompletionCandidates.Custom { shell ->

283

when (shell) {

284

ShellType.BASH -> """

285

COMPREPLY=($(compgen -W "dev staging prod" -- "${"$"}{COMP_WORDS[COMP_CWORD]}"))

286

""".trimIndent()

287

ShellType.FISH -> "echo -e 'dev\nstaging\nprod'"

288

else -> null

289

}

290

})

291

292

private val service by option("--service", help = "Service name",

293

completionCandidates = CompletionCandidates.Custom { shell ->

294

when (shell) {

295

ShellType.BASH -> """

296

local env=""

297

for ((i=1; i<COMP_CWORD; i++)); do

298

if [[ ${"$"}{COMP_WORDS[i]} == "--env" ]]; then

299

env=${"$"}{COMP_WORDS[i+1]}

300

break

301

fi

302

done

303

if [[ -n "${"$"}env" ]]; then

304

COMPREPLY=($(compgen -W "$(kubectl get services -n ${"$"}env --no-headers -o custom-columns=':metadata.name' 2>/dev/null || echo '')" -- "${"$"}{COMP_WORDS[COMP_CWORD]}"))

305

fi

306

""".trimIndent()

307

ShellType.FISH -> """

308

set -l env ""

309

set -l prev_was_env_flag false

310

for word in (commandline -opc)[2..-1]

311

if test "$prev_was_env_flag" = true

312

set env $word

313

set prev_was_env_flag false

314

else if test "$word" = "--env"

315

set prev_was_env_flag true

316

end

317

end

318

if test -n "$env"

319

kubectl get services -n $env --no-headers -o custom-columns=':metadata.name' 2>/dev/null

320

end

321

""".trimIndent()

322

else -> null

323

}

324

})

325

326

override fun run() {

327

echo("Deploying service $service to $environment")

328

}

329

}

330

```

331

332

### Installation and Usage

333

334

Instructions for installing and using completion scripts.

335

336

**Bash Completion Installation:**

337

338

```bash

339

# Generate completion script

340

mycommand --generate-completion bash > mycommand-completion.bash

341

342

# Install system-wide (Linux)

343

sudo cp mycommand-completion.bash /etc/bash_completion.d/

344

345

# Install user-specific

346

mkdir -p ~/.local/share/bash-completion/completions

347

cp mycommand-completion.bash ~/.local/share/bash-completion/completions/mycommand

348

349

# Or source directly in ~/.bashrc

350

echo "source /path/to/mycommand-completion.bash" >> ~/.bashrc

351

```

352

353

**Fish Completion Installation:**

354

355

```fish

356

# Generate completion script

357

mycommand --generate-completion fish > mycommand.fish

358

359

# Install user-specific

360

mkdir -p ~/.config/fish/completions

361

cp mycommand.fish ~/.config/fish/completions/

362

363

# Fish will automatically load completions from this directory

364

```

365

366

**Advanced Completion Patterns:**

367

368

```kotlin

369

class GitLikeCommand : CliktCommand() {

370

// Subcommand completion

371

init {

372

subcommands(

373

CommitCommand(),

374

PushCommand(),

375

PullCommand(),

376

BranchCommand()

377

)

378

}

379

380

override fun run() = Unit

381

}

382

383

class CommitCommand : CliktCommand(name = "commit") {

384

private val files by argument(help = "Files to commit",

385

completionCandidates = CompletionCandidates.Custom { shell ->

386

when (shell) {

387

ShellType.BASH -> """

388

COMPREPLY=($(compgen -W "$(git diff --name-only --cached 2>/dev/null || echo '')" -- "${"$"}{COMP_WORDS[COMP_CWORD]}"))

389

""".trimIndent()

390

ShellType.FISH -> "git diff --name-only --cached 2>/dev/null"

391

else -> null

392

}

393

}).multiple()

394

395

override fun run() {

396

echo("Committing files: ${files.joinToString(", ")}")

397

}

398

}

399

400

class BranchCommand : CliktCommand(name = "branch") {

401

private val branchName by argument(help = "Branch name",

402

completionCandidates = CompletionCandidates.Custom { shell ->

403

when (shell) {

404

ShellType.BASH -> """

405

COMPREPLY=($(compgen -W "$(git branch --format='%(refname:short)' 2>/dev/null || echo '')" -- "${"$"}{COMP_WORDS[COMP_CWORD]}"))

406

""".trimIndent()

407

ShellType.FISH -> "git branch --format='%(refname:short)' 2>/dev/null"

408

else -> null

409

}

410

}).optional()

411

412

override fun run() {

413

if (branchName != null) {

414

echo("Switching to branch: $branchName")

415

} else {

416

echo("Listing branches...")

417

}

418

}

419

}

420

```

421

422

## Debugging Completion

423

424

```kotlin

425

class MyCommand : CliktCommand() {

426

private val debugCompletion by option("--debug-completion",

427

help = "Debug completion generation").flag(default = false)

428

429

override fun run() {

430

if (debugCompletion) {

431

echo("Completion debugging enabled")

432

// Add debug logging for completion

433

}

434

}

435

}

436

```