@ stacks

Stacks is a modern clipboard manager. It's built to help you manage your code snippets, screenshots, and other copied items in a clean, organized fashion. If you're new here and want to know how Stacks can fit into your workflow, find out more on the main page.

Release: v0.15.6

Mon, Mar 18 2024

Generated from stack: 03BDHSE4NJ8JC3NSYEC9RLWT3

Stacks has had an embedded tiny HTTP service which exposes the underlying clip store as an API for a while now. But it's only been available in dev mode. Version 0.15.6 of Stacks makes this available in release mode.

Contents

Setup

The API is hosted on a Unix domain socket, in the app's data directory. You can actually just query it with curl:

curl --unix-socket \
  ~/'Library/Application Support/stream.cross.stacks/store-v3.0/sock' \
  http://localhost/03BCPN2DNQ529QRQKBQCZ4JV4

But you'll want to symlink the application binary to a location on your system's PATH. For example:

sudo ln -s \
  '/Applications/Stacks.app/Contents/MacOS/Stacks' \
  /usr/local/bin/stacks

Basics

When run this way, stacks operates as a tiny HTTP client, and exposes your clip store as a CLI tool. You can get the contents of a specific clip:

$ stacks <clip-id>

In the meta-panel for a given clip, the clip id is now copyable on click. This is useful for grabbing a clip id to use on the command line.

A handy trick is to alias content you're actively working with to what is effectively a register. For example, if you have a JSON payload on your clipboard, you can grab its id and ..

$ alias r1='stacks <stack-id>'
$ r1 | jq
...

Just be careful with pulling image clips since the stdout will be binary. I should attempt to detect if stdout is a TTY and avoid outputting images without an override.

Clip metadata and iterating an entire stack's clips

It's possible to grab the metadata for a given clip id with the --meta flag:

stacks <id> --meta

The metadata for a stack includes a list of that stack's child clip ids. This opens the door to all sorts of interesting things. For example, 03BDHSE4NJ8JC3NSYEC9RLWT3 is the id for the stack of clips that make up these release notes:

$ stacks 03BDHSE4NJ8JC3NSYEC9RLWT3 --meta | jq
{
  "clip": {
    "children": [
      "03BDHTV73AVGVHS5EGCD0V46V",
      "03BDHTURIU4UC4RRPTXLTDRN5",
      "03BDHUN7XGBXMQV5ZOF0HBH3N",
      "03BDHUCZ5K7HXHFO494YQ6XP3",
      "03BDHV6A0N8CVJPGZ6U0CVKRO",
      "03BDHV950KCSS7B21SEDG6KWD",
      "03BDHVMKPWG5F50OX0H8TT5XO",
      "03BDHVHAPQ9XI3WS6S5VITEE5",
      "03BDHVHUYL50OG2WTR9ZFGQ67",
      "03BDLTFUJRN8SZNNHEEJO2QK4",
      "03BDLTOCHS1FC6Q25XF9HVD9S",
      "03BDLU0YN3DJ317YEQYHQWM6U",
      "03BDLU1U9ZDBGRAM5LVCTBJ5H",
      "03BDLU8Q9IUOVUM16LWLT6NTP"
    ],
    "cross_stream": false,
    "ephemeral": false,
    "hash": "sha256-fe8eMCb9krddqfcDtiaHmTkYF8MwWQBow0QKSdw+uEc=",
    "id": "03BDHSE4NJ8JC3NSYEC9RLWT3",
    "last_touched": "03BDLUHGLHCQAZOXXJYHMCAQ8",
    "locked": true,
    "ordered": true,
    "stack_id": null,
    "touched": [
      "03BDHSE4NJ8JC3NSYEC9RLWT3",
      "03BDHX36EMW4APWYDMXUXETTG",
      "03BDLSUUN3OU2SQK4GS3YB9Y1"
    ]
  },
  "content": {
    "content_type": "Text",
    "hash": "sha256-fe8eMCb9krddqfcDtiaHmTkYF8MwWQBow0QKSdw+uEc=",
    "mime_type": "text/plain",
    "terse": "Sun, Mar 17 2024, 08:12 AM",
    "tiktokens": 14
  }
}
$ stacks 03BDHSE4NJ8JC3NSYEC9RLWT3 --meta | jq -r '.clip.children[]'
03BDHTV73AVGVHS5EGCD0V46V
03BDHTURIU4UC4RRPTXLTDRN5
03BDHUN7XGBXMQV5ZOF0HBH3N
03BDHUCZ5K7HXHFO494YQ6XP3
03BDHV6A0N8CVJPGZ6U0CVKRO
03BDHV950KCSS7B21SEDG6KWD
03BDHVMKPWG5F50OX0H8TT5XO
03BDHVHAPQ9XI3WS6S5VITEE5
03BDHVHUYL50OG2WTR9ZFGQ67
03BDLTFUJRN8SZNNHEEJO2QK4
03BDLTOCHS1FC6Q25XF9HVD9S
03BDLU0YN3DJ317YEQYHQWM6U
03BDLU1U9ZDBGRAM5LVCTBJ5H
03BDLU8Q9IUOVUM16LWLT6NTP
03BDLV68IRBUH4N692PJGIBJW
03BDLV9EWPULXIW98MBC4MSZN

The stack's CLI also takes a --html flag, which pulls the same HTML that stacks uses to render clips in its main UI. This allows you to get a clip's content as syntax highlighted or as rendered markdown. Images are <img> tags, with their content inline as base64 data.

You pull HTML for all clips in a stack like this:

$ stacks 03BDHSE4NJ8JC3NSYEC9RLWT3 --meta | 
  jq -r '.clip.children[]' | 
  xargs -I{} stacks {} --html

It's nice to wrap each of the generated HTML snippets with a <div>, which is reasonably doable, like this:

$ stacks 03BDHSE4NJ8JC3NSYEC9RLWT3 --meta | 
  jq -r '.clip.children[]' | 
  xargs -I{} bash -c 'echo "<div>$(stacks {} --html)</div>"'

At this point though, I'd encourage you to switch to working in nushell.

Scripting with nushell

Here's how it looks with nu to pull a HTML snippet for each clip in a stack, and wrap each snippet with a <div>

$ stacks 03BDHSE4NJ8JC3NSYEC9RLWT3 --meta | from json
 clip       children         0   03BDHTV73AVGVHS5EGCD0V46V
                             1   03BDHTURIU4UC4RRPTXLTDRN5
                             2   03BDHUN7XGBXMQV5ZOF0HBH3N
                             3   03BDHUCZ5K7HXHFO494YQ6XP3
                             4   03BDHV6A0N8CVJPGZ6U0CVKRO
                             5   03BDHV950KCSS7B21SEDG6KWD
                             6   03BDHVMKPWG5F50OX0H8TT5XO
                             7   03BDHVHAPQ9XI3WS6S5VITEE5
                             8   03BDHVHUYL50OG2WTR9ZFGQ67
                             9   03BDLTFUJRN8SZNNHEEJO2QK4
                            10   03BDLTOCHS1FC6Q25XF9HVD9S
                            11   03BDLU0YN3DJ317YEQYHQWM6U
                            12   03BDLU1U9ZDBGRAM5LVCTBJ5H
                            13   03BDLU8Q9IUOVUM16LWLT6NTP
                            14   03BDLV68IRBUH4N692PJGIBJW
                            15   03BDLV9EWPULXIW98MBC4MSZN
            cross_stream   false
            ephemeral      false
            hash           sha256-fe8eMCb9krddqfcDtiaHmTkYF8MwWQBow0QKSdw+uEc=
            id             03BDHSE4NJ8JC3NSYEC9RLWT3
            last_touched   03BDLWATRALX74PFWIT6U0496
            locked         true
            ordered        true
            stack_id
            touched         0   03BDHSE4NJ8JC3NSYEC9RLWT3
                            1   03BDHX36EMW4APWYDMXUXETTG
                            2   03BDLSUUN3OU2SQK4GS3YB9Y1
 content    content_type   Text
            hash           sha256-fe8eMCb9krddqfcDtiaHmTkYF8MwWQBow0QKSdw+uEc=
            mime_type      text/plain
            terse          Sun, Mar 17 2024, 08:12 AM
            tiktokens      14

This page you're reading was rendered like this:

$ stacks 03BDHSE4NJ8JC3NSYEC9RLWT3 --meta | 
  from json | 
  get clip.children | 
  each {|id| stacks $id --html} | 
  each {|x| $"<div>($x)</div>"}

Open a stack of clips in Heynote

Heynote is a dedicated scratchpad for developers. It’s a terrific little tool. There’s no load or save, just a single pane that allows you to edit different blocks of text. I've been wanting to "flick" a stack of clips to Heynote for editing.

Heynote stores its buffer at ~/Library/Application Support/Heynote/buffer.txt, employing a brilliantly simple method to differentiate text blocks. It uses three infinity symbols, the content type of the text, followed by a newline, like so: ∞∞∞json.

You can use Stack's clip store API to export a stack of clips in this format. This is a nushell plugin that does just that.

stacks.nu:

export def to-heynote [stack_id: string] {
    stacks --meta $stack_id |
        from json |
        get clip.children |
        each {|id| stacks $id --meta | from json} |
        where content.mime_type == "text/plain"  |
        each {|x| $"∞∞∞($x.content.content_type | str downcase)\n(stacks $x.clip.id)" } |
        save -f '~/Library/Application Support/Heynote/buffer.txt'
}

And you use it like this:

$ use stacks.nu
$ stacks to-heynote 03BDHSE4NJ8JC3NSYEC9RLWT3