[limen CLI-watch-subcommand] cli watch subcommand#32
Conversation
limen task CLI-watch-subcommand
Reviewer's GuideAdds a new Sequence diagram for new limen watch CLI subcommandsequenceDiagram
actor User
participant CLI as limen_cli_main
participant WatchCmd as watch_subcommand
participant Watch as run_watch
participant Snap as snapshot
participant Render as render_render_compact
participant JSON as emit_json
User->>CLI: limen watch [--once --interval --compact --no-color]
CLI->>WatchCmd: watch(once, interval, compact, no_color)
WatchCmd->>Watch: run_watch(root, tasks_path, once, interval, compact, color)
loop each_refresh_frame
Watch->>Snap: snapshot(tasks_path)
Snap-->>Watch: limen, board, per_lane, working, procs, budget
Watch->>JSON: emit_json(root, board, per_lane, working, procs, budget_spent, budget_daily)
alt compact
Watch->>Render: render_compact(board, per_lane, procs, budget_spent, budget_daily)
else full
Watch->>Render: render(limen, board, per_lane, working, procs, interval, color)
end
Render-->>Watch: frame
alt once
Watch->>User: click.echo(frame)
Watch-->>Watch: return
else continuous
Watch->>User: write frame to stdout
Watch->>Watch: time.sleep(interval)
end
end
File-Level Changes
Tips and commandsInteracting with Sourcery
Customizing Your ExperienceAccess your dashboard to:
Getting Help
|
There was a problem hiding this comment.
Hey - I've left some high level feedback:
- The root/tasks resolution logic in
watch.py(_resolve_root,_resolve_tasks_path) largely duplicates behavior already present incli.py; consider reusing or centralizing these helpers to avoid divergence over time. emit_jsoncurrently swallows all exceptions with a bareexcept Exception:; it would be safer to catch only filesystem-related errors (e.g.OSError) or at least log unexpected failures so issues writingfleet-status.jsonare visible.- The process filtering in
proc_countsrelies on substring checks ofpsoutput and may be fragile (e.g. matching unrelated commands containingopencodeorlimen watchin arguments); consider tightening the matching or using a more robust mechanism if available on your target platforms.
Prompt for AI Agents
Please address the comments from this code review:
## Overall Comments
- The root/tasks resolution logic in `watch.py` (`_resolve_root`, `_resolve_tasks_path`) largely duplicates behavior already present in `cli.py`; consider reusing or centralizing these helpers to avoid divergence over time.
- `emit_json` currently swallows all exceptions with a bare `except Exception:`; it would be safer to catch only filesystem-related errors (e.g. `OSError`) or at least log unexpected failures so issues writing `fleet-status.json` are visible.
- The process filtering in `proc_counts` relies on substring checks of `ps` output and may be fragile (e.g. matching unrelated commands containing `opencode` or `limen watch` in arguments); consider tightening the matching or using a more robust mechanism if available on your target platforms.Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.
There was a problem hiding this comment.
Code Review
This pull request introduces a live dashboard feature via a new watch subcommand for the Limen fleet CLI. It displays real-time task statuses, active processes, and budget usage in either a detailed or compact terminal view, while periodically exporting this data to a JSON log. Feedback on the implementation highlights opportunities to improve robustness and usability in run_watch, specifically by handling KeyboardInterrupt and FileNotFoundError gracefully, and ensuring ANSI clear sequences are disabled when the --no-color flag is set.
Important
The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.
| while True: | ||
| limen, board, per_lane, working, procs, budget = snapshot(tasks_path) | ||
| emit_json(root, board, per_lane, working, procs, budget.track.spent, budget.daily) | ||
| frame = ( | ||
| render_compact(board, per_lane, procs, budget.track.spent, budget.daily) | ||
| if compact | ||
| else render(limen, board, per_lane, working, procs, interval=interval, color=color) | ||
| ) | ||
| if once: | ||
| click.echo(frame) | ||
| return | ||
| sys.stdout.write(("" if compact else CLEAR) + frame + "\n") | ||
| sys.stdout.flush() | ||
| time.sleep(interval) |
There was a problem hiding this comment.
Robustness and Usability Improvements in run_watch
There are three key issues in the current implementation of run_watch:
- Unhandled
KeyboardInterrupt: When running in watch mode, pressingCtrl-Cwill raise aKeyboardInterruptand print a traceback to the user. This should be caught to exit watch mode gracefully. - Unhandled
FileNotFoundError: Iftasks.yamldoes not exist or is deleted/renamed while the watch loop is running, the command will crash with a traceback. It should handle this gracefully by printing a clean error message and either exiting (if--onceis set) or retrying in the next iteration. - ANSI Sequences with
--no-color: TheCLEARANSI escape sequence (\033[2J\033[H) is written to stdout even whencolorisFalse(i.e.,--no-coloris passed). This violates the user's preference and can corrupt output logs if redirected.
try:
while True:
try:
limen, board, per_lane, working, procs, budget = snapshot(tasks_path)
emit_json(root, board, per_lane, working, procs, budget.track.spent, budget.daily)
frame = (
render_compact(board, per_lane, procs, budget.track.spent, budget.daily)
if compact
else render(limen, board, per_lane, working, procs, interval=interval, color=color)
)
if once:
click.echo(frame)
return
sys.stdout.write(("" if compact or not color else CLEAR) + frame + "\n")
sys.stdout.flush()
except FileNotFoundError:
error_msg = f"Error: tasks.yaml not found at {tasks_path}"
if once:
click.echo(error_msg, err=True)
sys.exit(1)
sys.stdout.write(("" if compact or not color else CLEAR) + error_msg + "\n")
sys.stdout.flush()
except Exception as e:
error_msg = f"Error loading tasks: {e}"
if once:
click.echo(error_msg, err=True)
sys.exit(1)
sys.stdout.write(("" if compact or not color else CLEAR) + error_msg + "\n")
sys.stdout.flush()
time.sleep(interval)
except KeyboardInterrupt:
if not once and not compact:
click.echo("\nExiting watch mode.")There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 89006f30a1
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| } | ||
| try: | ||
| (root / "logs").mkdir(exist_ok=True) | ||
| (root / "logs" / "fleet-status.json").write_text(json.dumps(data, indent=2)) |
There was a problem hiding this comment.
Ignore the generated watch feed
When LIMEN_ROOT points at a normal git checkout, every limen watch frame, including --once, writes logs/fleet-status.json. I checked the repository ignore rules and logs/ is not ignored, so simply viewing the dashboard leaves an untracked generated file in operators' worktrees and makes it easy to accidentally commit transient fleet state. Please either write this feed under an ignored/cache path or add an ignore rule for the generated file.
Useful? React with 👍 / 👎.
| "codex": ("\033[36m", "codex"), | ||
| "claude": ("\033[35m", "claude"), | ||
| "opencode": ("\033[32m", "opencode"), | ||
| "agy": ("\033[33m", "antigravity"), |
There was a problem hiding this comment.
Track the default agy worker process
The existing local-lane path routes Antigravity work as agy and dispatches the default agy -p executable, but this watcher scans ps for the substring antigravity. On machines using the default lane command, active Antigravity jobs will leave live_procs at 0 and the dashboard/JSON feed will report the agy lane as idle while it is doing work.
Useful? React with 👍 / 👎.
| for lane, (color_code, process_name) in LANES.items(): | ||
| counts = per_lane.get(lane, Counter()) |
There was a problem hiding this comment.
Include unrouted
any tasks in the lane view
When tasks are still queued with the schema-supported target_agent: any (for example from scripts/mine-backlog.py before routing), snapshot() counts them under per_lane['any'], but the dashboard only renders fixed entries from LANES. In that state the global open/in-flight totals include these tasks while no lane row or working-task detail shows where they are queued, so operators can miss unrouted backlog or active any claims.
Useful? React with 👍 / 👎.
| ) -> str: | ||
| budget = limen.portal.budget | ||
| total = sum(board.values()) | ||
| done = board.get("done", 0) |
There was a problem hiding this comment.
Count archived tasks as completed
The rest of the product treats archived as completed work after a done task is closed, but this dashboard only counts the literal done status. Once operators archive completed tasks through the QA flow, the watch header and per-lane progress bars under-report completion and make total progress appear to regress even though the work is finished.
Useful? React with 👍 / 👎.
Autonomous limen dispatch of task
CLI-watch-subcommand.Wire scripts/watch.py in as
python3 -m limen watchwith a --compact one-line mode; add a cli/tests test thatwatch --oncerenders without error.Produced in an isolated worktree off origin — review before merge.
Summary by Sourcery
Add a CLI watch subcommand and supporting module to render a live limen fleet dashboard, including a compact mode and JSON status export.
New Features:
Tests: