A PopClip extension for Bike

I’ve created a PopClip extension for Bike.

PopClip shows a row of action buttons when you select text with your pointer in any app. This new extension adds an action button that sends the selected text into a nominated Bike document.

Full disclosure: I am the developer of PopClip. I’m also a Bike user!

I use the PopClip extension to collect ideas/links etc. into a Bike “Inbox”. It’s quite similar to the existing extension for TaskPaper.

It works with Bike 1 and is implemented using Bike’s AppleScript interface. (I’m afraid I haven’t tried Bike 2 yet!)

4 Likes

Thanks for sharing.

I think this is a biggest honor a developer can get … another developer using their todo list instead of building their own!

4 Likes

For your Bike 2 pop-clip extension, you may find that the bundled bike CLI works well – bypassing the need for the target .bike file to be opened.

If we are collecting in ~/Capture.bike for example, under a top-level Input heading, and needed a grandparental post office reminder, then perhaps something like:

bike create row --outline ~/Capture.bike --parent /Input --text "Post nursery rhyme book"

man bike:
bike2CLI.pdf.zip (13.5 KB)

Details of row-refs etc:
extension-kit/docs/session-automation.md at main · bike-outliner/extension-kit


( FWIW in my installations of Bike 2 and popclip here, the Bike 1 applescript version seems to be tripping up on something )

1 Like

Thanks for the very useful information. Much as I love the idea of scripting with AppleScript, it is just so cumbersome to use! That CLI call looks like it’s going to be much more robust.

1 Like

Update – your AppleScript function is working well with Bike 2.

( user error on my part – I had specified the file path with an unexpanded tilde)

1 Like

Good to know, thank you!

Admittedly, having only a plain text box for specifying the file path is not ideal. A file picker, or simply tilde expansion, would have eliminated the error. And PopClip gives no indication of what the actual problem is, just the generic “X”, leaving you to only guess at the issue. Both are areas I would like to improve.

Interestingly enough, the first time I tested my own extension, I put a tilde in the file path too. :man_facepalming:

1 Like

PS @jessegrosjean pointed out something that I had missed in the CLI documentation – that there is a direct match with your position option to add new items at top or bottom of the list.

You can refine the targeting of a --parent switch by following it with a --position {start, end} switch.

So to prepend items at the top of the list (rather than the default append) perhaps something like:

bike create row --outline ~/Capture.bike --parent /Input --position start --text "Something for the top of the list"
1 Like

FWIW a rough Bike 2 (bike CLI) re-draft,

  1. Allowing for filepath ~
  2. using the bike CLI to create the specified .bike file, and/or target row, if either doesn’t yet exist, and
  3. allowing for multiple selected lines, and aiming to preserve any indents.
Expand disclosure triangle to view YAML source
#popclip
name: Bike 2
identifier: com.robtrew.popclip.extension.bike2
description: Capture text to a Bike 2 document using the CLI. Creates the document and target row if they do not exist.
icon: symbol:bicycle
popclip version: 4151
keywords: outliner
app:
  name: Bike
  link: https://www.hogbaysoftware.com/bike/
  bundle identifiers: [com.hogbaysoftware.Bike, com.hogbaysoftware.Bike-setapp]
  check installed: true
options:
  - identifier: document
    label: Document Path
    type: string
    description: Absolute path to the Bike document, such as "~/Documents/Inbox.bike".
  - identifier: row
    label: Parent Row Path
    type: string
    default value: /Inbox
    description: Bike outline path for the parent row (e.g., /Inbox). Leave blank to add directly to the root.
  - identifier: position
    label: Add At
    type: multiple
    values: [start, end]
    value labels: [Top, Bottom]
    default value: start
    description: Where in the list to add new rows.
actions:
  - requirements: [text]
    after: show-status
    interpreter: zsh
    shell script: |
      setopt extended_glob
      
      BIKE="/usr/local/bin/bike"
      DOC_PATH="${POPCLIP_OPTION_DOCUMENT/#\~/$HOME}"
      
      # Spaces, tabs, newlines trimmed from start and end
      TRIMMED_TEXT="${POPCLIP_TEXT##[[:space:]]#}"
      TRIMMED_TEXT="${TRIMMED_TEXT%%[[:space:]]#}"
      
      # Any indents preserved, all non-empty lines have markdown bullets
      BULLETED_TEXT=$(printf "%s" "$TRIMMED_TEXT" | sed -E 's/^([[:blank:]]*)([^[:blank:]-]+)/\1- \2/')
      
      add_rows() {
        printf "%s\n" "$BULLETED_TEXT" | "$BIKE" create rows -f - --outline "$DOC_PATH" "$@"
      }
      
      if [[ ! -f "$DOC_PATH" ]]; then
        "$BIKE" create outline "$DOC_PATH"
      fi
      
      if [[ -n "$POPCLIP_OPTION_ROW" ]]; then
        ROW_EXISTS=$("$BIKE" get outline --refs "$POPCLIP_OPTION_ROW" --outline "$DOC_PATH" --shape flat -o txt)
        
        # The parent heading created if not found
        if [[ -z "$ROW_EXISTS" ]]; then
          ROW_TEXT="${POPCLIP_OPTION_ROW#/}"
          "$BIKE" create row --outline "$DOC_PATH" --text "$ROW_TEXT"
        fi
        
        add_rows --parent "$POPCLIP_OPTION_ROW" --position "$POPCLIP_OPTION_POSITION"
      else
        add_rows --position "$POPCLIP_OPTION_POSITION"
      fi

Updated the draft Bike 2 PopClip extension YAML (above) to allow for multiple selected lines.

(aims to preserve any indentation as outline structure)

1 Like

Popclip repurchased. Had been using it for years, and forget why I stopped. I’ll be making a heavy commitment to Bike, so it is welcome

2 Likes

A variant which experiments with adding a couple more features to the three already listed above:

  1. Allowing for filepath ~
  2. using the bike CLI to create the specified .bike file, and/or target row, if either doesn’t yet exist, and
  3. allowing for multiple selected lines, and aiming to preserve any indents.

4. Adds a date-stamped added badge to the (first) line added to the inbox, and
5. Copies to clipboard a bike:// url link to that line in the inbox.

Expand disclosure triangle to view Popclip YAML source
#popclip
name: Bike 2
identifier: com.robtrew.popclip.extension.bike2
description: Capture text to a Bike 2 document using the CLI. Creates the document and target row if they do not exist.
icon: symbol:bicycle
popclip version: 4151
keywords: outliner
app:
  name: Bike
  link: https://www.hogbaysoftware.com/bike/
  bundle identifiers: [com.hogbaysoftware.Bike, com.hogbaysoftware.Bike-setapp]
  check installed: true
options:
  - identifier: document
    label: Document Path
    type: string
    default value: ~/Documents/Inbox.bike
    description: Absolute path to the Bike document, such as "~/Documents/Inbox.bike".
  - identifier: row
    label: Parent Row Path
    type: string
    default value: /Inbox
    description: Bike outline path for the parent row (e.g., /Inbox). Leave blank to add directly to the root.
  - identifier: position
    label: Add At
    type: multiple
    values: [start, end]
    value labels: [Top, Bottom]
    default value: start
    description: Where in the list to add new rows.
actions:
  - requirements: [text]
    after: show-status  
    interpreter: zsh
    shell script: |
        setopt extended_glob

        DOC_PATH="${POPCLIP_OPTION_DOCUMENT/#\~/$HOME}"

        # Spaces, tabs, newlines trimmed from start and end
        TRIMMED_TEXT="${POPCLIP_TEXT##[[:space:]]#}"
        TRIMMED_TEXT="${TRIMMED_TEXT%%[[:space:]]#}"

        # Any indents preserved, all non-empty lines have markdown bullets
        BULLETED_TEXT=$(printf "%s" "$TRIMMED_TEXT" | sed -E 's/^([[:blank:]]*)([^[:blank:]-]+)/\1- \2/')

        add_rows() {
            # Text of first line, and its session ID, as two lines of text
            NAME_SESSIONID=$(printf "%s\n" "$BULLETED_TEXT" |
                bike create rows -f - --outline "$DOC_PATH" "$@" |
                jaq -r '.[0].text[0].string, .[0].id'
            )
            
            FIRST_ROW_TEXT="${NAME_SESSIONID%$'\n'*}"
            FIRST_CLAUSE="${FIRST_ROW_TEXT%%[.,;:]*}"
            
            SESSION_ID="${NAME_SESSIONID##*$'\n'}"
            
            # Persistent ID of the document
            DOC_ID=$(bike get outline /* --outline "$DOC_PATH" | jaq -r ".persistentId")
            
            BIKE_URL=$(bike update rows "$SESSION_ID" --outline "$DOC_PATH" \
                        --attr added="$(date +'%Y-%m-%d %H:%M')" \
                        --persistent-id @ensure |
                        jaq -r --arg doc "$DOC_ID" '"bike://\($doc)/#\(.[0].row.persistentId)"')
            
            MD_LINK="[${FIRST_CLAUSE}](${BIKE_URL})"
            
            # Placed in various pasteboard items   
            osascript -l JavaScript -e "
                ObjC.import('AppKit');
                const pb = $.NSPasteboard.generalPasteboard;
                pb.clearContents;
                pb.setStringForType('${FIRST_CLAUSE}', 'public.url-name');
                pb.setStringForType('${BIKE_URL}', 'public.url');
                pb.setStringForType('${MD_LINK}', 'public.utf8-plain-text');
            " > /dev/null
            
            echo "$MD_LINK"
        }

        if [[ ! -f "$DOC_PATH" ]]; then
            bike create outline "$DOC_PATH"
        fi

        if [[ -n "$POPCLIP_OPTION_ROW" ]]; then
            ROW_EXISTS=$(bike get outline --refs "$POPCLIP_OPTION_ROW" --outline "$DOC_PATH" --shape flat -o txt)
            
            # The parent heading created if not found
            if [[ -z "$ROW_EXISTS" ]]; then
                ROW_TEXT="${POPCLIP_OPTION_ROW#/}"
                bike create row --outline "$DOC_PATH" --text "$ROW_TEXT"
            fi
            
            add_rows --parent "$POPCLIP_OPTION_ROW" --position "$POPCLIP_OPTION_POSITION"
        else
            add_rows --position "$POPCLIP_OPTION_POSITION"
        fi
1 Like

This is awesome, thank you! At some point, I’ll come back around to take a look at this as inspiration to update the extension for Bike 2.0.

1 Like