TUI for Todoist written with React+Ink.
0

Configure Feed

Select the types of activity you want to include in your feed.

show descriptions and groups in command hints

Aly Raffauf (Feb 15, 2026, 8:50 PM EST) 110d17d8 7cef3046

+45 -10
+22 -7
source/app.tsx
··· 25 25 let cachedProjects: Map<string, string> | null = null; 26 26 let cachedProjectsAt = 0; 27 27 28 + const HINT_PAD = 26; 29 + 28 30 function CommandHints({input}: {input: string}) { 29 31 if (input === '?') { 30 - return commands.map(c => ( 31 - <Text key={c.prefix} dimColor> 32 - {' '} 33 - {c.hint} 34 - </Text> 35 - )); 32 + const elements: React.ReactNode[] = []; 33 + let lastGroup = ''; 34 + for (const c of commands) { 35 + if (c.group !== lastGroup && lastGroup) { 36 + elements.push(<Text key={`sep-${c.group}`}> </Text>); 37 + } 38 + 39 + lastGroup = c.group; 40 + elements.push( 41 + <Text key={c.prefix} dimColor> 42 + {' '} 43 + {c.hint.padEnd(HINT_PAD)} 44 + {c.description} 45 + </Text>, 46 + ); 47 + } 48 + 49 + return <>{elements}</>; 36 50 } 37 51 38 52 if (input) { ··· 41 55 .map(c => ( 42 56 <Text key={c.prefix} dimColor> 43 57 {' '} 44 - {c.hint} 58 + {c.hint.padEnd(HINT_PAD)} 59 + {c.description} 45 60 </Text> 46 61 )); 47 62 }
+23 -3
source/commands.ts
··· 16 16 type Command = { 17 17 prefix: string; 18 18 hint: string; 19 + description: string; 20 + group: 'tasks' | 'nav' | 'app'; 19 21 run: (args: string, ctx: CommandContext) => Promise<void>; 20 22 }; 21 23 22 24 export const commands: Command[] = [ 23 25 { 24 26 prefix: 'done ', 25 - hint: 'done <number>', 27 + hint: 'done <n>', 28 + description: 'complete a task', 29 + group: 'tasks', 26 30 run: async (args, {api, tasks, setTasks, setMessage}) => { 27 31 const num = Number.parseInt(args, 10); 28 32 const task = tasks[num - 1]; ··· 37 41 38 42 { 39 43 prefix: 'edit ', 40 - hint: 'edit <n> [due|p1-p4|labels|desc|project|title]', 44 + hint: 'edit <n> <field> <value>', 45 + description: 'edit a task field', 46 + group: 'tasks', 41 47 run: async (args, {api, tasks, projects, setTasks, setMessage}) => { 42 48 const spaceIdx = args.indexOf(' '); 43 49 const numStr = spaceIdx === -1 ? args : args.slice(0, spaceIdx); ··· 105 111 106 112 { 107 113 prefix: 'desc ', 108 - hint: 'desc <number>', 114 + hint: 'desc <n>', 115 + description: 'show task description', 116 + group: 'tasks', 109 117 run: async (args, {tasks, setMessage}) => { 110 118 const num = Number.parseInt(args, 10); 111 119 const task = tasks[num - 1]; ··· 120 128 { 121 129 prefix: 'add ', 122 130 hint: 'add <task>', 131 + description: 'add a new task', 132 + group: 'tasks', 123 133 run: async (args, {api, tasks, setTasks}) => { 124 134 const task = await api.quickAddTask({text: args}); 125 135 setTasks([...tasks, task]); ··· 128 138 { 129 139 prefix: 'filter ', 130 140 hint: 'filter <query>', 141 + description: 'filter tasks', 142 + group: 'nav', 131 143 run: async (args, {setView}) => { 132 144 setView({type: 'filter', query: args}); 133 145 }, ··· 135 147 { 136 148 prefix: 'refresh', 137 149 hint: 'refresh', 150 + description: 'refresh tasks', 151 + group: 'nav', 138 152 run: async (_args, {refresh}) => { 139 153 await refresh(); 140 154 }, ··· 143 157 { 144 158 prefix: 'today', 145 159 hint: 'today', 160 + description: "show today's tasks", 161 + group: 'nav', 146 162 run: async (_args, {setView}) => { 147 163 setView({type: 'filter', query: 'today'}); 148 164 }, ··· 151 167 { 152 168 prefix: 'home', 153 169 hint: 'home', 170 + description: 'go to home view', 171 + group: 'nav', 154 172 run: async (_args, {setView, homeFilter}) => { 155 173 setView({type: 'filter', query: homeFilter}); 156 174 }, ··· 159 177 { 160 178 prefix: 'quit', 161 179 hint: 'quit', 180 + description: 'exit dewy', 181 + group: 'app', 162 182 run: async (_args, {exit}) => { 163 183 exit(); 164 184 },