├── README.md ├── completion.zsh └── gh-todo /README.md: -------------------------------------------------------------------------------- 1 | # gh todo 2 | 3 | [GitHub CLI] extension for todo list manager, via GitHub issues. 4 | 5 | > Keep three and only three lists: a Todo List, a Watch List, and a Later List. 6 | > 7 | > --- [Marc Andreessen’s guide to personal productivity. 8 | > ](https://pmarchive.com/guide_to_personal_productivity.html) 9 | 10 | ## Install 11 | 12 | ```bash 13 | gh extension install yuler/gh-todo 14 | ``` 15 | 16 | ## Features 17 | 18 | - Simple 19 | - Easy to use via command line 20 | - Integration GitHub issues 21 | 22 | ## How it works 23 | 24 | First, you need run `gh todo init`. It will create `todo` repo in your GitHub account. 25 | 26 | **Note:** 27 | 28 | - You can override the default repo name `todo` with the environment variable `GH_TODO_REPO`. Like this: 29 | 30 | ```bash 31 | GH_TODO_REPO=repo-todo gh todo init 32 | ``` 33 | 34 | - And you can use `gh todo init --template yuler/template-todo` based on a template repository. 35 | 36 | Then you can use `gh todo add` to add new task. It will create an issue with today(yyyy-MM-dd) as the title in `todo` repo. 37 | 38 | You can specify the issue title with `--scope`. The default is today(yyyy-MM-dd). Current support [yesterday, tomorrow, week, month, year]. Any other will be used directly as issue title. 39 | 40 | The `gh todo` or `gh todo list` where show todo list. 41 | 42 | ## Usage 43 | 44 | ```bash 45 | # Show help for command 46 | gh todo --help 47 | # Create `todo` repo 48 | gh todo init --template=yuler/template-todo 49 | # Open `issues` in browser 50 | gh todo home 51 | # Add todo item 52 | gh todo add [item] 53 | # Open `issue` in browser 54 | gh todo view 55 | # Close `issue` 56 | gh todo done 57 | # Show todo list 58 | gh todo list 59 | ``` 60 | 61 | ## ZSH Completion 62 | 63 | - Define `gh-todo` function in `.zshrc` 64 | 65 | ```zsh 66 | gh-todo() { 67 | gh todo $@ 68 | } 69 | ``` 70 | 71 | - Ensure autoload `compinit` 72 | - Add completion to `zsh/site-functions` 73 | 74 | ```bash 75 | curl https://raw.githubusercontent.com/yuler/gh-todo/main/completion.zsh > /usr/local/share/zsh/site-functions/\_gh-todo 76 | ``` 77 | 78 | ## Related 79 | 80 | - [yuler/template-todo] 81 | - [todo.txt-cli] 82 | - [taskbook] 83 | 84 | 85 | 86 | [github cli]: https://github.com/cli/cli 87 | [yuler/template-todo]: https://github.com/yuler/template-todo 88 | [todo.txt-cli]: https://github.com/todotxt/todo.txt-cli 89 | [taskbook]: https://github.com/klaussinani/taskbook 90 | -------------------------------------------------------------------------------- /completion.zsh: -------------------------------------------------------------------------------- 1 | #compdef gh-todo 2 | 3 | _gh-todo() { 4 | local line state 5 | 6 | _arguments -C \ 7 | "--help[Show help for command]" \ 8 | "--version[Show version]" \ 9 | "1: :->cmds" \ 10 | "*::arg:->args" 11 | 12 | case "$state" in 13 | cmds) 14 | _values "Actions:" \ 15 | "init[Create \`todo\` repo]" \ 16 | "home[Open \`issues\` in browser]" \ 17 | "add[Add todo item]" \ 18 | "edit[Open \`issue\` in browser]" \ 19 | "done[Close \`issue\`]" \ 20 | "list[Show todo list]" 21 | ;; 22 | args) 23 | case $line[1] in 24 | init) 25 | _arguments '(--template)--template[template repo]' 26 | ;; 27 | add | view | edit | done | list) 28 | _arguments '(--scope)--scope[todo scope]' 29 | ;; 30 | esac 31 | ;; 32 | esac 33 | } 34 | -------------------------------------------------------------------------------- /gh-todo: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | set -eo pipefail 3 | 4 | usage() { 5 | cat <<-EOF 6 | Usage: gh todo 7 | 8 | [GitHub CLI] extension for todo list manager via GitHub issues 9 | 10 | Actions: 11 | init Create \`todo\` repo 12 | home Open \`issues\` in browser 13 | add Add todo item 14 | view/edit Open \`issue\` in browser 15 | done Close \`issue\` 16 | list Show todo list 17 | 18 | Options: 19 | --version Show version 20 | --help Show help for command 21 | --scope TODO scope, default: today(yyyy-MM-dd). 22 | Support [yesterday, tomorrow, week, month, year]. 23 | Any other will be used directly as issue title. 24 | Examples: 25 | gh todo add abc 26 | EOF 27 | } 28 | 29 | version="0.4.3" 30 | 31 | repo="$(gh config get -h github.com user)/todo" 32 | 33 | if [ -n "$GH_TODO_REPO" ]; then 34 | repo="$GH_TODO_REPO" 35 | fi 36 | 37 | # parse `--scope` option 38 | POSITIONAL=() 39 | while [[ $# -gt 0 ]]; do 40 | key="$1" 41 | 42 | case $key in 43 | --scope) 44 | scope="$2" 45 | shift # past argument 46 | shift # past value 47 | ;; 48 | *) # unknown option 49 | POSITIONAL+=("$1") # save it in an array for later 50 | shift # past argument 51 | ;; 52 | esac 53 | done 54 | set -- "${POSITIONAL[@]}" # restore positional parameters 55 | 56 | get_issue_id() { 57 | gh issue list --search "$issue_title" --json "number" --jq ".[0].number" --repo $repo 58 | } 59 | 60 | init() { 61 | exec gh repo create --confirm --private $@ $repo 62 | } 63 | 64 | home() { 65 | gh issue list --web --repo $repo 66 | } 67 | 68 | add() { 69 | input="$1" 70 | issue_id=$(get_issue_id) 71 | if [[ -z $issue_id ]]; then 72 | exec gh issue create --title "$issue_title" --body "- [ ] $input" --repo $repo 73 | else 74 | body=$(gh issue view $issue_id --json "body" --jq ".body" --repo $repo) 75 | body=$(echo -e "$body\n- [ ] $input") 76 | exec gh issue edit $issue_id --body "$body" --repo $repo 77 | fi 78 | } 79 | 80 | edit() { 81 | issue_id=$(get_issue_id) 82 | if [[ -z $issue_id ]]; then 83 | echo "No plans for $issue_title yet." 84 | fi 85 | exec gh issue view $issue_id --repo $repo --web 86 | } 87 | 88 | _done() { 89 | issue_id=$(get_issue_id) 90 | if [[ -z $issue_id ]]; then 91 | echo "No plans for $issue_title yet." 92 | else 93 | exec gh issue close $issue_id --repo $repo 94 | fi 95 | } 96 | 97 | list() { 98 | issue_id=$(get_issue_id) 99 | if [[ -z $issue_id ]]; then 100 | echo "No plans for $issue_title yet." 101 | else 102 | exec gh issue view $issue_id --repo $repo 103 | fi 104 | } 105 | 106 | # main 107 | if [[ $# -eq 0 ]]; then 108 | list 109 | exit 0 110 | fi 111 | 112 | issue_title=$(date +"%Y-%m-%d") 113 | # get issue id by `--scope` 114 | if [ -n "$scope" ]; then 115 | case "$scope" in 116 | yesterday) 117 | issue_title=$(date -v-1d +"%Y-%m-%d") 118 | ;; 119 | tomorrow) 120 | issue_title=$(date -v+1d +"%Y-%m-%d") 121 | ;; 122 | week) 123 | issue_title="Week: $(date -v -Mon +"%Y-%m-%d") ~ $(date -v -Mon -v+6d +"%Y-%m-%d")" 124 | ;; 125 | month) 126 | issue_title="Month: $(date +"%Y-%m")" 127 | ;; 128 | year) 129 | issue_title="Year: $(date +"%Y")" 130 | ;; 131 | *) 132 | issue_title=$scope 133 | ;; 134 | esac 135 | fi 136 | 137 | while [ $# -gt 0 ]; do 138 | case "$1" in 139 | --version) 140 | echo $version 141 | exit 0 142 | ;; 143 | --help) 144 | usage 145 | exit 0 146 | ;; 147 | init) 148 | shift 149 | init $@ 150 | ;; 151 | home) 152 | shift 153 | home 154 | ;; 155 | add) 156 | if [[ -z "$2" ]]; then 157 | echo -n "[Enter]: " 158 | read -e -r input 159 | else 160 | shift 161 | input=$* 162 | fi 163 | add "$input" 164 | ;; 165 | view | edit) 166 | shift 167 | edit 168 | ;; 169 | done) 170 | shift 171 | _done 172 | ;; 173 | list) 174 | shift 175 | list 176 | ;; 177 | *) 178 | usage >&2 179 | exit 1 180 | ;; 181 | esac 182 | shift 183 | done 184 | --------------------------------------------------------------------------------