Repository: charmbracelet/kancli
Branch: main
Commit: 00dafa62ac8c
Files: 12
Total size: 16.0 KB
Directory structure:
gitextract_bfn0vuyb/
├── .github/
│ └── CODEOWNERS
├── .gitignore
├── README.md
├── column.go
├── data.go
├── form.go
├── go.mod
├── go.sum
├── keys.go
├── main.go
├── model.go
└── task.go
================================================
FILE CONTENTS
================================================
================================================
FILE: .github/CODEOWNERS
================================================
* @bashbunni
================================================
FILE: .gitignore
================================================
kancli
.DS_Store
debug.log
================================================
FILE: README.md
================================================
# Kancli
Welcome to our demo repo for a kanban board for the command line.
There is a video to go along with this repo on our [YouTube
channel](https://youtube.com/c/charmcli) if you would like a full walk through
tutorial on the topic.
## Feedback
We'd love to hear your thoughts on this tutorial. Feel free to drop us a note!
* [Twitter](https://twitter.com/charmcli)
* [The Fediverse](https://mastodon.social/@charmcli)
* [Discord](https://charm.sh/chat)
## License
[MIT](https://github.com/charmbracelet/bubbletea/raw/master/LICENSE)
***
Part of [Charm](https://charm.sh).
<a href="https://charm.sh/"><img alt="The Charm logo" src="https://stuff.charm.sh/charm-badge.jpg" width="400"></a>
Charm热爱开源 • Charm loves open source
================================================
FILE: column.go
================================================
package main
import (
"github.com/charmbracelet/bubbles/key"
"github.com/charmbracelet/bubbles/list"
tea "github.com/charmbracelet/bubbletea"
"github.com/charmbracelet/lipgloss"
)
const APPEND = -1
type column struct {
focus bool
status status
list list.Model
height int
width int
}
func (c *column) Focus() {
c.focus = true
}
func (c *column) Blur() {
c.focus = false
}
func (c *column) Focused() bool {
return c.focus
}
func newColumn(status status) column {
var focus bool
if status == todo {
focus = true
}
defaultList := list.New([]list.Item{}, list.NewDefaultDelegate(), 0, 0)
defaultList.SetShowHelp(false)
return column{focus: focus, status: status, list: defaultList}
}
// Init does initial setup for the column.
func (c column) Init() tea.Cmd {
return nil
}
// Update handles all the I/O for columns.
func (c column) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
var cmd tea.Cmd
switch msg := msg.(type) {
case tea.WindowSizeMsg:
c.setSize(msg.Width, msg.Height)
c.list.SetSize(msg.Width/margin, msg.Height/2)
case tea.KeyMsg:
switch {
case key.Matches(msg, keys.Edit):
if len(c.list.VisibleItems()) != 0 {
task := c.list.SelectedItem().(Task)
f := NewForm(task.title, task.description)
f.index = c.list.Index()
f.col = c
return f.Update(nil)
}
case key.Matches(msg, keys.New):
f := newDefaultForm()
f.index = APPEND
f.col = c
return f.Update(nil)
case key.Matches(msg, keys.Delete):
return c, c.DeleteCurrent()
case key.Matches(msg, keys.Enter):
return c, c.MoveToNext()
}
}
c.list, cmd = c.list.Update(msg)
return c, cmd
}
func (c column) View() string {
return c.getStyle().Render(c.list.View())
}
func (c *column) DeleteCurrent() tea.Cmd {
if len(c.list.VisibleItems()) > 0 {
c.list.RemoveItem(c.list.Index())
}
var cmd tea.Cmd
c.list, cmd = c.list.Update(nil)
return cmd
}
func (c *column) Set(i int, t Task) tea.Cmd {
if i != APPEND {
return c.list.SetItem(i, t)
}
return c.list.InsertItem(APPEND, t)
}
func (c *column) setSize(width, height int) {
c.width = width / margin
}
func (c *column) getStyle() lipgloss.Style {
if c.Focused() {
return lipgloss.NewStyle().
Padding(1, 2).
Border(lipgloss.RoundedBorder()).
BorderForeground(lipgloss.Color("62")).
Height(c.height).
Width(c.width)
}
return lipgloss.NewStyle().
Padding(1, 2).
Border(lipgloss.HiddenBorder()).
Height(c.height).
Width(c.width)
}
type moveMsg struct {
Task
}
func (c *column) MoveToNext() tea.Cmd {
var task Task
var ok bool
// If nothing is selected, the SelectedItem will return Nil.
if task, ok = c.list.SelectedItem().(Task); !ok {
return nil
}
// move item
c.list.RemoveItem(c.list.Index())
task.status = c.status.getNext()
// refresh list
var cmd tea.Cmd
c.list, cmd = c.list.Update(nil)
return tea.Sequence(cmd, func() tea.Msg { return moveMsg{task} })
}
================================================
FILE: data.go
================================================
package main
import "github.com/charmbracelet/bubbles/list"
// Provides the mock data to fill the kanban board
func (b *Board) initLists() {
b.cols = []column{
newColumn(todo),
newColumn(inProgress),
newColumn(done),
}
// Init To Do
b.cols[todo].list.Title = "To Do"
b.cols[todo].list.SetItems([]list.Item{
Task{status: todo, title: "buy milk", description: "strawberry milk"},
Task{status: todo, title: "eat sushi", description: "negitoro roll, miso soup, rice"},
Task{status: todo, title: "fold laundry", description: "or wear wrinkly t-shirts"},
})
// Init in progress
b.cols[inProgress].list.Title = "In Progress"
b.cols[inProgress].list.SetItems([]list.Item{
Task{status: inProgress, title: "write code", description: "don't worry, it's Go"},
})
// Init done
b.cols[done].list.Title = "Done"
b.cols[done].list.SetItems([]list.Item{
Task{status: done, title: "stay cool", description: "as a cucumber"},
})
}
================================================
FILE: form.go
================================================
package main
import (
"github.com/charmbracelet/bubbles/help"
"github.com/charmbracelet/bubbles/key"
"github.com/charmbracelet/bubbles/textarea"
"github.com/charmbracelet/bubbles/textinput"
tea "github.com/charmbracelet/bubbletea"
"github.com/charmbracelet/lipgloss"
)
type Form struct {
help help.Model
title textinput.Model
description textarea.Model
col column
index int
}
func newDefaultForm() *Form {
return NewForm("task name", "")
}
func NewForm(title, description string) *Form {
form := Form{
help: help.New(),
title: textinput.New(),
description: textarea.New(),
}
form.title.Placeholder = title
form.description.Placeholder = description
form.title.Focus()
return &form
}
func (f Form) CreateTask() Task {
return Task{f.col.status, f.title.Value(), f.description.Value()}
}
func (f Form) Init() tea.Cmd {
return nil
}
func (f Form) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
var cmd tea.Cmd
switch msg := msg.(type) {
case column:
f.col = msg
f.col.list.Index()
case tea.KeyMsg:
switch {
case key.Matches(msg, keys.Quit):
return f, tea.Quit
case key.Matches(msg, keys.Back):
return board.Update(nil)
case key.Matches(msg, keys.Enter):
if f.title.Focused() {
f.title.Blur()
f.description.Focus()
return f, textarea.Blink
}
// Return the completed form as a message.
return board.Update(f)
}
}
if f.title.Focused() {
f.title, cmd = f.title.Update(msg)
return f, cmd
}
f.description, cmd = f.description.Update(msg)
return f, cmd
}
func (f Form) View() string {
return lipgloss.JoinVertical(
lipgloss.Left,
"Create a new task",
f.title.View(),
f.description.View(),
f.help.View(keys))
}
================================================
FILE: go.mod
================================================
module github.com/charmbracelet/kancli
go 1.19
require (
github.com/charmbracelet/bubbles v0.16.1
github.com/charmbracelet/bubbletea v0.24.2
github.com/charmbracelet/lipgloss v0.7.1
)
require (
github.com/atotto/clipboard v0.1.4 // indirect
github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect
github.com/containerd/console v1.0.4-0.20230313162750-1ae8d489ac81 // indirect
github.com/lucasb-eyer/go-colorful v1.2.0 // indirect
github.com/mattn/go-isatty v0.0.19 // indirect
github.com/mattn/go-localereader v0.0.1 // indirect
github.com/mattn/go-runewidth v0.0.14 // indirect
github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 // indirect
github.com/muesli/cancelreader v0.2.2 // indirect
github.com/muesli/reflow v0.3.0 // indirect
github.com/muesli/termenv v0.15.1 // indirect
github.com/rivo/uniseg v0.4.4 // indirect
github.com/sahilm/fuzzy v0.1.0 // indirect
golang.org/x/sync v0.3.0 // indirect
golang.org/x/sys v0.9.0 // indirect
golang.org/x/term v0.9.0 // indirect
golang.org/x/text v0.10.0 // indirect
)
================================================
FILE: go.sum
================================================
github.com/atotto/clipboard v0.1.4 h1:EH0zSVneZPSuFR11BlR9YppQTVDbh5+16AmcJi4g1z4=
github.com/atotto/clipboard v0.1.4/go.mod h1:ZY9tmq7sm5xIbd9bOK4onWV4S6X0u6GY7Vn0Yu86PYI=
github.com/aymanbagabas/go-osc52/v2 v2.0.1 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiErDT4WkJ2k=
github.com/aymanbagabas/go-osc52/v2 v2.0.1/go.mod h1:uYgXzlJ7ZpABp8OJ+exZzJJhRNQ2ASbcXHWsFqH8hp8=
github.com/charmbracelet/bubbles v0.16.1 h1:6uzpAAaT9ZqKssntbvZMlksWHruQLNxg49H5WdeuYSY=
github.com/charmbracelet/bubbles v0.16.1/go.mod h1:2QCp9LFlEsBQMvIYERr7Ww2H2bA7xen1idUDIzm/+Xc=
github.com/charmbracelet/bubbletea v0.24.2 h1:uaQIKx9Ai6Gdh5zpTbGiWpytMU+CfsPp06RaW2cx/SY=
github.com/charmbracelet/bubbletea v0.24.2/go.mod h1:XdrNrV4J8GiyshTtx3DNuYkR1FDaJmO3l2nejekbsgg=
github.com/charmbracelet/lipgloss v0.7.1 h1:17WMwi7N1b1rVWOjMT+rCh7sQkvDU75B2hbZpc5Kc1E=
github.com/charmbracelet/lipgloss v0.7.1/go.mod h1:yG0k3giv8Qj8edTCbbg6AlQ5e8KNWpFujkNawKNhE2c=
github.com/containerd/console v1.0.4-0.20230313162750-1ae8d489ac81 h1:q2hJAaP1k2wIvVRd/hEHD7lacgqrCPS+k8g1MndzfWY=
github.com/containerd/console v1.0.4-0.20230313162750-1ae8d489ac81/go.mod h1:YynlIjWYF8myEu6sdkwKIvGQq+cOckRm6So2avqoYAk=
github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc=
github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY=
github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=
github.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA=
github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/mattn/go-localereader v0.0.1 h1:ygSAOl7ZXTx4RdPYinUpg6W99U8jWvWi9Ye2JC/oIi4=
github.com/mattn/go-localereader v0.0.1/go.mod h1:8fBrzywKY7BI3czFoHkuzRoWE9C+EiG4R1k4Cjx5p88=
github.com/mattn/go-runewidth v0.0.12/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk=
github.com/mattn/go-runewidth v0.0.14 h1:+xnbZSEeDbOIg5/mE6JF0w6n9duR1l3/WmbinWVwUuU=
github.com/mattn/go-runewidth v0.0.14/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 h1:ZK8zHtRHOkbHy6Mmr5D264iyp3TiX5OmNcI5cIARiQI=
github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6/go.mod h1:CJlz5H+gyd6CUWT45Oy4q24RdLyn7Md9Vj2/ldJBSIo=
github.com/muesli/cancelreader v0.2.2 h1:3I4Kt4BQjOR54NavqnDogx/MIoWBFa0StPA8ELUXHmA=
github.com/muesli/cancelreader v0.2.2/go.mod h1:3XuTXfFS2VjM+HTLZY9Ak0l6eUKfijIfMUZ4EgX0QYo=
github.com/muesli/reflow v0.3.0 h1:IFsN6K9NfGtjeggFP+68I4chLZV2yIKsXJFNZ+eWh6s=
github.com/muesli/reflow v0.3.0/go.mod h1:pbwTDkVPibjO2kyvBQRBxTWEEGDGq0FlB1BIKtnHY/8=
github.com/muesli/termenv v0.15.1 h1:UzuTb/+hhlBugQz28rpzey4ZuKcZ03MeKsoG7IJZIxs=
github.com/muesli/termenv v0.15.1/go.mod h1:HeAQPTzpfs016yGtA4g00CsdYnVLJvxsS4ANqrZs2sQ=
github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
github.com/rivo/uniseg v0.4.4 h1:8TfxU8dW6PdqD27gjM8MVNuicgxIjxpm4K7x4jp8sis=
github.com/rivo/uniseg v0.4.4/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
github.com/sahilm/fuzzy v0.1.0 h1:FzWGaw2Opqyu+794ZQ9SYifWv2EIXpwP4q8dY1kDAwI=
github.com/sahilm/fuzzy v0.1.0/go.mod h1:VFvziUEIMCrT6A6tw2RFIXPXXmzXbOsSHF0DOI8ZK9Y=
golang.org/x/sync v0.3.0 h1:ftCYgMx6zT/asHUrPw8BLLscYtGznsLAnjq5RH9P66E=
golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y=
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.9.0 h1:KS/R3tvhPqvJvwcKfnBHJwwthS11LRhmM5D59eEXa0s=
golang.org/x/sys v0.9.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.9.0 h1:GRRCnKYhdQrD8kfRAdQ6Zcw1P0OcELxGLKJvtjVMZ28=
golang.org/x/term v0.9.0/go.mod h1:M6DEAAIenWoTxdKrOltXcmDY3rSplQUkrvaDU5FcQyo=
golang.org/x/text v0.10.0 h1:UpjohKhiEgNc0CSauXmwYftY1+LlaC75SJwh0SgCX58=
golang.org/x/text v0.10.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
================================================
FILE: keys.go
================================================
package main
import "github.com/charmbracelet/bubbles/key"
// ShortHelp returns keybindings to be shown in the mini help view. It's part
// of the key.Map interface.
func (k keyMap) ShortHelp() []key.Binding {
return []key.Binding{k.Help, k.Quit}
}
// FullHelp returns keybindings for the expanded help view. It's part of the
// key.Map interface.
func (k keyMap) FullHelp() [][]key.Binding {
return [][]key.Binding{
{k.Up, k.Down, k.Left, k.Right}, // first column
{k.Help, k.Quit}, // second column
}
}
type keyMap struct {
New key.Binding
Edit key.Binding
Delete key.Binding
Up key.Binding
Down key.Binding
Right key.Binding
Left key.Binding
Enter key.Binding
Help key.Binding
Quit key.Binding
Back key.Binding
}
var keys = keyMap{
New: key.NewBinding(
key.WithKeys("n"),
key.WithHelp("n", "new"),
),
Edit: key.NewBinding(
key.WithKeys("e"),
key.WithHelp("e", "edit"),
),
Delete: key.NewBinding(
key.WithKeys("d"),
key.WithHelp("d", "delete"),
),
Up: key.NewBinding(
key.WithKeys("up", "k"),
key.WithHelp("↑/k", "move up"),
),
Down: key.NewBinding(
key.WithKeys("down", "j"),
key.WithHelp("↓/j", "move down"),
),
Right: key.NewBinding(
key.WithKeys("right", "l"),
key.WithHelp("→/l", "move right"),
),
Left: key.NewBinding(
key.WithKeys("left", "h"),
key.WithHelp("←/h", "move left"),
),
Enter: key.NewBinding(
key.WithKeys("enter"),
key.WithHelp("enter", "enter"),
),
Help: key.NewBinding(
key.WithKeys("?"),
key.WithHelp("?", "toggle help"),
),
Quit: key.NewBinding(
key.WithKeys("q", "ctrl+c"),
key.WithHelp("q/ctrl+c", "quit"),
),
Back: key.NewBinding(
key.WithKeys("esc"),
key.WithHelp("esc", "back"),
),
}
================================================
FILE: main.go
================================================
package main
import (
"fmt"
"os"
tea "github.com/charmbracelet/bubbletea"
)
type status int
func (s status) getNext() status {
if s == done {
return todo
}
return s + 1
}
func (s status) getPrev() status {
if s == todo {
return done
}
return s - 1
}
const margin = 4
var board *Board
const (
todo status = iota
inProgress
done
)
func main() {
f, err := tea.LogToFile("debug.log", "debug")
if err != nil {
fmt.Println(err)
os.Exit(1)
}
defer f.Close()
board = NewBoard()
board.initLists()
p := tea.NewProgram(board)
if _, err := p.Run(); err != nil {
fmt.Println(err)
os.Exit(1)
}
}
================================================
FILE: model.go
================================================
package main
import (
"github.com/charmbracelet/bubbles/help"
"github.com/charmbracelet/bubbles/key"
tea "github.com/charmbracelet/bubbletea"
"github.com/charmbracelet/lipgloss"
)
type Board struct {
help help.Model
loaded bool
focused status
cols []column
quitting bool
}
func NewBoard() *Board {
help := help.New()
help.ShowAll = true
return &Board{help: help, focused: todo}
}
func (m *Board) Init() tea.Cmd {
return nil
}
func (m *Board) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
switch msg := msg.(type) {
case tea.WindowSizeMsg:
var cmd tea.Cmd
var cmds []tea.Cmd
m.help.Width = msg.Width - margin
for i := 0; i < len(m.cols); i++ {
var res tea.Model
res, cmd = m.cols[i].Update(msg)
m.cols[i] = res.(column)
cmds = append(cmds, cmd)
}
m.loaded = true
return m, tea.Batch(cmds...)
case Form:
return m, m.cols[m.focused].Set(msg.index, msg.CreateTask())
case moveMsg:
return m, m.cols[m.focused.getNext()].Set(APPEND, msg.Task)
case tea.KeyMsg:
switch {
case key.Matches(msg, keys.Quit):
m.quitting = true
return m, tea.Quit
case key.Matches(msg, keys.Left):
m.cols[m.focused].Blur()
m.focused = m.focused.getPrev()
m.cols[m.focused].Focus()
case key.Matches(msg, keys.Right):
m.cols[m.focused].Blur()
m.focused = m.focused.getNext()
m.cols[m.focused].Focus()
}
}
res, cmd := m.cols[m.focused].Update(msg)
if _, ok := res.(column); ok {
m.cols[m.focused] = res.(column)
} else {
return res, cmd
}
return m, cmd
}
// Changing to pointer receiver to get back to this model after adding a new task via the form... Otherwise I would need to pass this model along to the form and it becomes highly coupled to the other models.
func (m *Board) View() string {
if m.quitting {
return ""
}
if !m.loaded {
return "loading..."
}
board := lipgloss.JoinHorizontal(
lipgloss.Left,
m.cols[todo].View(),
m.cols[inProgress].View(),
m.cols[done].View(),
)
return lipgloss.JoinVertical(lipgloss.Left, board, m.help.View(keys))
}
================================================
FILE: task.go
================================================
package main
type Task struct {
status status
title string
description string
}
func NewTask(status status, title, description string) Task {
return Task{status: status, title: title, description: description}
}
func (t *Task) Next() {
if t.status == done {
t.status = todo
} else {
t.status++
}
}
// implement the list.Item interface
func (t Task) FilterValue() string {
return t.title
}
func (t Task) Title() string {
return t.title
}
func (t Task) Description() string {
return t.description
}
gitextract_bfn0vuyb/ ├── .github/ │ └── CODEOWNERS ├── .gitignore ├── README.md ├── column.go ├── data.go ├── form.go ├── go.mod ├── go.sum ├── keys.go ├── main.go ├── model.go └── task.go
SYMBOL INDEX (45 symbols across 7 files)
FILE: column.go
constant APPEND (line 10) | APPEND = -1
type column (line 12) | type column struct
method Focus (line 20) | func (c *column) Focus() {
method Blur (line 24) | func (c *column) Blur() {
method Focused (line 28) | func (c *column) Focused() bool {
method Init (line 43) | func (c column) Init() tea.Cmd {
method Update (line 48) | func (c column) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
method View (line 79) | func (c column) View() string {
method DeleteCurrent (line 83) | func (c *column) DeleteCurrent() tea.Cmd {
method Set (line 93) | func (c *column) Set(i int, t Task) tea.Cmd {
method setSize (line 100) | func (c *column) setSize(width, height int) {
method getStyle (line 104) | func (c *column) getStyle() lipgloss.Style {
method MoveToNext (line 124) | func (c *column) MoveToNext() tea.Cmd {
function newColumn (line 32) | func newColumn(status status) column {
type moveMsg (line 120) | type moveMsg struct
FILE: data.go
method initLists (line 7) | func (b *Board) initLists() {
FILE: form.go
type Form (line 12) | type Form struct
method CreateTask (line 36) | func (f Form) CreateTask() Task {
method Init (line 40) | func (f Form) Init() tea.Cmd {
method Update (line 44) | func (f Form) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
method View (line 75) | func (f Form) View() string {
function newDefaultForm (line 20) | func newDefaultForm() *Form {
function NewForm (line 24) | func NewForm(title, description string) *Form {
FILE: keys.go
type keyMap (line 20) | type keyMap struct
method ShortHelp (line 7) | func (k keyMap) ShortHelp() []key.Binding {
method FullHelp (line 13) | func (k keyMap) FullHelp() [][]key.Binding {
FILE: main.go
type status (line 10) | type status
method getNext (line 12) | func (s status) getNext() status {
method getPrev (line 19) | func (s status) getPrev() status {
constant margin (line 26) | margin = 4
constant todo (line 31) | todo status = iota
constant inProgress (line 32) | inProgress
constant done (line 33) | done
function main (line 36) | func main() {
FILE: model.go
type Board (line 10) | type Board struct
method Init (line 24) | func (m *Board) Init() tea.Cmd {
method Update (line 28) | func (m *Board) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
method View (line 71) | func (m *Board) View() string {
function NewBoard (line 18) | func NewBoard() *Board {
FILE: task.go
type Task (line 3) | type Task struct
method Next (line 13) | func (t *Task) Next() {
method FilterValue (line 22) | func (t Task) FilterValue() string {
method Title (line 26) | func (t Task) Title() string {
method Description (line 30) | func (t Task) Description() string {
function NewTask (line 9) | func NewTask(status status, title, description string) Task {
Condensed preview — 12 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (18K chars).
[
{
"path": ".github/CODEOWNERS",
"chars": 14,
"preview": "* @bashbunni\n"
},
{
"path": ".gitignore",
"chars": 27,
"preview": "kancli\n.DS_Store\ndebug.log\n"
},
{
"path": "README.md",
"chars": 740,
"preview": "# Kancli\n\nWelcome to our demo repo for a kanban board for the command line.\n\nThere is a video to go along with this repo"
},
{
"path": "column.go",
"chars": 2914,
"preview": "package main\n\nimport (\n\t\"github.com/charmbracelet/bubbles/key\"\n\t\"github.com/charmbracelet/bubbles/list\"\n\ttea \"github.com"
},
{
"path": "data.go",
"chars": 945,
"preview": "package main\n\nimport \"github.com/charmbracelet/bubbles/list\"\n\n// Provides the mock data to fill the kanban board\n\nfunc ("
},
{
"path": "form.go",
"chars": 1738,
"preview": "package main\n\nimport (\n\t\"github.com/charmbracelet/bubbles/help\"\n\t\"github.com/charmbracelet/bubbles/key\"\n\t\"github.com/cha"
},
{
"path": "go.mod",
"chars": 1046,
"preview": "module github.com/charmbracelet/kancli\n\ngo 1.19\n\nrequire (\n\tgithub.com/charmbracelet/bubbles v0.16.1\n\tgithub.com/charmbr"
},
{
"path": "go.sum",
"chars": 4062,
"preview": "github.com/atotto/clipboard v0.1.4 h1:EH0zSVneZPSuFR11BlR9YppQTVDbh5+16AmcJi4g1z4=\ngithub.com/atotto/clipboard v0.1.4/go"
},
{
"path": "keys.go",
"chars": 1740,
"preview": "package main\n\nimport \"github.com/charmbracelet/bubbles/key\"\n\n// ShortHelp returns keybindings to be shown in the mini he"
},
{
"path": "main.go",
"chars": 625,
"preview": "package main\n\nimport (\n\t\"fmt\"\n\t\"os\"\n\n\ttea \"github.com/charmbracelet/bubbletea\"\n)\n\ntype status int\n\nfunc (s status) getNe"
},
{
"path": "model.go",
"chars": 2045,
"preview": "package main\n\nimport (\n\t\"github.com/charmbracelet/bubbles/help\"\n\t\"github.com/charmbracelet/bubbles/key\"\n\ttea \"github.com"
},
{
"path": "task.go",
"chars": 528,
"preview": "package main\n\ntype Task struct {\n\tstatus status\n\ttitle string\n\tdescription string\n}\n\nfunc NewTask(status stat"
}
]
About this extraction
This page contains the full source code of the charmbracelet/kancli GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 12 files (16.0 KB), approximately 6.2k tokens, and a symbol index with 45 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.