Utility scripts for the CLI?

I would like some utility shell scripts (preferably in bash or zsh) for using the 1Password CLI to perform the following functions:
1. Create a new secure note.
2. Delete a secure note (completely delete it, don't leave it in the Trash).
3. Update the contents of a secure note with new data.
4. Rename a secure note (the note under the original name should be deleted).
5. Copy a secure note to another secure note with a different name (the note under the original name should remain).

I'm willing for this to be done with documents instead of a secure notes, if that turns out to be easier.

I believe that all of these can be accomplished via a combination of multiple CLI calls, "jq" calls, and scripting logic. I'm willing to write these, but before I start to "reinvent the wheel", I'm wondering if anyone has already created scripts that perform any of these functions.

Thank you in advance.


1Password Version: Not Provided
Extension Version: Not Provided
OS Version: Not Provided
Sync Type: Not Provided

Comments

  • OOPS! I forgot that there's no way to empty the Trash folder using the CLI, so some of these are currently impossible. I think #1 and #5 can be performed, however ... and maybe #4.

  • So ... please, please, please (!!!) could you folks implement a way to empty the Trash via the CLI?

    If that is done, then I think I could write these 5 scripts, and I'll post them here. This will give CLI users a number of added capabilities, without the 1Password programmers having to do any work to develop anything else (besides the CLI Trash-emptying capability).

  • rickfillionrickfillion Junior Member

    Team Member

    Hi @HippoMan,

    Unfortunately only 1 seems to be possible. 2, 4, 5 would require empty trash. 3 would require the ability to update an existing item which doesn't yet exist in the tool.

    I'm very much looking forward to us spending the time to do item management properly.

    Rick

  • HippoManHippoMan
    edited May 23

    If we could empty the Trash via the CLI, then #3 would also be possible, as follows ...
    Step 1: Query the uuid of the original item.
    Step 2: Create a new item with the same name as the original item, containing the updated data.
    Step 3: If step 2 succeeds, move the original item to the Trash using its uuid.
    Step 4: Empty the Trash.

    So when the 1Password staff works on enhancing the item-management capabilities, I request that a CLI empty-the-Trash function be the first CLI enhancement that you implement. That way, I could write all 5 scripts and give people all those capabilities while the rest of the CLI enhancement work progresses over time.

    Alternatively, I'm willing to sign a non-disclosure agreement and write the CLI empty-the-Trash function myself, and give it to you folks.

  • rickfillionrickfillion Junior Member

    Team Member

    That makes sense. I do think it's a little sad to do trash/delete/create-new as a poor man's edit, but you're right that it'd work.

    Have you considered throwing your Hippo-Hat into ring here? https://discussions.agilebits.com/discussion/88757/help-shape-the-future-of-the-1password-command-line-tool :)

    Rick

  • Yes, I read that, and I already sent an email about this to ROT13([email protected]) a little more than a week ago.

  • HippoManHippoMan
    edited May 23

    Actually, I just now think I figured out how to write all these scripts using the currently-implemented CLI ...

    After an item is created via op create document xxx --vault=yyy, it shows up under op list items. Then, after op delete item UUID is invoked using that item's uuid, the item no longer shows up under op list items, even though it still is returned via op get item UUID and op get item NAME.

    So, I can write an item_exists function which simply does an op list items and looks to see if there is an item with the given uuid among the returned results. All the scripts would use this function to determine whether or not there already is an existing item, and this means that after doing an op delete item ..., that item will appear to be non-existent to the scripts.

    This way, the scripts would work with the same view as the vault view of the 1Password web page. This item_exists methodology is hacky and inefficient, but I think it will work.

    Does anyone see any problems with this approach? If not, I'm going to start on this script work in my spare time.

    Excelsior! (Latin expression for "Onward and Upward!")

  • Here's my initial, quick-and-dirty attempt for the item_exists function. It's a shell script that is meant to be called within other scripts to let them know whether a given item exists outside of the Trash. It either returns 0 or something non-zero, depending upon whether or not the specified item exists. Comments? ...

    #!/bin/zsh -f
    
    prog=${0##*/}
    op=/usr/local/bin/op
    jq=/usr/bin/jq
    tmp0=/tmp/.${prog}.0.$$
    tmp1=/tmp/.${prog}.1.$$
    
    quit() {
      /bin/rm -f ${tmp0} ${tmp1}
      exit ${1}
    }
    
    trap 'quit 1' 1 2 3 15
    
    [[ $# -lt 1 ]] && {
      print - "
    usage: ${prog} item-uuid [ optional 'op list items' arguments ]
    
      returns 0 if the item exists, otherwise non-zero
    
      optional arguments could contain '--vault=XXX', for example
    "
      exit 1
    }
    
    uuid="${1}"
    shift
    
    filter=".[] | select(.uuid == \"${uuid}\") | .uuid"
    
    # Use temp files so that we can catch the `op` return code
    # before passing any results to `jq`, and likewise for
    # catching the `jq` return code before checking its results.
    
    ${op} list items "${@}" >${tmp0} || quit 1
    
    ${jq} -M "${filter}" <${tmp0} >${tmp1} || quit 1
    
    if [[ -s "${tmp1}" ]]
    then
      quit 0
    else
      quit 1
    fi
    
  • HippoManHippoMan
    edited May 23

    ... and assuming the above script is named op-uuid-exists, the following script accepts either a uuid or an item name on the command line, and it determines whether there is one and only one item with that name or uuid existing outside of the Trash. Its return code is 0 if there is indeed only one single non-Trash match. If there are no non-Trash matches, its return code is -1, and if there are multiple non-Trash matches, the return code is the number of matches (2 or greater). Comments? ...

    #!/bin/zsh -f
    
    prog=${0##*/}
    op=/usr/local/bin/op
    jq=/usr/bin/jq
    tmp0=/tmp/.${prog}.0.$$
    tmp1=/tmp/.${prog}.1.$$
    
    quit() {
      /bin/rm -f ${tmp0} ${tmp1}
      exit ${1}
    }
    
    trap 'quit 1' 1 2 3 15
    
    [[ $# -lt 1 ]] && {
      print - "
    usage: ${prog} item [ optional 'op get item' arguments ]
    
      returns 0 if the item exists, otherwise non-zero
    
      optional arguments could contain '--vault=XXX', for example
    "
      quit -2
    }
    
    item="${1}"
    shift
    
    # Use temp files so that we can catch the `op` return code
    # before passing any results to `jq`, and likewise for
    # catching the `jq` return code before checking its results.
    
    ${op} get item "${item}" "${@}" >${tmp0} || quit 1
    [[ -s "${tmp0}" ]] || quit 1
    
    filter='.uuid'
    ${jq} -M --raw-output "${filter}" <${tmp0} >${tmp1} || quit 1
    
    found=0
    while read uuid
    do
      /usr/local/bin/op-uuid-exists "${uuid}" || continue
      (( found = found + 1 ))
    done <${tmp1}
    
    case "${found}" in
    (1)
      quit 0
      ;;
    (0)
      quit -1
      ;;
    (*)
      print - "${found} matches for ${item} ${*}"
      quit ${found}
      ;;
    esac
    
  • PS: in the first script, the exit 1 after the usage message should be changed to quit -2. It's too late for me to edit the comment containing the first script.

  • MrCMrC Community Moderator
    edited May 23

    @HippoMan,

    You might try to avoid the temp files altogether. Example:

    if [ ! -z `op list items | jq -M "${filter}"; then
        echo FOUND
    else
        echo NOT FOUND
    fi
    
  • Thank you. Yes, I'll clean up the scripts later and take your suggestion. These are just proofs of concept, at the moment. Once I get these going, I'll probably put them up on github.

  • ... and here's script #1 from my original list of proposed scripts: op-create-document. I'm doing all the scripts for documents first. Once they're all working, I'll then write the corresponding scripts for secure notes (or maybe enhance these scripts to handle both documents and secure notes), because secure notes are a bit more complicated. This script won't create a document if one with the same name already exists outside of the Trash ...

    #!/bin/zsh -f
    
    prog=${0##*/}
    op=/usr/local/bin/op
    jq=/usr/bin/jq
    tmp=/tmp/.${prog}.0.$$
    
    quit() {
      /bin/rm -rf ${tmp}
      exit ${1}
    }
    
    trap 'quit 1' 1 2 3 15
    
    stdin=
    while getopts :iI o
    do
      case "${o}" in
      ([iI])
        stdin=t
        ;;
      (*)
        print -r -- "${0##*/}: invalid option: -${o}"
        exit 1
        ;;
      esac
    done
    
    shift $(( ${OPTIND} - 1 ))
    
    [[ $# -lt 1 ]] && {
      print - "
    usage: ${prog} [ -iI ] item-name [ optional 'op create document' arguments ]
    
      returns 0 if the item exists, otherwise non-zero
    
      option -i or -I means take item contents from stdin
    
      optional arguments could contain '--vault=XXX'
      or '--title=FOOBAR', for example
    "
      quit -2
    }
    
    name="${1}"
    shift
    
    base="$(/usr/bin/basename ${name})"
    
    /usr/local/bin/op-item-exists "${base}" && {
      print "${prog}: item already exists: ${name}"
      quit 1
    }
    
    if [[ -n "${stdin:-}" ]]
    then
      mkdir -p "${tmp}" 1>/dev/null 2>&1 || {
        print - "${prog}: unable to create temp dir"
        quit 1
      }
      path="${tmp}/${base}"
      /bin/cat - >${path} || {
        print - "${prog}: unable to create temp file"
        quit 1
      }
    else
      path="${name}"
    fi
    
    ${op} create document ${path} "${@}"
    
    quit $?
    
  • HippoManHippoMan
    edited May 23

    Here is script #2 (for any items, not just documents): op-delete-item. It's mostly just a wrapper around op delete item, but it also uses op-item-exists to make sure that the item exists outside of the Trash, before it attempts the deletion ...

    #!/bin/zsh -f
    
    prog=${0##*/}
    op=/usr/local/bin/op
    
    [[ $# -lt 1 ]] && {
      print - "
    usage: ${prog} [ -iI ] item-name [ optional 'op delete item' arguments ]
    
      returns 0 if the item is deleted, otherwise non-zero
    
      optional arguments could contain '--vault=XXX'
    "
      exit -2
    }
    
    item="${1}"
    shift
    
    /usr/local/bin/op-item-exists "${item}" || {
      print "${prog}: item not found: ${item}"
      exit 1
    }
    
    ${op} delete item "${item}" "${@}"
    
    exit $?
    
  • Here is script #3 (just for documents): op-update-document. It will only perform an update if the referenced item already exists ...

    #!/bin/zsh -f                  
    
    prog=${0##*/}
    op=/usr/local/bin/op
    jq=/usr/bin/jq
    tmp0=/tmp/.${prog}.0.$$
    tmp1=/tmp/.${prog}.1.$$
    
    quit() {
      /bin/rm -rf ${tmp0} ${tmp1}
      exit ${1}
    }
    
    trap 'quit 1' 1 2 3 15
    
    stdin=
    while getopts :iI o
    do
      case "${o}" in
      ([iI])
        stdin=t
        ;;
      (*)
        print -r -- "${0##*/}: invalid option: -${o}"
        exit 1
        ;;
      esac
    done
    
    shift $(( ${OPTIND} - 1 ))
    
    [[ $# -lt 1 ]] && {
      print - "
    usage: ${prog} [ -iI ] item-name [ optional 'op create document' arguments ]
    
      returns 0 if the item exists and is updated, otherwise non-zero
    
      option -i or -I means take item contents from stdin
    
      optional arguments could contain '--vault=XXX'
      or '--title=FOOBAR', for example
    "
      quit -2
    }
    
    name="${1}"
    shift
    
    base="$(/usr/bin/basename ${name})"
    
    /usr/local/bin/op-item-exists "${base}" >${tmp1} || {
      print "${prog}: item not found: ${name}"
      quit 1
    }
    
    if [[ -n "${stdin:-}" ]]
    then
      mkdir -p "${tmp0}" 1>/dev/null 2>&1 || {
        print - "${prog}: unable to create temp dir"
        quit 1
      }
      path="${tmp0}/${base}"
      /bin/cat - >${path} || {
        print - "${prog}: unable to create temp file"
        quit 1
      }
    else
      path="${name}"
    fi
    
    ${op} create document ${path} "${@}"
    rc=$?
    
    [[ ${rc} == 0 ]] && {
      # tmp1 contains the uuid of the original document
      /usr/local/bin/op-rm-item "$(/bin/cat ${tmp1})"
      rc=$?
    }
    
    quit ${rc}
    
  • rickfillionrickfillion Junior Member

    Team Member

    That approach should work, @HippoMan. Thanks for sharing it. :)

    Regarding your email you sent last week... hrmm... I'm not seeing anything here. It's possible that I'm looking wrong though, as I'm not a pro at our email system. Did you use the same email address as your forum account?

    Rick

  • Yes, my email came from the same address that I use here. However, I'll send it again later today or tomorrow.

    Also, I'm glad that you think my approach will work. I'll have some more scripts within a few days.

  • HippoManHippoMan
    edited May 25

    Note: when you see op-item-exists in the scripts above, it's the code that I posted here: https://discussions.agilebits.com/discussion/comment/432998/#Comment_432998

    This is a slightly enhanced version of op-item-exists ...

    #!/bin/zsh -f
    
    prog=${0##*/}
    op=/usr/local/bin/op
    jq=/usr/bin/jq
    tmp0=/tmp/.${prog}.0.$$
    tmp1=/tmp/.${prog}.1.$$
    
    quit() {
      /bin/rm -f ${tmp0} ${tmp1}
      exit ${1}
    }
    
    trap 'quit 1' 1 2 3 15
    
    [[ $# -lt 1 ]] && {
      print - "
    usage: ${prog} item [ optional 'op get item' arguments ]
    
      returns 0 if the item exists, otherwise non-zero
    
      optional arguments could contain '--vault=XXX', for example
    "
      quit -2
    }
    
    item="${1}"
    shift
    
    # Use temp files so that we can catch the `op` return code
    # before passing any results to `jq`, and likewise for
    # catching the `jq` return code before checking its results.
    
    ${op} get item "${item}" "${@}" >${tmp0} || quit 1
    [[ -s "${tmp0}" ]] || quit 1
    
    filter='.uuid'
    ${jq} -M --raw-output "${filter}" <${tmp0} >${tmp1} || quit 1
    
    # Count the number if items found.
    found=0
    while read uuid
    do
      /usr/local/bin/op-uuid-exists "${uuid}" || continue
      (( found = found + 1 ))
    done <${tmp1}
    
    case "${found}" in
    (1)
      # Found one item. Return its uuid in stdout with a 0 return code
      /bin/cat "${tmp1}"
      quit 0
      ;;
    (0)
      # No items found
      quit -1
      ;;
    (*)
      # Found more than one item.
      # The return code is the number of items.
      print - "${found} matches for ${item} ${*}"
      quit ${found}
      ;;
    esac
    
  • rickfillionrickfillion Junior Member

    Team Member

    That's very cool. I wish our tool made that easier though.

    Rick

Leave a Comment

BoldItalicStrikethroughOrdered listUnordered list
Emoji
Image
Align leftAlign centerAlign rightToggle HTML viewToggle full pageToggle lights
Drop image/file