Git Product home page Git Product logo

op.nvim's Introduction

op.nvim

Neovim version 1Password CLI V2 GitHub license

PrerequisitesInstallConfigurationCommandsFeaturesAPI

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!

Screenshots and Gifs (click to expand)

Secure Notes Editor

Secure Notes Editor

1Password Sidebar

1Password Sidebar

Item Creation

Item creation

Secret Detection Diagnostics

Secret Detection diagnostics

More screenshots and demo gifs in the Wiki!

Featured on the 1Password Blog!

Prerequisites

Required:

Optional, but recommended:

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,
  -- command to use for opening URLs,
  -- can be a function or a string
  url_open_command = function()
    if vim.fn.has('mac') == 1 then
      return 'open'
    elseif vim.fn.has('unix') == 1 then
      return 'xdg-open'
    end
    return nil
  end,
  -- 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. Accepts new or create as an argument to create a new Secure Note.
  • :OpSidebar * - Toggle the 1Password sidebar open/closed. Accepts refresh 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 text
  • OpSidebarItem - the text for items under a section header
  • OpSidebarFavoriteIcon - the star icon used for the 'Favorites' section header
  • OpSidebarIconDefault - 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.

statusline when not signed in

statusline when signed in

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.

op.nvim's People

Contributors

aaronkollasch avatar jamesiain avatar mrjones2014 avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar

op.nvim's Issues

Automatic secret detection in buffers

We can asynchronously detect secret strings (based on our patterns) in the buffer, and present these as diagnostics (and possibly code actions) to Neovim.

It should produce a warning diagnostic that you might have a secret/token/password in your source code

Modularize CLI bindings

The CLI bindings can be their own module, with a configurable backend.

It can be a separate project, pulled in via git submodule or something using luarocks into this repo.

This repo would set up the Go plugin backend as the CLI backend.

Documentation

Users need some pre-req guidance to keep the install/user-experience positive. I think this could be accomplished by providing some detailed prerequisites (because I'm stupid sometimes, and I'm always forgetting about ASS-U-MEs).

Because I don't have a full handle (yet) on the capabilities of this plugin, rather than do a PR thing, I'll just throw some rough notes into the ring to edge the process forward.

Not at all complete or polished, but should be enough to give you a sense of where I'm coming from

screen1

screen2

op-nvim.md

Nice start, Matt. This one's already a keeper for me.

Clarify error message when `op` CLI is not installed

If op is not installed or not on path, the error states that the op.nvim binary is not installed -- this isn't always accurate.

Looking at the code, I'm not sure why this would be the case, but you get the same error if the op.nvim binary is installed, but the op CLI itself is not installed on the system.

Automate updating manifest

This bit in op.vim

call remote#host#RegisterPlugin('op-nvim', '0', [
\ {'type': 'function', 'name': 'OpCmd', 'sync': 1, 'opts': {}},
\ {'type': 'function', 'name': 'OpDesignateField', 'sync': 1, 'opts': {}},
\ {'type': 'function', 'name': 'OpEnableStatusline', 'sync': 0, 'opts': {}},
\ {'type': 'function', 'name': 'OpSetup', 'sync': 1, 'opts': {}},
\ ])

is generated by running the Go binary like so: ./bin/op-nvim --manifest op-nvim

We should automate updating that when the interface changes.

Add support for non-biometric unlock

I don't think this plugin will work currently without biometric unlock enabled for the CLI because the CLI will ask for a password.

In that case we would need to have some sort of password input (maybe via vim.ui.input() but ideally it would be a concealed field, so maybe we can do some trickery with a custom input, saving the entered characters to a Lua string internally, but rendering the chars as ******, and then pipe it to stdin of the CLI process somehow.

Can we handle `op signin`?

If op signin accepts --account to make the signin non-interactive, we could handle account switching too.

Add a health check

:h checkhealth.

Basically, add a require('op.health').check() module+function that uses the APIs from :h health-functions to report status of the plugin.

Things we should check:

  • Is the binary installed?
  • Are we able to connect with the desktop app?
  • Is token auth being used?

Figure out how to do item creation

one idea would be to do a kind of session manager. we'd need separate commands like:

  • :OpNew -- start session
  • :OpAdd -- add visual selection to field list
  • :OpNewDone -- prompt for item title and field names, and create the item

open and fill?

I wonder if there's a way to have the CLI trigger an Open & Fill, or if there's a onepassword:// URL we can use to trigger the desktop app to trigger an Open & Fill

rewrite `op.vim` in Lua

The thing I'm not sure how to do in Lua is to properly get the path to the op-nvim binary generated from the Go code. In Vimscript you can do:

let s:path = expand('<sfile>:p:h')
let s:bin_path = s:path . '/../bin/op-nvim'

But from my experimenting, the same didn't seem to work from Lua for some reason.

Add test for field designation

The field designation Go file uses regex.MustCompile -- we should have a simple test that runs it and makes sure the regex actually all compile.

Add icons to item select

When selecting items we should add NerdFont icons (but icons should be able to be turned off for the (few) users who won’t use a NerdFont).

Lua help wanted: make commands async using coroutines

I'm not super familiar with Lua coroutines so this will probably be a lot of work, but I'm thinking some of this stuff can be made async using coroutines. On the Go side it's extremely simple and trivial to make stuff async using Goroutines, but we'd need to use Lua coroutines on the Lua side.

Basically I'm thinking like:

Lua makes a request to Go, and suspends coroutine execution => Go does some async stuff, then calls a Lua function => the Lua function called by Go resumes the original Lua coroutine

1Password secure notes editor

We should be able to load secure notes items into a buffer, set vim.bo.filetype = 'markdown' (since I think 1Password secure notes uses markdown), set the vim.bo.buftype = 'nofile', and edit 1Password secure notes in Neovim.

As for updating, there's one of two approaches we can do, the first one needs some research:

  • Write the buffer to /dev/null and set up BufWritePre autocmds to update the secure note in 1Password (important: would need to validate whether writing to /dev/null compromises the security of the note in some way)
  • Just use a separate command to update the secure note in 1Password, like :OpWrite or :OpSaveNote` or something like that

This could actually be a really awesome way to use 1Password secure notes. If I can get it working well enough/seamlessly enough it might even replace Notion for me.

1Password Sidebar

Would be cool to have a sidebar where you could pin items (or I wonder if the CLI can get “favorites” items from 1Password) and have different actions on <CR> keypress.

For login items, it would open and fill, for Secure Notes, it would open the note.

Each item type could use a different NerdFont icon. Potentially mapping popular URLs like GitHub to the GitHub NerdFont logo icon for GitHub login, for example.

this would all be static/cached, just a list of titles with UUID and icon, and open either in a sidebar or a select (maybe configurable).

Cannot connect to desktop app

Whenever I try to run :OpSignin, or any other :Op* command, I get the following error:

[ERROR] 2023/02/08 17:44:40 connecting to desktop app: read: connection reset, make sure the CLI is correctly installed 
and Connect with 1Password CLI is enabled in the 1Password app   

Things I've tried so far:

  • Double and triple checked requirements and installation instructions for op.nvim and onepassword-cli.
  • Tested the op command works fine outside of nvim. It works fine.
  • Tested with biometric lock enabled, as well as token-based sessions.
  • Uninstall and reinstall op cli
  • Specifying op_cli_path in config

So far, nothing has helped. I am on Arch Linux running neovim v0.8.3. Not sure if it matters, but I am in a wayland desktop environment.

Thanks in advance!

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.