inbox
The inbox command manages a local inbox of the open issues assigned to you - from Jira, and from any plugin that is an inbox source. The watcher polls every source in the background, stores discovered issues locally, and shows a desktop toast when a new issue appears or an existing one visibly changes. From the inbox you can pin, snooze, dismiss, open in the browser, or take an issue into your task list - one at a time, or the whole pile in one sitting with triage.
Every sync reconciles the list against each source: issues that stop appearing in their source’s poll (closed or reassigned) are marked gone and leave the list instead of lingering forever. They stay inspectable with --all.
Decisions that are the same every time - anything High or above goes on top, Epics are never yours, work from one project carries one tag - are written once as rules, and the inbox applies them by itself.
The inbox also writes to an issue’s source, in two places and no more: start moves an issue into progress, close resolves it, and comment leaves a note. Those are the two moments a local task and its issue meet - taking the issue and finishing the work - and Closing the loop is how they connect.
kasl inbox [OPTIONS] [COMMAND]Running kasl inbox without a subcommand lists the active (non-dismissed) issues.
Options
Section titled “Options”-n, --limit <N>: Show only the top N issues after filtering and sorting--all: Include issues gone from their source (closed or reassigned); they sort below the present ones--snoozed: Include issues that are still asleep, each showing the date it is due back--dismissed: Include dismissed issues, each saying whether you or a rule dismissed it--json: Print the list as one JSON document - see JSON output. It belongs to the list:kasl inbox --json pin PROJ-412is refused before anything is pinned; give--jsonto the subcommand that prints data
Filters
Section titled “Filters”An inbox of two hundred open issues is a pile, not a list. These cuts turn it into one; they combine (every set filter must hold) and apply to the bare kasl inbox, to list, and to every picker (take, open, pin, unpin, dismiss without a key), so a take --since 7d offers only this week’s issues.
--since <WINDOW>: Only issues first seen within the window -1d,7d,12h,2w; a bare number is days--new: Only issues discovered in the last day (the same window as theNEWbadge)--changed <WINDOW>: Only issues that visibly changed (status, priority, score) within the window; issues that never changed are out--min-score <N>: Only issues whose ranking field (e.g. Scoring) is at least N; issues without a score are out--priority <NAME[+]>: Only issues of that priority, named as your Jira names it; add+for that priority and everything more urgent (High+). An unknown name lists the ones the inbox knows--status <NAME>: Only issues in that status, by name or id, regardless of case; repeat it for any of several--type <NAME>: Only issues of that type, as your Jira names it (Bug,Story,Epic), regardless of case; repeat it for any of several--project <KEY>: Only issues of that project - the prefix of the key; repeat it for any of several--sort <score|priority|new|changed>: Order of the list;scoreis the inbox’s own order (ranking field, then priority). Pinned issues lead and gone issues trail whatever the order--preset <NAME>: The cuts a preset in the config names; a filter given beside it wins over the preset’s own
Priority names are read against every priority the inbox has ever seen, not only the ones on the list right now. The same words are what a rule’s when is written in, so a rule pins exactly what the same cuts would list.
A cut list says so in its header - Inbox: 12 of 200 issues (since 7d, score ≥ 5): - so a filtered view never reads as the whole inbox. When nothing matches, the message names the cuts and the size of the whole.
Commands
Section titled “Commands”sync - Sync the inbox from every source
Section titled “sync - Sync the inbox from every source”kasl inbox syncPolls every source immediately instead of waiting for the background cadence: Jira when it is configured, and each plugin source that is set up. Each source answers for itself, counting fetched, new, changed and gone issues:
[✓] Jira synced: 7 fetched, 1 new, 2 changed, 1 gone.[✓] gitlab synced: 3 fetched, 0 new, 1 changed, 0 gone.A source that fails is named with its reason and the others still sync; the command then exits non-zero. With no source at all, sync says how to add one.
list - List active inbox issues
Section titled “list - List active inbox issues”kasl inbox list [OPTIONS]Options:
-n, --limit <N>: Show only the top N issues--all: Include issues gone from Jira--snoozed: Include issues that are still asleep--dismissed: Include dismissed issues- The filters above:
--since,--new,--changed,--min-score,--priority,--status,--type,--project,--sort --json: Print the same slice, in the same order, as one JSON document - see JSON output
The CHANGE column carries freshness badges for about a day: NEW for freshly discovered issues, a change summary such as status→In Progress, ↑prio High, or score 5→8 for existing ones, and gone for issues no longer returned by Jira (visible only with --all). taken marks an issue you have already started; unlike the others it does not fade, and it outranks NEW and change summaries. zzz Mar 4 is a sleeping issue and the date it is due back, shown only under --snoozed; back marks one whose snooze has just run out. gone outranks everything - and for an issue you took, it reads resolved once Jira has said that is why it left. Under --dismissed, a dismissed issue reads dismissed, or ruled out: NAME when a rule hid it.
triage - Triage the inbox issue by issue
Section titled “triage - Triage the inbox issue by issue”kasl inbox triage [--snooze-for <FOR>] [FILTERS]Options:
--snooze-for <FOR>: How longsnoozesleeps during this run -3d,12h,2w. Defaults to1d.- The filters above, so
triage --since 7d --min-score 5walks exactly that slice
Walks the inbox one issue at a time and asks what to do with each. Two hundred issues are not triaged by running take, snooze and dismiss two hundred times, each re-reading the list to find the next row.
Each issue is shown with its position, badge, score and priority, and offers:
- take - make it a task and start
- snooze - not now, bring it back later (for
--snooze-for) - dismiss - not mine, hide it for good
- open - look at it in the browser, then decide
- skip - leave it and move on (the default)
- quit - stop here; everything not yet decided is left untouched
open is a look, not a decision: the issue is asked about again once the browser is up. skip leads because the common answer in a long run is “not this one”, and it is the only choice that changes nothing if the finger slips. A run ends with what it came to:
[✓] Triaged: 3 taken, 5 snoozed, 2 dismissed, 12 skipped.Triage is a conversation, so it needs a terminal. In a script, use inbox take, inbox snooze and inbox dismiss with explicit keys.
show - Show one inbox issue
Section titled “show - Show one inbox issue”kasl inbox show [KEY] [--why] [--json] [FILTERS]Arguments:
KEY: Issue key, e.g.PROJ-123. Omit it on a terminal to pick from the inbox; the filters narrow what the picker offers.
Options:
--why: Explain where the ranking comes from and what each mark means--json: Print the issue - and with--whythe explanation - as one JSON document; see JSON output
One issue in full: the whole summary, its type and project, the URL, and the dates a six-column table row has no room for. A key the inbox does not hold is an error (exit code 1).
--why answers the question a list sorted by an invisible field invites - why is this issue here:
+----------+--------------+---------------------------------------------------+| WHAT | VALUE | WHY |+----------+--------------+---------------------------------------------------+| score | 8 | read from Scoring in Jira; the list is ordered by || | | it, highest first || priority | High | Jira priority id 2, which breaks ties on equal || | | scores - lower is more urgent || pinned | yes | pinned issues lead the list whatever the order |+----------+--------------+---------------------------------------------------+A pin says who made it - pinned by hand, or pinned by the rule 'on fire' - and every rule the issue matches now gets a line of its own saying what that rule does.
Note what this is not: a score broken into points. kasl does not compute importance. The score is a number read straight out of the Jira field you named in configuration, and the priority rank is Jira’s own priority id - so --why names where each part came from rather than inventing a local formula that would disagree with Jira about which issue matters.
pin - Pin an inbox issue
Section titled “pin - Pin an inbox issue”kasl inbox pin [KEY] [FILTERS]Arguments:
KEY: Issue key, e.g.PROJ-123. Omit it on a terminal to pick from the inbox; the filters narrow what the picker offers.
Pinned issues stay on top of the list.
unpin - Unpin an inbox issue
Section titled “unpin - Unpin an inbox issue”kasl inbox unpin [KEY] [FILTERS]Arguments:
KEY: Issue key, e.g.PROJ-123. Omit it on a terminal to pick from the inbox; the filters narrow what the picker offers.
dismiss - Dismiss an inbox issue
Section titled “dismiss - Dismiss an inbox issue”kasl inbox dismiss [KEY] [FILTERS]Arguments:
KEY: Issue key, e.g.PROJ-123. Omit it on a terminal to pick from the inbox; the filters narrow what the picker offers.
Hides an issue from the list until you bring it back with
undismiss. Dismissal is the answer to
“this is not mine”; for “not now”, see snooze.
undismiss - Undismiss an inbox issue
Section titled “undismiss - Undismiss an inbox issue”kasl inbox undismiss [KEY]Arguments:
KEY: Issue key, e.g.PROJ-123. Omit it on a terminal to pick from the dismissed issues.
Brings a dismissed issue back into the list, whoever dismissed it. This is the
way back from a rule that hid the wrong issues: correct the rule in
the config, then bring back what it hid - kasl inbox list --dismissed shows
each one with the rule that hid it. Bringing an issue back by hand takes the
decision from the rule, which will not hide it again unless the issue leaves
the rule and comes back into it.
[✓] PROJ-123 is back in the inbox.snooze - Snooze an inbox issue
Section titled “snooze - Snooze an inbox issue”kasl inbox snooze [KEY] [FOR] [FILTERS]Arguments:
KEY: Issue key, e.g.PROJ-123. Omit it on a terminal to pick from the inbox; the filters narrow what the picker offers.FOR: How long to sleep -3d,12h,2w; a bare number is days. Defaults to1d.
Puts an issue down until the moment passes. It leaves the list, stops counting
among what is waiting, and comes back on its own when its time is up - with a
toast and a back badge saying why it has returned.
The difference from dismiss is the returning. Dismissal means “never” and is
right for an issue that is not yours; snoozing means “not now” and is right for
the issue you will deal with on Monday. Without it the only way to defer an
issue was to keep reading past it, which is how an inbox stops being read.
Sleeping issues come back whether or not Jira is reachable: the due date is local bookkeeping, so a VPN outage cannot hold an issue past its moment.
[✓] Snoozed PROJ-123 until Mar 4 09:30.unsnooze - Wake a snoozed inbox issue
Section titled “unsnooze - Wake a snoozed inbox issue”kasl inbox unsnooze [KEY]Arguments:
KEY: Issue key, e.g.PROJ-123. Omit it on a terminal to pick from the sleeping issues - only those, since waking an awake issue does nothing.
Brings a sleeping issue back before its time.
open - Open issue URL in browser
Section titled “open - Open issue URL in browser”kasl inbox open [KEY] [FILTERS]Arguments:
KEY: Issue key, e.g.PROJ-123. Omit it on a terminal to pick from the inbox; the filters narrow what the picker offers.
take - Start working on an issue
Section titled “take - Start working on an issue”kasl inbox take [KEY] [FILTERS]Arguments:
KEY: Issue key, e.g.PROJ-123. Omit it on a terminal to pick from the inbox; the filters narrow what the picker offers.
Creates a task named KEY summary and records that the issue is in hand: the
task stores the issue key, and the issue stays in the inbox wearing a taken
badge. Dismissing is still a separate act - take means “I started this”, not
“this is not mine”.
Tags come with the take when a rule says so - "when": {"project": "PAY"}, "then": {"tag": "payments"} tags every task taken from PAY, from the
command line, from triage and from a toast button alike.
Taking the same issue twice does not create a second task; the command says which task it already became. The stored key survives renaming the task, so the link outlives the summary it started with.
With start_on_take on, the take also starts the issue in
Jira, exactly as start would. The task is
made first and stands whatever Jira answers: a VPN that is down costs the move,
not the task, and the answer says so and names kasl inbox start KEY to try
again.
[✓] Imported PROJ-123 into tasks.[✓] PROJ-123 is now In Progress in Jira.start - Start an issue at its source
Section titled “start - Start an issue at its source”kasl inbox start [KEY] [--to <NAME>] [FILTERS]Arguments:
KEY: Issue key, e.g.PROJ-123. Omit it on a terminal to pick from the inbox; the filters narrow what the picker offers.
Options:
--to <NAME>: Which way, when Jira offers several - the target status (In Review) or the transition (Start Review), either spelling, any case
Moves the issue into progress: through the transition whose target status is in Jira’s in progress category. kasl never moves an issue by status name - names are your workflow’s own and come translated (“In Progress”, “В работе”), while the category is the same on every Jira, so this works on any workflow without setup. An issue already in progress is left as it is and the command says so.
When more than one transition leads into progress, kasl asks on a terminal and
refuses to guess anywhere else; --to settles it in a script. A move that does
not happen - no way from the current status, several ways and nobody to ask,
an issue Jira does not show you - fails the command, so a script can tell.
An issue from a plugin source is started by the plugin: kasl sends
it a transition with to: start and the --to you gave, and keeps the
status it answers. A source that does not move issues says so, and the command
fails.
close - Close an issue at its source
Section titled “close - Close an issue at its source”kasl inbox close [KEY] [--comment <TEXT>] [--to <NAME>] [--resolution <NAME>] [FILTERS]Arguments:
KEY: Issue key, e.g.PROJ-123. Omit it on a terminal to pick from the inbox; the filters narrow what the picker offers.
Options:
-m, --comment <TEXT>: A comment to add before closing--to <NAME>: Which way, when Jira offers several (Resolved,Closed,Resolve Issue)--resolution <NAME>: The resolution, when closing asks for one (Done,Fixed,Won't Fix)
Moves the issue into Jira’s done category, the same way start moves it into
progress. When the close screen requires a resolution, kasl does not assume
one - Fixed, Done and Won't Do all close an issue and mean different
things on the team’s reports. A single allowed value is no choice at all; on a
terminal several are asked about; in a script --resolution names one, and
without it the command lists them and fails.
The comment goes first. A closed issue is often no longer editable, and Jira then refuses comments on it too, so closing first could leave the note with nowhere to go. A comment Jira refuses stops the close.
Other required fields on the close screen are Jira’s to explain: its own words
come back as the error - customfield_10100: Story points are required. -
and the issue is finished in the browser.
An issue from a plugin source is closed by the plugin, with the comment,
--to and --resolution handed over in the same transition request.
comment - Comment on an issue at its source
Section titled “comment - Comment on an issue at its source”kasl inbox comment [KEY] [TEXT] [FILTERS]Arguments:
KEY: Issue key, e.g.PROJ-123. Omit it on a terminal to pick from the inbox; the filters narrow what the picker offers.TEXT: The comment. Omit it on a terminal to type it; elsewhere it is required.
Closing the loop
Section titled “Closing the loop”A task taken from the inbox and the issue it came from are one piece of work in two places, and each side hears when the other finishes.
Finished here, so Jira is asked. When a task taken from an issue reaches
100% - in kasl task edit, or when kasl task find carries it to 100% - kasl
asks what to do in Jira:
? PROJ-123 is done here. In Jira ›❯ Leave PROJ-123 as it is Close PROJ-123 Comment on PROJ-123Leaving it is the default, so Enter on a question about a shared tracker changes nothing in it. The question is only asked on a terminal, and not when the inbox already knows Jira resolved the issue.
Resolved in Jira, so the task is offered. When an issue you took leaves the
inbox, kasl asks Jira why - one request per departed issue, and only for issues
you took. Resolved, the row is marked resolved; unresolved (reassigned, moved
out of reach) it stays gone. If the task is still open, the offer is made
once, wherever the departure was seen:
- the watcher shows a toast with a single Mark done button;
kasl inbox syncon a terminal asks, with every such task ticked:
? Resolved in Jira - mark these tasks done (space toggles, enter confirms) ›✔ PROJ-123 Fix login redirect (Done)kasl inbox syncin a script says it instead:
[i] PROJ-123 was resolved in Jira (Done); its task is still open - `kasl task find` offers to finish it.The offer does not repeat on every poll. What lasts is
kasl task find: an unfinished
task whose issue Jira resolved is marked resolved in Jira, ticked, and
proposed at 100%.
Marking a task done moves its newest day to 100%: today’s row when there is
one, otherwise a row for today that continues the task - the same thing
task find does when work carries over, so today’s report shows it finished
today. An answer Jira did not give (VPN down) is asked for again on the next
poll.
Rules are written in the config, under inbox.rules. Each has a when and a then, and may have a name:
{ "inbox": { "rules": [ { "name": "on fire", "when": { "priority": "High+" }, "then": { "pin": true, "urgent": true } }, { "when": { "min_score": 8 }, "then": { "pin": true } }, { "when": { "type": ["Epic", "Sub-task"] }, "then": { "dismiss": true } }, { "when": { "project": "PAY" }, "then": { "tag": "payments" } } ] }}when uses the words of the filters: priority (a name as your Jira writes it, + for that priority and everything more urgent), min_score, status, type and project. Every condition given must hold; a list means any of its names; names match whatever the case. A priority nobody has had yet is not an error: the rule waits until the inbox has seen that name, and kasl inbox rules says it is waiting.
then is one or more of:
pin- pin the issue, askasl inbox pindoesurgent- announce it with a toast that stays until you act on it: a reminder toast on Windows, a critical notification on Linux; macOS has only the ordinary toast. It takes the place of the issue’s new or changed toast rather than adding a second one, counts in an hourly budget of its own so a busy afternoon cannot crowd it out, and wakes the issue if it was snoozed.notify: falsesilences it;notify_changes: falsedoes notdismiss- hide the issue, askasl inbox dismissdoestag- one tag or a list, put on the task when the issue is taken
Without a name, a rule is called by its conditions - priority High+ - in messages, toasts and show --why.
When a rule acts. Pin, urgent and dismiss act when an issue arrives already matching, or when a change makes it match: an issue raised to High is pinned on that poll. They do not act again while it keeps matching, so unpinning an issue a rule pinned sticks. If it drops below High and is raised again, that is news, and the rule acts again. Tags act at the take. Issues already in the inbox when you write a rule are left alone until you apply the rules to them with kasl inbox rules --apply.
When rules disagree. Every matching rule acts. Dismissal wins over pin and urgent - an issue a rule hides is not also pinned and announced. A rule never hides an issue you have taken, and never touches one that is already dismissed: those are your decisions.
A rule that cannot be read is skipped, never guessed at. A misspelt key (prority) is refused rather than ignored, because ignored it would leave a rule without conditions, which matches every issue. So is a rule that does nothing, one that both dismisses and pins, and a dismiss with no conditions at all. The rest of the config and the other rules carry on: a typo in a rule never stops the watcher recording your day. kasl inbox warns in one line, and kasl inbox rules says which rule and why:
[!] Inbox rule 4 is skipped: unknown field `prority`, expected one of `priority`, `min_score`, `status`, `type`, `project`.What a rule pinned or dismissed carries its name: show --why says pinned by the rule 'on fire', and list --dismissed shows what each rule hid. Pinning, unpinning, dismissing or bringing back by hand takes the decision back from the rule.
rules - Show and apply inbox rules
Section titled “rules - Show and apply inbox rules”kasl inbox rules [--apply] [--json]Options:
--apply: Apply the rules to the issues already in the inbox, as if each had just arrived--json: Print the rules, the ones skipped and what--applychanged as one JSON document; see JSON output
Lists the rules with what each matches in the inbox now and what its pin or dismissal has not reached yet. Without --apply this is a dry run - the way to check a dismiss rule before it hides anything:
+---+---------+-----------------------+-------------------+------------------------------+| # | NAME | WHEN | THEN | NOW |+---+---------+-----------------------+-------------------+------------------------------+| 1 | on fire | priority High+ | pin, urgent toast | 1 match, 1 not yet pinned || 2 | — | type Epic or Sub-task | dismiss | 1 match, 1 not yet dismissed || 3 | — | project PAY | tag payments | 1 match, 1 taken |+---+---------+-----------------------+-------------------+------------------------------+[i] 2 issue(s) match a rule that has not acted on them: rules act when an issue arrives or changes. `kasl inbox rules --apply` acts on them now.--apply pins and dismisses what the rules say, and tags the tasks of issues already taken. It shows no toasts - none of it is news - and running it twice changes nothing the second time:
[i] PROJ-412: pinned by 'on fire'.[i] PROJ-418: dismissed by 'type Epic or Sub-task'.[i] PAY-77: its task tagged payments.[✓] The rules changed 3 issue(s).+---+---------+-----------------------+-------------------+----------------------+| # | NAME | WHEN | THEN | NOW |+---+---------+-----------------------+-------------------+----------------------+| 1 | on fire | priority High+ | pin, urgent toast | 1 match, 1 pinned || 2 | — | type Epic or Sub-task | dismiss | 1 match, 1 dismissed || 3 | — | project PAY | tag payments | 1 match, 1 taken |+---+---------+-----------------------+-------------------+----------------------+Presets
Section titled “Presets”The cuts you type every morning can have a name. A preset is written in the config, under inbox.presets, in the words of the filters - since, new, changed, min_score, priority, status, type, project and sort:
{ "inbox": { "presets": { "urgent": { "priority": "High+", "since": "7d" }, "fresh-bugs": { "type": "Bug", "new": true, "sort": "priority" } } }}kasl config set writes one without opening the file:
kasl config set inbox.presets.urgent '{"priority": "High+", "since": "7d"}'--preset NAME works wherever the filters do - the bare list, list, triage and every picker - and the header names it:
$ kasl inbox --preset urgentInbox: 3 of 41 issues (preset urgent, since 7d, priority High+):A filter given beside the preset wins over the preset’s own field, so kasl inbox --preset urgent --since 1d narrows the preset to the last day; --new replaces the preset’s window too. A list filter (--status, --type, --project) replaces the preset’s list whole.
A preset is read when a command names it, so a misspelt one fails that command - naming the preset and what is wrong with it - and never the rest of the config. kasl config check finds it before you do. An unknown name lists the presets the config has.
Toast buttons
Section titled “Toast buttons”A toast about one issue carries the three decisions worth making about it, so the pile can be triaged without opening a terminal:
- Take - imports the issue into your tasks, exactly as
kasl inbox takedoes - including the move in Jira whenstart_on_takeis on - Snooze - puts it to sleep for
toast_snooze_for(a day by default) - Dismiss - hides it from the list
The toast saying a taken issue was resolved in Jira has one button of its own, Mark done, which finishes the task.
A button has nobody to ask, so a take from a toast moves the issue only when Jira offers exactly one way into progress, and only with a session kasl already holds (a cached login or the password in the keyring). Otherwise the answer toast says what stopped it and names kasl inbox start KEY.
Clicking the toast body, as before, opens the issue in the browser.
The press is carried out by the running kasl watch daemon, which answers with a second toast saying what happened - KA-1 is now a task, KA-1 sleeps until Sep 18 09:30. An answer within a couple of seconds is the point: a button that changes something in silence leaves you unsure the press registered.
Pressing a button for an issue that Jira has since closed or reassigned says so (KA-1 is no longer in the inbox) instead of failing quietly. Pressing Take twice reports the task that already exists rather than making a second copy - which matters, because a toast can be pressed from the notification centre long after it appeared.
Stopping the watcher does not leave a dead button: with no daemon running, the press is carried out on the spot instead.
What each platform can do
Section titled “What each platform can do”| Platform | Toast body | Buttons |
|---|---|---|
| Windows | Opens the issue | Take, Snooze, Dismiss; Mark done on a resolved issue |
| Linux (and other XDG desktops) | Opens the issue | Take, Snooze, Dismiss; Mark done on a resolved issue |
| macOS | Display only | None - use kasl inbox triage |
On macOS the notification API cannot report a click at all, so the buttons are not drawn rather than drawn dead: kasl inbox triage walks the same three decisions with room to think.
Windows needs no registry entry and no protocol of its own. A toast button there can only ask the shell to launch a URI, and no argument survives that trip, so the watcher writes one shortcut per (action, issue) under toast-buttons/ in the data directory and the button launches that. The shortcuts of a decided issue are removed once it is settled.
Sources
Section titled “Sources”An issue comes from one source, and its row says which: jira, or the name of
the plugin that listed it (source in the JSON). Everything
the inbox does by itself is the same whatever the source - the list, badges,
snoozes, triage, toasts and rules; a source only says what is
assigned, and moves or comments on an issue when asked.
- Jira is kasl’s own source: the
jirablock and theinboxsection of the config switch it on. - A plugin is a source when its manifest answers
list_issues; it is polled once it is installed and set up, on the same cadence as Jira.kasl plugin listshows which plugins are sources and what else they answer (transition,comment). How a source answers is in Writing a plugin. - GitLab is the plugin source that ships with kasl: issues and merge
requests assigned to you and the merge requests you are asked to review,
keyed
group/app#14andgroup/app!31- see Working with the GitLab inbox.
A key is one word of ASCII letters, digits and - _ . / # ! : @ + ~, at most
200 characters, whatever its source: it goes onto command lines and into the
toast buttons. A source listing anything else fails its sync whole. In bash
and zsh, quote a key with ! - kasl inbox take 'group/app!31' - since !
starts history expansion there.
Each source is reconciled on its own: a poll marks gone only its own issues that it no longer lists, never another source’s. A key belongs to the source that listed it first; another source listing the same key is left out, with a warning naming both. A source’s answer that kasl cannot read whole fails its sync and changes nothing, as a failed Jira poll does. Only Jira is asked why a taken issue left, so the Mark done offer of Closing the loop is Jira’s; a plugin’s issue that leaves is simply gone.
start, close and comment go to the issue’s source, and so does the start
of inbox.start_on_take. A key the inbox has never seen goes to Jira, the one
source kasl can ask about an issue it did not list.
Background Polling
Section titled “Background Polling”Polling runs inside kasl watch (both daemon and --foreground modes), for every source. New issues trigger a desktop notification; clicking the toast opens the issue in the browser on Windows and Linux, and its buttons decide it. On macOS the toast is display-only - the notification API cannot report a click - so opening stays on kasl inbox open. Each issue is notified about only once. Status and priority changes on existing issues also toast (a score change or a return after a missed poll only sets the badge), and issues leaving the inbox can toast too when notify_gone is enabled. Snoozed issues whose time is up are woken at the start of each poll and toast their return; that step is local and runs even when the Jira poll itself is skipped or fails.
When an issue you took leaves the inbox, the poll also asks Jira why, and a resolved one with an open task gets the Mark done toast described in Closing the loop. That toast counts against the same hourly budget as the rest, and is not held back by notify_gone: it is a question about your task, not news that an issue left.
A poll that fails - VPN down, Jira unreachable, a session that could not be renewed - changes nothing: the list is reconciled only against an answer Jira actually gave, so a bad night does not mark every issue gone and bring all of them back in the morning. Two answers that look like answers are refused the same way: an expired session that Jira serves as the anonymous user (an empty page with status 200), and a list shorter than the total Jira counted, which happens when an issue is created or resolved while the pages are read.
Toasts are budgeted over time, not per poll, and across sources: one person reads them all. Within any hour at most five single toasts show; past that, one summary toast says how many issues were new, changed or left and opens the source’s list of your issues, and the rest of the hour stays quiet. A trickle of three changes every five minutes is held to the same bound as two hundred at once.
Urgent toasts - the ones a rule asks for - are budgeted the same way in a lane of their own: an afternoon of ordinary changes does not spend the room an escalation needs, and fifty issues turning urgent at once still collapse into one summary.
Each poll also runs the rules over what it brought. Pins and dismissals by rule happen with or without toasts; kasl inbox sync says what each rule did:
[✓] Jira synced: 6 fetched, 2 new, 1 changed, 0 gone.[i] PROJ-420: pinned by 'on fire'; urgent by 'on fire'.[i] PROJ-431: pinned by 'on fire'; urgent by 'on fire'.[i] PROJ-433: dismissed by 'type Epic or Sub-task'.Configuration
Section titled “Configuration”Jira’s poll is enabled by adding the inbox section to the config; the jira section must be configured as well. A plugin source needs no section - it is polled once it is set up - and these settings apply to it all the same, their defaults when the section is absent. enabled: false stops every source. kasl config set changes one setting - kasl config set inbox.notify_gone true - and checks it first.
{ "inbox": { "enabled": true, "poll_interval_secs": 300, "notify": true, "notify_changes": true, "notify_gone": false, "toast_snooze_for": "1d", "start_on_take": false, "custom_fields": [{ "id": "customfield_12345", "label": "Scoring" }], "sort_by_field": "customfield_12345", "rules": [ { "name": "on fire", "when": { "priority": "High+" }, "then": { "pin": true, "urgent": true } }, { "when": { "type": "Epic" }, "then": { "dismiss": true } } ] }}enabled: Whether the watcher polls the inbox’s sources (defaulttrue)poll_interval_secs: Seconds between polls (default300)notify: Show desktop toasts for new issues (defaulttrue); whenfalse, all inbox toasts are offnotify_changes: Toast when an existing issue changes status or priority (defaulttrue); score changes only set the badgenotify_gone: Toast when an issue leaves the inbox — closed or reassigned (defaultfalse)toast_snooze_for: How long a toast’s Snooze button sleeps —3d,12h,2w(default1d), the same spelling astriage --snooze-forstart_on_take: Every take - command, triage, toast button - also starts the issue at its source (defaultfalse). Off by default because it writes to a tracker the whole team reads, and in some teams “In Progress” means more than “on my list”custom_fields: Extra Jira fields to fetch and display, such as a Scoring fieldsort_by_field: Field id used to rank the list in descending orderrules: What the inbox does by itself - see Rules (default: none)presets: Named cuts of the list - see Presets (default: none)
Examples
Section titled “Examples”# Show the inboxkasl inbox
# Top five issues by rankingkasl inbox -n 5
# This week's arrivals, most urgent firstkasl inbox --since 7d --sort priority
# What is worth a look: High or above, scoring at least 5kasl inbox --priority High+ --min-score 5
# Pick something to start from what changed todaykasl inbox take --changed 1d
# Sync now and show the resultkasl inbox synckasl inbox list
# Check what left the inboxkasl inbox list --all
# Clear the whole pile in one sittingkasl inbox triage
# Triage only this week's serious ones, sleeping the rest for three dayskasl inbox triage --since 7d --min-score 5 --snooze-for 3d
# Put one issue down until Thursday, and see what is asleepkasl inbox snooze PROJ-123 3dkasl inbox --snoozed
# Why is this issue at the top?kasl inbox show PROJ-123 --why
# Only bugs from two projectskasl inbox --type Bug --project PAY --project OPS
# Check the rules, then apply them to the issues already herekasl inbox ruleskasl inbox rules --apply
# See what was dismissed - by you or by a rule - and bring one backkasl inbox list --dismissedkasl inbox undismiss PROJ-456
# Work with a specific issuekasl inbox pin PROJ-123kasl inbox open PROJ-123kasl inbox take PROJ-123
# Move it in Jira by handkasl inbox start PROJ-123kasl inbox close PROJ-123 --comment "Fixed in 2.3" --resolution Donekasl inbox comment PROJ-123 "Waiting on the API team"Scripting
Section titled “Scripting”Every subcommand takes its issue key as an argument, so the inbox can be driven from scripts without any interactive prompt:
kasl inbox synckasl inbox take PROJ-123kasl inbox dismiss PROJ-456kasl inbox close PROJ-123 --to Closed --resolution Donestart and close exit non-zero when the issue did not end up where they were sent, and name the flag that settles it.
JSON output
Section titled “JSON output”list, show and rules take --json and print one document each; the
conventions every kasl document keeps are in
kasl in scripts and CI. They read the
local inbox table only - never Jira - so they answer at once, whatever the
network is doing.
kasl inbox list --json - the same slice in the same order the table shows,
the filters, --sort and --limit included:
{ "issues": [ { "key": "PROJ-431", "summary": "Checkout fails on saved cards", "status": "In Progress", "status_id": "3", "priority": "High", "score": 8.5, "type": "Bug", "project": "PROJ", "source": "jira", "url": "https://jira.example.com/browse/PROJ-431", "pinned": true, "dismissed": false, "ruled_by": null, "badge": "NEW", "first_seen": "2026-08-08T08:41:07", "last_seen": "2026-08-08T18:05:00", "changed_at": null, "taken_at": null, "snoozed_until": null, "gone_at": null, "left": null, "resolution": null } ], "total": 3, "filter": ["since 7d", "priority High+"]}| Field | Meaning |
|---|---|
total |
How many issues the inbox holds before the filters and the limit; fewer issues than this is a slice. |
filter |
The filters in the words the list’s header uses; [] for the whole inbox. |
status |
The status’s name; null until kasl has learnt the name for status_id. |
score |
The ranking field’s value, when one is configured. |
source |
Where the issue came from: jira, or the name of the plugin that listed it - see Sources. |
ruled_by |
The rule behind the pin or the dismissal; null when a hand set it, or nothing did. |
badge |
What the CHANGE column shows: NEW, taken, status→In Progress, zzz Mar 4, gone. |
left |
Why a gone issue left: resolved or moved; null while it is present, or not yet known. |
resolution |
The resolution Jira gave, when it left resolved. |
rigger reads the inbox this way, beside its own tasks:
kasl inbox list --all --snoozed --jsonkasl inbox show KEY --json prints {"issue": {...}, "why": null}; with
--why, why lists what the table under --why says, one object per line:
{ "issue": { "key": "PROJ-431", "...": "..." }, "why": [ { "what": "score", "value": "8.5", "because": "read from the ranking field in Jira; the list is ordered by it, highest first" }, { "what": "pinned", "value": "yes", "because": "pinned by hand; pinned issues lead the list whatever the order" } ]}kasl inbox rules --json - the table, the skipped rules and, with --apply,
what changed (applied is null on a dry run):
{ "rules": [ { "number": 1, "name": "on fire", "when": "priority High+", "then": "pin, urgent toast", "matches": "1 match, 1 not yet pinned", "waiting": 1 } ], "skipped": [ { "number": 2, "reason": "unknown field `prority`, expected one of `priority`, `min_score`, `status`, `type`, `project`" } ], "applied": null}Related commands
Section titled “Related commands”watch- the daemon that polls the inboxtask- wheretakeputs the issuesetup- configuring the Jira connection- Configuration - the
inboxblock in full