Awesome
<div align="center">op.nvim
<!-- panvimdoc-ignore-start -->Prerequisites • Install • Configuration • Commands • Features • API
<!-- panvimdoc-ignore-end --> </div>1Password for Neovim! Create items using strings from the current buffer as fields,
and insert item reference URIs (e.g. op://vault-name/item-name/field-name
)
directly from Neovim. Edit Secure Notes directly in Neovim. Works with biometric unlock!
Secure Notes Editor
1Password Sidebar
Item Creation
Secret Detection Diagnostics
</details> <!-- panvimdoc-ignore-end -->More screenshots and demo gifs in the Wiki!
Featured on the 1Password Blog!
Prerequisites
Required:
- 1Password CLI v2 installed
Optional, but recommended:
- 1Password 8 desktop app (required to use biometric unlock for CLI)
- Biometric unlock for CLI enabled (see Using Token-Based Sessions if you do not use biometric unlock for CLI)
- A Neovim plugin to handle
vim.ui.select()
andvim.ui.input()
— I recommend telescope.nvim paired with dressing.nvim
Windows Support
This plugin does not currently support Windows. I don't use Windows so I can't test on Windows. However, I would happily accept Pull Requests adding Windows support, with a commitment to ongoing maintenance from the PR author.
Install
This project uses git tags to adhere to Semantic Versioning.
packer.nvim
-- if you want to update without pinning to a version
use({ 'mrjones2014/op.nvim', run = 'make install' })
-- if you'd like to use a specific version
use({ 'mrjones2014/op.nvim', run = 'make install', tag = 'v1.0.0' })
vim-plug
" if you want to update without pinning to a version
Plug 'mrjones2014/op.nvim', { 'do': 'make install' }
" if you'd like to use a specific version
Plug 'mrjones2014/op.nvim', { 'do': 'make install', 'tag': 'v1.0.0' }
No other setup is required if using biometric unlock for the 1Password CLI, however there are a few settings you can change if needed. See Configuration.
Configuration
Configuration can be set by calling require('op').setup(config_table)
.
The require('op').setup()
function is idempotent (i.e. can be called multiple times without side effects).
require('op').setup({
-- you can change this to a full path if `op`
-- is not on your $PATH
op_cli_path = 'op',
-- Whether to sign in on start.
signin_on_start = false,
-- show NerdFont icons in `vim.ui.select()` interfaces,
-- set to false if you do not use a NerdFont or just
-- don't want icons
use_icons = true,
-- settings for op.nvim sidebar
sidebar = {
-- sections to include in the sidebar
sections = {
favorites = true,
secure_notes = true,
},
-- sidebar width
width = 40,
-- put the sidebar on the right or left side
side = 'right',
-- keymappings for the sidebar buffer.
-- can be a string mapping to a function from
-- the module `op.sidebar.actions`,
-- an editor command string, or a function.
-- if you supply a function, a table with the following
-- fields will be passed as an argument:
-- {
-- title: string,
-- icon: string,
-- type: 'header' | 'item'
-- -- data will be nil if type == 'header'
-- data: nil | {
-- uuid: string,
-- vault_uuid: string,
-- category: string,
-- url: string
-- }
-- }
mappings = {
-- if it's a Secure Note, open in op.nvim's Secure Notes editor;
-- if it's an item with a URL, open & fill the item in default browser;
-- otherwise, open in 1Password 8 desktop app
['<CR>'] = 'default_open',
-- open in 1Password 8 desktop app
['go'] = 'open_in_desktop_app',
-- edit in 1Password 8 desktop app
['ge'] = 'edit_in_desktop_app',
},
},
-- Custom formatter function for statusline component
statusline_fmt = function(account_name)
if not account_name or #account_name == 0 then
return ' 1Password: No active session'
end
return string.format(' 1Password: %s', account_name)
end
-- global_args accepts any arguments
-- listed under "Global Flags" in
-- `op --help` output.
global_args = {
-- use the item cache
'--cache',
-- print output with no color, since we
-- aren't viewing the output directly anyway
'--no-color',
},
-- Use biometric unlock by default,
-- set this to false and also see
-- "Using Token-Based Sessions" section
-- of README.md if you don't use biometric
-- unlock for CLI.
biometric_unlock = true,
-- settings for Secure Notes editor
secure_notes = {
-- prefix for buffer names when
-- editing 1Password Secure Notes
buf_name_prefix = '1P:',
}
-- configuration for automatic secret detection
-- it can also be triggered manually with `:OpAnalyzeBuffer`
secret_detection_diagnostics = {
-- disable the feature if set to true
disabled = false,
-- severity of produced diagnostics
severity = vim.diagnostic.severity.WARN,
-- disable on files longer than this
max_file_lines = 10000,
-- disable on these filetypes
disabled_filetypes = {
'nofile',
'TelescopePrompt',
'NvimTree',
'Trouble',
'1PasswordSidebar',
},
}
})
Using Token-Based Sessions
If you do not use biometric unlock for the 1Password CLI, you can use token-based sessions.
You must run eval $(op signin)
before launching Neovim in order for op.nvim
to be
able to access the session. You also must configure op.nvim
with biometric_unlock = false
.
Commands
* = Asynchronous
† = Partially asynchronous
:OpSignin
* - Choose a 1Password account to sign in with. Accepts account shorthand, signin address, account UUID, or user UUID as an optional argument.:OpSignout
* - End your current 1Password CLI session.:OpWhoami
* - Check which 1Password account your current CLI session is using.:OpCreate
† - Create a new item using strings in the current buffer as fields.:OpView
† - Open an item in the 1Password 8 desktop app.:OpEdit
† - Open an item to the edit view in the 1Password 8 desktop app.:OpOpen
- Select an item to open & fill in your default browser:OpInsert
- Insert an item reference at current cursor position.:OpNote
- Find and open a 1Password Secure Note item. Acceptsnew
orcreate
as an argument to create a new Secure Note.:OpSidebar
* - Toggle the 1Password sidebar open/closed. Acceptsrefresh
as an argument to reload items.:OpAnalyzeBuffer
* - Run secret detection diagnostics on current buffer manually.
All commands are also available as a Lua API, see API. Additionally there are two utility methods for grabbing secrets to use in scripting:
require('op').get_secret(item_name: string, field_name: string): string|nil, string|nil
require('op').get_secret_async(item_name: string, field_name: string, callback: fun(secret: string|nil, stderr: string|nil))
Features
- Biometric unlock! Unlock 1Password with fingerprint or Apple watch from within Neovim
- Create items from strings in the current buffer
- If the Treesitter query fails or there's no Treesitter parser for the current filetype, fallback to manual value input (if a Treesitter parser exists, please open an issue or PR so we can get the right query added!)
- Infer default field and item names based on field value patterns
- Open an item in the 1Password 8 desktop app
- Insert an item reference URI (e.g.
op://vault-name/item-name/field-name
) - Switch between multiple 1Password accounts (only works with biometric unlock enabled)
- Select an item to open & fill in your default browser
- Secure Notes Editor (See Secure Notes Editor)
- Automatically detect hard-coded secrets in buffers and produce diagnostics
- Statusline component that updates asynchronously (See Statusline)
- Most commands are partially or fully asynchronous
Secure Notes Editor
Edit your 1Password Secure Notes items directly in Neovim! Run :OpNote
to find a Secure Note item, or :OpNote new
/:OpNote create
to create a new one, and open it in a new buffer. The buffer will have filetype=markdown
so you get Markdown filetype highlighting,
and will append .md
in the buffer name — this is just so that nvim-web-devicons
will assign the Markdown icon to the buffer, e.g. if you're using bufferline.nvim
or similar. It will not change the title of your Secure Note in 1Password.
Running :w
will update the Secure Note in 1Password, and :e
will sync the current Secure Note from 1Password into the buffer.
Notes Editor Security
The Secure Notes editor will never write your notes to disk. It uses a special buftype
option, buftype=acwrite
,
which allows op.nvim
to intercept the :w
and :e
commands by setting up an autocmd BufWriteCmd
and autocmd BufReadCmd
,
respectively, which then allows op.nvim
to completely handle "writing" and "reading" the Secure Note by updating it via the 1Password CLI.
Note that in order to write the contents back to the correct item, op.nvim
associates buffer IDs with { uuid, vault_uuid }
pairs.
op.nvim
does not store the note title or anything other than the UUID and vault UUID in the edit session.
Sidebar
op.nvim
can show a sidebar listing your favorites, Secure Notes, or both. Keymappings can be added to open, view, etc. the items
in the sidebar. See screenshot in the Wiki.
Highlighting Groups
For colorscheme authors, you can use the following highlighting group names:
OpSidebarHeader
- the section header textOpSidebarItem
- the text for items under a section headerOpSidebarFavoriteIcon
- the star icon used for the 'Favorites' section headerOpSidebarIconDefault
- all other icons in the sidebar (e.g. item category icons)
Sidebar Security
In order to implement key mappings on the sidebar, the item's title, ID, vault ID, category, and primary URL are stored in memory
to render the sidebar. No other data is stored, and this data is stored internally to the plugin and never exported. Other Lua
code should not be able to access this data. However, this data is passed to functions which are setup as sidebar
keymappings (see sidebar.mappings
section under Configuration).
Statusline
op.nvim
provides a statusline component as a function that returns a string.
The statusline component updates asynchronously using goroutines,
and will either show "1Password: No active session" when you do not have an active 1Password CLI
session, or "1Password: Account Name" after you've started a session.
See screenshots below.
<!-- panvimdoc-ignore-end -->API
All commands are also available as a Lua API as described below:
require('op').op_signin(account_identifier: string | nil)
require('op').signout()
require('op').op_whoami()
require('op').op_create()
require('op').op_view()
require('op').op_edit()
require('op').op_open()
require('op').op_insert()
require('op').op_note(create_new: boolean)
require('op').op_sidebar(should_refresh: boolean)
require('op').op_analyze_buffer()
Additionally there are two utility methods for grabbing secrets to use in scripting:
require('op').get_secret(item_name: string, field_name: string): string|nil, string|nil
require('op').get_secret_async(item_name: string, field_name: string, callback: fun(secret: string|nil, stderr: string|nil))
Additionally, part of op.nvim
's design includes complete bindings to the CLI that you can use for scripting with Lua. This API
is available in the op.api
module. This module returns a table that matches the hierarchy of the 1Password CLI commands.
The only exception is that op events-api
is reformatted as op.eventsApi
, for obvious reasons. Each command is accessed
as a function that takes the command flags and arguments as a list. The functions all return three values, which are
the STDOUT
as a list of lines, STDERR
as a list of lines, and the exit code as a number.
Some examples are below:
local op = require('op.api')
local stdout, stderr, exit_code = op.signin()
local stdout, stderr, exit_code = op.account.get({ '--format', 'json' })
local stdout, stderr, exit_code = op.item.list({ '--format', 'json' })
local stdout, stderr, exit_code =
op.eventsApi.create({ 'SigninEvents', '--features', 'signinattempts', '--expires-in', '1h' })
local stdout, stderr, exit_code = op.connect.server.create({ 'Production', '--vaults', 'Production' })
-- all API functions can be called asynchronously by setting `args.async = true`
-- and passing a callback as a second parameter
op.account.get({ async = true, '--format', 'json' }, function(stdout, stderr, exit_code)
-- do stuff with stdout, stderr, exit_code
end)
If you implement a cool feature using the API, please consider contributing it to this plugin in a PR!
See lua/op/types.lua for type annotations describing the require('op.api')
table. This file
should also provide type information and completions when using lua-language-server
.