Something went wrong. Try again.
Configuration for my NixOS based systems and Home Manager
Something went wrong. Try again.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240#!/usr/bin/env rcnl=''debounce=2nextok=()once=0model=()target=()fn usage { echo 'usage: todowatch.rc [-h] [--model model] [file-or-directory]' >[1=2] echo 'try: todowatch.rc --help' >[1=2] exit usage}fn help { cat <<'---'todowatch - watch files and run TODO(ai) tasks with opencodeusage: todowatch.rc [-h] [--model model] [file-or-directory]With no argument, the script watches the current directory.Give a directory name to watch a whole tree.Give a file name to watch one file.How it works:- The script waits for edits with entr or inotifywait.- After an edit, it searches the files for the exact text TODO(ai): .- The task text starts after the marker on that line.- Comment lines below the marker continue the task. The script treats these prefixes as comments: // # -- ; * block comments <!-- -->- A blank line can sit inside the task.- A code line, or two blank lines in a row, end the task.The script then runs one command per task: opencode run "Do the following TODO(ai) task: <task> File: <path>:<line>"<path> is relative to the directory where you started the script.Use --model to select the opencode model for each task.Notes:- The script waits 2 seconds after each edit before it acts. Change the debounce value at the top of the script to adjust this.- You need ripgrep, awk, entr or inotifywait, and opencode.---}while(! ~ $1 ()){ switch($1){ case -h --help help exit 0 case --once once=1 case --model if(~ $#* 1) usage shift model=$1 case --* usage case * if(! ~ $#target 0) usage target=$1 } shift}if(~ $#target 0) target=.if(! test -e $target){ usage}fn todotext { file=$1 start=$2 awk -v start=$start -f /dev/stdin $file <<'awk'# Collect a TODO(ai) task that starts on line "start" of the file.# The task continues on comment lines below it. A code line ends the task.# Append s to the result and stop reading. END then prints the result.function done(s){ if(s!="") out=out "\n" s; exit }# The TODO line: keep only the text after the marker.NR==start{ out=$0 sub(/.*TODO\(ai\): /,"",out) p=$0 sub(/^[ \t]+/,"",p) inblock=0 if(p~/^\/\*/){ t=p sub(/^\/\*/,"",t) inblock=(t!~/\*\//) } next}# Each line after the TODO line.NR>start{ trim=$0 sub(/\r$/,"",trim) sub(/^[ \t]+/,"",trim) # One blank line is allowed inside the task. # Two blank lines in a row end the task. if(trim==""){ if(++blank>=2) exit next } l=trim # Inside a /* block: strip the * at the start of each line. # A */ ends the task; keep any text before it. if(inblock){ sub(/^\*+[ \t]?/,"",l) if(l~/\*\/[ \t]*$/){ sub(/[ \t]*\*\/[ \t]*$/,"",l) done(l) } if(l!=""){out=out "\n" l; blank=0} next } # A new /* here opens a block. If it closes on this same line, # keep the text before */ and stop. if(l~/^\/\*/){ sub(/^\/\*/,"",l) if(l~/\*\/[ \t]*$/){ sub(/[ \t]*\*\/[ \t]*$/,"",l) done(l) } inblock=1 if(l!=""){out=out "\n" l; blank=0} next } # HTML comments: <!-- opens, --> closes and stops the task. html=0 if(l~/^<!--/){html=1; sub(/^<!--/,"",l)} if(l~/-->[ \t]*$/){html=1; sub(/[ \t]*-->[ \t]*$/,"",l)} # Line comments in most languages: // # -- ;; mark=0 if(l~/^(\/\/|#|--|;+)/){mark=1; sub(/^(\/\/|#|--|;+)[ \t]?/,"",l)} # A comment line adds its text to the task. # Any other line, for example code, ends the task. if(html || mark){ if(l!=""){out=out "\n" l; blank=0} if(html && trim~/-->[ \t]*$/) exit next } exit}END{print out}awk}fn scan { now=`{date +%s} if(! ~ $#nextok 0 && test $now -lt $nextok) return nextok=`{expr $now + $debounce} locs=() ifs=$nl{ if(test -f $target){ locs=`{rg -n --with-filename --no-heading 'TODO\(ai\): ' $target >[2]/dev/null | cut -d: -f1-2} } else { files=`{rg --files $target >[2]/dev/null} if(! ~ $#files 0) locs=`{rg -n --with-filename --no-heading 'TODO\(ai\): ' $files >[2]/dev/null | cut -d: -f1-2} } } if(~ $#locs 0) return cwd=`{pwd} ifs=$nl{ for(loc in $locs){ f=`{echo $loc | cut -d: -f1} n=`{echo $loc | cut -d: -f2} ifs=''{text=`{todotext $f $n}} if(! ~ $#text 0){ rel=`{printf %s $f | sed s@^$cwd/@@} msg='Do the following TODO(ai) task:' ^ $nl ^ $text ^ $nl ^ $nl ^ 'File: '$rel:$n args=() if(! ~ $#model 0) args=(--model $model) opencode run $args $^msg } } }}if(~ $once 1){ scan exit 0}watcher=()if(which inotifywait >/dev/null >[2=1]){ watcher=inotifywait} else if(which entr >/dev/null >[2=1]){ watcher=entr}if(~ $#watcher 0){ echo 'todowatch: install entr or inotifywait' >[1=2] exit deps}if(~ $watcher inotifywait){ excl='(^|/)(\.git|node_modules)(/|$)' args=(-q -m -e close_write,moved_to --format %w%f --exclude $excl) if(test -d $target) args=(-r $args) $args $target | for(f){scan}} else { while(true){ files=() ifs=$nl{ if(test -f $target){files=($target)} else {files=`{rg --files $target >[2]/dev/null}} } args=($0 --once) if(! ~ $#model 0) args=($args --model $model) if(! ~ $#files 0) {for(f in $files) echo $f} | entr -d $args $target sleep 1 }}