or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

commands.mdindex.mdinput.mdprogram.mdscreen.md

commands.mddocs/

0

# Command System

1

2

Asynchronous operations and side effects through commands, including built-in commands for timers, batching, external process execution, and custom async operations.

3

4

## Capabilities

5

6

### Command Fundamentals

7

8

Commands represent I/O operations that run asynchronously and return messages.

9

10

```go { .api }

11

/**

12

* Cmd represents an I/O operation that returns a message

13

* Commands are returned from Init() and Update() to perform side effects

14

* Return nil for no-op commands

15

*/

16

type Cmd func() Msg

17

18

// Core message interface that all messages implement

19

type Msg interface{}

20

```

21

22

Commands are the primary way to perform side effects in Bubble Tea:

23

24

```go

25

func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {

26

switch msg := msg.(type) {

27

case tea.KeyMsg:

28

if msg.String() == "r" {

29

// Return a command to refresh data

30

return m, m.fetchDataCmd()

31

}

32

case dataFetchedMsg:

33

m.data = msg.data

34

return m, nil

35

}

36

return m, nil

37

}

38

39

func (m model) fetchDataCmd() tea.Cmd {

40

return func() tea.Msg {

41

data := fetchDataFromAPI() // This runs in a goroutine

42

return dataFetchedMsg{data: data}

43

}

44

}

45

```

46

47

### Command Batching

48

49

Execute multiple commands concurrently with no ordering guarantees.

50

51

```go { .api }

52

/**

53

* Batch executes commands concurrently with no ordering guarantees

54

* Use for independent operations that can run simultaneously

55

* @param cmds - Variable number of commands to execute

56

* @returns Single command that runs all provided commands

57

*/

58

func Batch(cmds ...Cmd) Cmd

59

60

/**

61

* BatchMsg is sent when batch execution completes

62

* You typically don't need to handle this directly

63

*/

64

type BatchMsg []Cmd

65

```

66

67

**Usage Example:**

68

69

```go

70

func (m model) Init() tea.Cmd {

71

return tea.Batch(

72

m.loadUserCmd(),

73

m.loadSettingsCmd(),

74

m.checkUpdatesCmd(),

75

)

76

}

77

78

func (m model) loadUserCmd() tea.Cmd {

79

return func() tea.Msg {

80

user := loadUser()

81

return userLoadedMsg{user}

82

}

83

}

84

85

func (m model) loadSettingsCmd() tea.Cmd {

86

return func() tea.Msg {

87

settings := loadSettings()

88

return settingsLoadedMsg{settings}

89

}

90

}

91

```

92

93

### Command Sequencing

94

95

Execute commands one at a time in specified order.

96

97

```go { .api }

98

/**

99

* Sequence executes commands one at a time in order

100

* Each command waits for the previous to complete

101

* @param cmds - Commands to execute in sequence

102

* @returns Single command that runs commands sequentially

103

*/

104

func Sequence(cmds ...Cmd) Cmd

105

```

106

107

**Usage Example:**

108

109

```go

110

func (m model) saveAndExit() tea.Cmd {

111

return tea.Sequence(

112

m.saveFileCmd(),

113

m.showSavedMessageCmd(),

114

tea.Quit(),

115

)

116

}

117

118

func (m model) saveFileCmd() tea.Cmd {

119

return func() tea.Msg {

120

err := saveFile(m.filename, m.content)

121

return fileSavedMsg{err: err}

122

}

123

}

124

125

func (m model) showSavedMessageCmd() tea.Cmd {

126

return func() tea.Msg {

127

return showMessageMsg{text: "File saved!"}

128

}

129

}

130

```

131

132

### Timer Commands

133

134

Create time-based operations for animations, polling, and scheduled events.

135

136

```go { .api }

137

/**

138

* Tick creates a timer that fires once after the specified duration

139

* Timer starts when the command is executed (not when created)

140

* @param d - Duration to wait before sending message

141

* @param fn - Function that returns message when timer fires

142

* @returns Command that sends timed message

143

*/

144

func Tick(d time.Duration, fn func(time.Time) Msg) Cmd

145

146

/**

147

* Every creates a timer that syncs with the system clock

148

* Useful for synchronized ticking (e.g., every minute on the minute)

149

* @param duration - Duration for timer alignment

150

* @param fn - Function that returns message when timer fires

151

* @returns Command that sends system-clock-aligned message

152

*/

153

func Every(duration time.Duration, fn func(time.Time) Msg) Cmd

154

```

155

156

**Usage Examples:**

157

158

```go

159

// Simple tick timer

160

type tickMsg time.Time

161

162

func (m model) Init() tea.Cmd {

163

return tea.Tick(time.Second, func(t time.Time) tea.Msg {

164

return tickMsg(t)

165

})

166

}

167

168

func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {

169

switch msg.(type) {

170

case tickMsg:

171

// Update every second

172

m.counter++

173

return m, tea.Tick(time.Second, func(t time.Time) tea.Msg {

174

return tickMsg(t)

175

})

176

}

177

return m, nil

178

}

179

180

// System clock alignment

181

func (m model) startClockSync() tea.Cmd {

182

return tea.Every(time.Minute, func(t time.Time) tea.Msg {

183

return clockUpdateMsg{time: t}

184

})

185

}

186

```

187

188

### Window and Terminal Commands

189

190

Commands for controlling terminal and window properties.

191

192

```go { .api }

193

/**

194

* SetWindowTitle sets the terminal window title

195

* @param title - New title for the terminal window

196

* @returns Command to set window title

197

*/

198

func SetWindowTitle(title string) Cmd

199

200

/**

201

* WindowSize queries the current terminal size

202

* Delivers results via WindowSizeMsg

203

* Note: Size messages are automatically sent on start and resize

204

* @returns Command to query terminal dimensions

205

*/

206

func WindowSize() Cmd

207

```

208

209

**Usage Example:**

210

211

```go

212

func (m model) Init() tea.Cmd {

213

return tea.Batch(

214

tea.SetWindowTitle("My App - " + m.filename),

215

tea.WindowSize(),

216

)

217

}

218

219

func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {

220

switch msg := msg.(type) {

221

case tea.WindowSizeMsg:

222

m.width = msg.Width

223

m.height = msg.Height

224

return m, nil

225

}

226

return m, nil

227

}

228

```

229

230

### External Process Execution

231

232

Execute external processes and programs while suspending the TUI.

233

234

```go { .api }

235

/**

236

* ExecCallback is called when command execution completes

237

* @param error - Error from command execution (nil if successful)

238

* @returns Message to send to update function

239

*/

240

type ExecCallback func(error) Msg

241

242

/**

243

* ExecCommand interface for executable commands

244

* Implemented by exec.Cmd and custom command types

245

*/

246

type ExecCommand interface {

247

Run() error

248

SetStdin(io.Reader)

249

SetStdout(io.Writer)

250

SetStderr(io.Writer)

251

}

252

253

/**

254

* Exec executes arbitrary I/O in blocking fashion

255

* Program is suspended while command runs

256

* @param c - Command implementing ExecCommand interface

257

* @param fn - Callback function for completion notification

258

* @returns Command that blocks and executes external process

259

*/

260

func Exec(c ExecCommand, fn ExecCallback) Cmd

261

262

/**

263

* ExecProcess executes an *exec.Cmd in blocking fashion

264

* Convenience wrapper around Exec for standard commands

265

* @param c - Standard library exec.Cmd

266

* @param fn - Callback for completion (can be nil)

267

* @returns Command that executes external process

268

*/

269

func ExecProcess(c *exec.Cmd, fn ExecCallback) Cmd

270

```

271

272

**Usage Examples:**

273

274

```go

275

import "os/exec"

276

277

type editorFinishedMsg struct {

278

err error

279

}

280

281

func (m model) openEditor() tea.Cmd {

282

cmd := exec.Command("vim", m.filename)

283

return tea.ExecProcess(cmd, func(err error) tea.Msg {

284

return editorFinishedMsg{err: err}

285

})

286

}

287

288

func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {

289

switch msg := msg.(type) {

290

case tea.KeyMsg:

291

if msg.String() == "e" {

292

return m, m.openEditor()

293

}

294

case editorFinishedMsg:

295

if msg.err != nil {

296

m.status = "Editor failed: " + msg.err.Error()

297

} else {

298

m.status = "File edited successfully"

299

return m, m.reloadFileCmd()

300

}

301

return m, nil

302

}

303

return m, nil

304

}

305

306

// Simple execution without callback

307

func (m model) runGitStatus() tea.Cmd {

308

cmd := exec.Command("git", "status")

309

return tea.ExecProcess(cmd, nil)

310

}

311

```

312

313

## Custom Command Patterns

314

315

### HTTP Requests

316

317

Common pattern for making HTTP requests:

318

319

```go

320

type httpResponseMsg struct {

321

status int

322

body []byte

323

err error

324

}

325

326

func fetchURL(url string) tea.Cmd {

327

return func() tea.Msg {

328

resp, err := http.Get(url)

329

if err != nil {

330

return httpResponseMsg{err: err}

331

}

332

defer resp.Body.Close()

333

334

body, err := io.ReadAll(resp.Body)

335

return httpResponseMsg{

336

status: resp.StatusCode,

337

body: body,

338

err: err,

339

}

340

}

341

}

342

343

func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {

344

switch msg := msg.(type) {

345

case httpResponseMsg:

346

if msg.err != nil {

347

m.error = msg.err.Error()

348

return m, nil

349

}

350

m.data = string(msg.body)

351

return m, nil

352

}

353

return m, nil

354

}

355

```

356

357

### File I/O Operations

358

359

Pattern for file operations:

360

361

```go

362

type fileReadMsg struct {

363

filename string

364

content []byte

365

err error

366

}

367

368

type fileWriteMsg struct {

369

filename string

370

err error

371

}

372

373

func readFileCmd(filename string) tea.Cmd {

374

return func() tea.Msg {

375

content, err := os.ReadFile(filename)

376

return fileReadMsg{

377

filename: filename,

378

content: content,

379

err: err,

380

}

381

}

382

}

383

384

func writeFileCmd(filename string, content []byte) tea.Cmd {

385

return func() tea.Msg {

386

err := os.WriteFile(filename, content, 0644)

387

return fileWriteMsg{

388

filename: filename,

389

err: err,

390

}

391

}

392

}

393

```

394

395

### Periodic Tasks

396

397

Pattern for recurring operations:

398

399

```go

400

type pollMsg time.Time

401

402

func startPolling(interval time.Duration) tea.Cmd {

403

return tea.Tick(interval, func(t time.Time) tea.Msg {

404

return pollMsg(t)

405

})

406

}

407

408

func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {

409

switch msg.(type) {

410

case pollMsg:

411

// Do periodic work

412

cmd := m.checkStatusCmd()

413

414

// Schedule next poll

415

nextPoll := startPolling(30 * time.Second)

416

417

return m, tea.Batch(cmd, nextPoll)

418

}

419

return m, nil

420

}

421

```

422

423

### Conditional Commands

424

425

Pattern for conditional command execution:

426

427

```go

428

func conditionalCmd(condition bool, cmd tea.Cmd) tea.Cmd {

429

if condition {

430

return cmd

431

}

432

return nil

433

}

434

435

func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {

436

switch msg := msg.(type) {

437

case tea.KeyMsg:

438

if msg.String() == "s" {

439

return m, conditionalCmd(

440

m.hasUnsavedChanges,

441

m.saveFileCmd(),

442

)

443

}

444

}

445

return m, nil

446

}

447

```

448

449

### Long-Running Tasks with Progress

450

451

Pattern for tasks that provide progress updates:

452

453

```go

454

type progressMsg struct {

455

percent int

456

status string

457

}

458

459

type completedMsg struct {

460

result interface{}

461

err error

462

}

463

464

func longRunningTask() tea.Cmd {

465

return func() tea.Msg {

466

// This would typically be in a separate goroutine

467

// sending progress updates via a channel

468

469

for i := 0; i <= 100; i += 10 {

470

// In real implementation, send progress via program.Send()

471

time.Sleep(100 * time.Millisecond)

472

}

473

474

return completedMsg{result: "Task complete!"}

475

}

476

}

477

```

478

479

## Deprecated Functions

480

481

```go { .api }

482

/**

483

* Sequentially executes commands but returns first non-nil message

484

* Deprecated: Use Sequence instead for better behavior

485

*/

486

func Sequentially(cmds ...Cmd) Cmd

487

```