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.
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
Nushell
Heynote
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
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.
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>"'
$ 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>"}
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
Made by @ndyg