A Neovim plugin for logging time to Jira tasks directly from your editor. Track time spent on issues and automatically sync with Jira using the Atlassian API.
- ⏱️ Built-in Timer: Track time spent on tasks with a running timer
- 🔄 Git Integration: Automatically detect Jira issue key from git branch names
- 🔐 OAuth 2.0 Authentication: Secure authentication with Atlassian
- 📊 Statusline Integration: Display current issue and timer in lualine or custom statuslines
- 💾 Persistent State: Timer state survives Neovim restarts
- 🎯 Smart Issue Selection: Auto-detect from branch or select from your assigned issues
- 📝 View Worklogs: See all logged time for any issue
- Neovim >= 0.8.0
- plenary.nvim (required)
- lualine.nvim (optional, for statusline integration)
- Git (for branch detection)
- Atlassian Jira account with OAuth 2.0 app credentials
Using lazy.nvim
{
'ZGltYQ/jira-time.nvim',
dependencies = {
'nvim-lua/plenary.nvim',
},
config = function()
require('jira-time').setup({
oauth = {
client_id = 'your-oauth-client-id',
client_secret = 'your-oauth-client-secret',
},
})
end,
}Using packer.nvim
use {
'ZGltYQ/jira-time.nvim',
requires = { 'nvim-lua/plenary.nvim' },
config = function()
require('jira-time').setup({
oauth = {
client_id = 'your-oauth-client-id',
client_secret = 'your-oauth-client-secret',
},
})
end,
}- Go to Atlassian Developer Console
- Click Create → OAuth 2.0 integration
- Fill in the app details:
- App name: Choose any name (e.g., "Neovim Jira Timer")
- Click Create
- In the app settings:
- Click Permissions → Add → Jira API
- Add Callback URL:
http://localhost:8080/callback - Add required Scopes:
read:jira-work- Read Jira issues and worklogswrite:jira-work- Create and update worklogsread:jira-user- Read user informationoffline_access- Refresh tokens
- Click Settings and copy your Client ID and Client Secret
- Add them to your Neovim configuration (see Configuration section below)
Full configuration example:
require('jira-time').setup({
-- Required: OAuth 2.0 credentials from Atlassian Developer Console
oauth = {
client_id = 'your-oauth-client-id',
client_secret = 'your-oauth-client-secret',
-- Optional: Defaults shown below
redirect_uri = 'http://localhost:8080/callback',
scopes = {
'read:jira-work',
'write:jira-work',
'read:jira-user',
'offline_access',
},
},
-- Timer configuration
timer = {
auto_save_interval = 60, -- Save timer state every 60 seconds
format = '%H:%M:%S', -- Time format
auto_start_on_branch_change = false,
},
-- Statusline configuration
statusline = {
enabled = true,
mode = 'standalone', -- 'standalone', 'lualine', or 'custom'
position = 'right', -- Position: 'left', 'center', or 'right' (standalone mode only)
format = '[%s] ⏱ %s', -- Format: [ISSUE-KEY] ⏱ HH:MM:SS
show_when_inactive = false, -- Show even when timer is paused
},
-- Git branch patterns to extract Jira issue keys
branch_patterns = {
'([A-Z]+%-[0-9]+)', -- PROJ-123 anywhere
'feature/([A-Z]+%-[0-9]+)', -- feature/PROJ-123-description
'bugfix/([A-Z]+%-[0-9]+)', -- bugfix/PROJ-123-description
'hotfix/([A-Z]+%-[0-9]+)', -- hotfix/PROJ-123-description
},
-- UI configuration
ui = {
border = 'rounded',
confirm_before_logging = true,
},
-- Keymaps configuration
keymaps = {
enabled = true, -- Enable default keymaps
prefix = '<leader>j', -- Prefix for all jira-time keymaps
start = 's', -- <leader>js - Start timer
stop = 'x', -- <leader>jx - Stop timer
log = 'l', -- <leader>jl - Log time
select = 'i', -- <leader>ji - Select issue
view = 'v', -- <leader>jv - View worklogs
status = 't', -- <leader>jt - Show status
},
})Default keymaps are automatically configured with the prefix <leader>j:
| Keymap | Command | Description |
|---|---|---|
<leader>js |
:JiraTimeStart |
Start timer (auto-detect from branch or select issue) |
<leader>jx |
:JiraTimeStop |
Stop the running timer |
<leader>jl |
:JiraTimeLog |
Log time to Jira |
<leader>ji |
:JiraTimeSelect |
Select a different issue |
<leader>jv |
:JiraTimeView |
View worklogs for current issue |
<leader>jt |
:JiraTimeStatus |
Show plugin status |
You can customize or disable keymaps in your configuration:
require('jira-time').setup({
keymaps = {
enabled = true, -- Set to false to disable all keymaps
prefix = '<leader>j', -- Change the prefix
start = 's', -- Change individual keys
stop = 'x',
log = 'l',
select = 'i',
view = 'v',
status = 't',
},
})To disable a specific keymap, set it to false:
require('jira-time').setup({
keymaps = {
start = false, -- Disable <leader>js
-- Other keymaps remain enabled
},
})First, authenticate with Jira:
:JiraAuthThis will:
- Open your browser for OAuth authentication
- Ask you to authorize the app in Atlassian
- Automatically redirect to
http://localhost:8080/callback - Automatically discover your Jira Cloud ID
- Save your authentication tokens for future use
Note: Your Jira site URL (e.g., yourcompany.atlassian.net) is automatically discovered during authentication - no manual configuration needed!
Start the timer for a Jira issue.
- If
issue-keyis provided, starts timer for that issue - If no argument, tries to detect issue from current git branch
- If detection fails, shows a list of your assigned issues to select from
:JiraTimeStart " Auto-detect from branch or select
:JiraTimeStart PROJ-123 " Start timer for specific issueStop the running timer.
:JiraTimeStopLog tracked time to Jira.
- If
durationis provided, logs that amount - If no argument, logs the current timer's elapsed time
- Prompts for an optional comment/description
:JiraTimeLog " Log current timer elapsed time
:JiraTimeLog 2h 30m " Log specific duration
:JiraTimeLog 150m " Log 150 minutes
:JiraTimeLog 1h " Log 1 hourDuration formats:
2h 30m- 2 hours and 30 minutes150m- 150 minutes1h- 1 hour45m- 45 minutes150- 150 minutes (default unit)
View worklogs for an issue.
- If
issue-keyis provided, shows worklogs for that issue - If no argument, shows worklogs for current timer issue
:JiraTimeView " View worklogs for current issue
:JiraTimeView PROJ-123 " View worklogs for specific issueManually select a Jira issue from your assigned issues.
:JiraTimeSelectShow plugin status (useful for debugging).
:JiraTimeStatusThe plugin displays a live timer at the bottom of Neovim showing:
- Current Jira issue key (e.g.,
PROJ-123) - Elapsed time (e.g.,
01:23:45) - Running/paused indicator (⏱/⏸)
Example: ⏱ [PROJ-123] ⏱ 01:23:45
No configuration needed! The timer automatically appears in your statusline when you start tracking:
require('jira-time').setup({
oauth = {
client_id = 'your-oauth-client-id',
client_secret = 'your-oauth-client-secret',
},
-- Statusline is enabled by default in standalone mode
})The timer will automatically show at the bottom-right of Neovim when you run :JiraTimeStart.
Customize the position:
require('jira-time').setup({
statusline = {
enabled = true,
mode = 'standalone', -- Default mode
position = 'right', -- 'left', 'center', or 'right'
format = '[%s] ⏱ %s', -- [ISSUE-KEY] ⏱ HH:MM:SS
show_when_inactive = false, -- Show even when timer is paused
},
})If you use lualine, switch to lualine mode:
require('jira-time').setup({
statusline = {
mode = 'lualine',
},
})
-- Then add to your lualine config:
require('lualine').setup({
sections = {
lualine_x = {
require('jira-time.statusline').lualine_component(),
'encoding',
'fileformat',
'filetype',
},
},
})For AstroNvim or custom heirline setups, create ~/.config/nvim/lua/plugins/heirline.lua:
-- First, configure jira-time to use custom mode
require('jira-time').setup({
statusline = {
mode = 'custom',
},
})Then add the heirline integration:
-- ~/.config/nvim/lua/plugins/heirline.lua
return {
"rebelot/heirline.nvim",
opts = function(_, opts)
local status = require("astroui.status")
-- Create jira-time component
local jira_component = status.component.builder({
{
provider = function()
local ok, jira = pcall(require, 'jira-time.statusline')
if ok then
local jira_status = jira.get_status()
if jira_status ~= '' then
return ' ' .. jira_status .. ' '
end
end
return ""
end,
hl = status.hl.get_attributes("git_branch", true),
on_click = {
callback = function()
vim.cmd("JiraTimeStatus")
end,
name = "jira_time_click",
},
},
})
-- Insert component into statusline
table.insert(opts.statusline, 9, jira_component)
return opts
end,
}For other statusline plugins (feline, galaxyline, etc.):
require('jira-time').setup({
statusline = {
mode = 'custom', -- Disable automatic integration
},
})
-- Then use in your statusline config:
local jira_status = require('jira-time.statusline').get_component()
statusline = statusline .. jira_status()-
Start working on a task:
git checkout -b feature/PROJ-123-add-new-feature
-
Start timer in Neovim:
:JiraTimeStart " Automatically detects PROJ-123 from branch -
Work on your code (timer runs in background, visible in statusline)
-
Stop timer when taking a break:
:JiraTimeStop
-
Resume timer:
:JiraTimeStart " Resumes with saved time -
Log time to Jira when done:
:JiraTimeLog " Logs accumulated time with optional comment
The plugin can automatically extract Jira issue keys from your git branch names. Common patterns supported by default:
PROJ-123feature/PROJ-123-descriptionbugfix/PROJ-123-descriptionhotfix/PROJ-123-description
You can customize patterns in the configuration.
Run :JiraAuth to authenticate with Jira.
Check your OAuth configuration and ensure the scopes include read:jira-work.
Check that the data directory is writable:
:echo stdpath('data') .. '/jira-time'- Verify your branch name contains a Jira issue key
- Check the
branch_patternsconfiguration - Use
:JiraTimeStart PROJ-123to manually specify the issue
Plugin data is stored in:
- Auth tokens:
~/.local/share/nvim/jira-time/auth.json - Timer state:
~/.local/share/nvim/jira-time/timer.json
Contributions are welcome! Please feel free to submit a Pull Request.
MIT License - see LICENSE file for details
Built with:
- plenary.nvim for HTTP requests
- Atlassian Jira REST API
- OAuth 2.0 (3LO) authentication