How can I org-roam-protocol capture to separate Roam profiles, e.g. using .dir-locals.el, with a single target org-roam-capture-ref-template id?

Hello all,

I’m successfully using Roam, but have recently introduced a ~/dirB that I currently use for separate work away from the ~/dirA home mindset, and never the twain shall meet.

In the effort to allow profiles which do not exist to one another, I have enabled .dir-locals.el to point at ~/dirB while I’m in it, and that’s working great so far, except for one thing. To get to a complete feature-set, I need to get org-roam-protocol capturing separated, but I can’t quite figure it out.

As some background there, when I use org-roam-capture-extension to the emacs-server and capture a link through the org-roam-protocol, it doesn’t seem to notice or obey any .dir-locals.el, no matter what I try.

Here’s what I thought might trivially work, if a bit ugly, but this does not work:

((nil . ((org-roam-directory . "~/dirB")
         (org-roam-db-location . "~/dirB")
         ;; does NOT work below    
         (org-roam-capture-ref-templates .
            '(("r" "ref" plain "* %U%\n%i) %?"
               :target (file+head "~/dirB/web/${slug}.org"
                                  "#+title: ${title}\n#+roam_key: ${ref}\n#+created: %U\n")
                :unnarrowed t
                :empty-lines-before 2)))

Even restarting the emacs server while in the ~/dirB fails.

The capture target and any dir-locals do not get updated within the emacs server, but it is critical to my workflow that I can navigate to a work file and set this “work mode” throughout all of the core Roam functionality, preferably via these .dir-locals.el which have been working so far. If there’s some ELISP fanciness to let the server somehow can delegate to a local variable or function within the capture that could properly procure the correct local variable that might be all I need, but I haven’t found luck in my search to learn how to read from dir-locals within any capture templates.

Code may speak louder than words, so this is fine, if you can write current-org-roam-directory:

  :target (file+head "%(current-org-roam-directory)/web/${slug}.org"

Please offer any suggestions you might have which permit (a) org-roam-protocol to capture to different roam profiles based on where I have navigated to and (b) do not re-introduce work or home profiles to one another or introduce a second capture type – the extension only sends one capture type on one hotkey, which is the ideal workflow.

You can use command-line option to launch Emacs with a different init-directory with --init-directory option.

It looks like you can run an Emacs server in a different process with a name.

Combined, I think you can run a daemon with different org-roam-directory and org-roam-db-location – then I guess you can have two different protocol calls, one for work, the other for personal.

Just an idea. I don’t use org-protocol or org-roam-protocol but I can connect to a different emacsclient with different variables from the desktop one, so it looks like the idea will work.

Thanks for the input.

In this case, the firefox extension will only have one protocol that it’s calling.
That’s the bit in the original post about:

[do not] introduce a second capture type – the extension only sends one capture type on one hotkey, which is the ideal workflow.

Would that stymie these efforts or does that somehow work with two servers or two org protocol calls? I don’t see how it can, yet.

My suggestion above is it to define two different protocols: org-protocol-work:// and org-protocol:// or something like this. The rest should be controlled by the OS level setting that you see Org-roam manual and Org manual. You’d have two different bookmarklet with a call to the emacsclient with different options (manual):

You can run multiple Emacs servers on the same machine by giving each one a unique server name, using the variable server-name. For example, M-x set-variable RET server-name RET “foo” RET sets the server name to ‘foo’. The emacsclient program can specify a server by name, using the ‘-s’ or the ‘-f’ option (see emacsclient Options), depending on whether or not the server uses a TCP socket (see TCP Emacs server).

Alternatively, you may be able to work with a single org-protorol:// like the normal way, but create your custom function, like my/org-roam-protocol-open-ref-work and create a corresponding JS bookmarklet like roam-ref-work. And set org-protocol-protocol-alist (see how org-roam-protocol.el does this. This may be easier.

You should be able to copy the function from org-roam-protocol and let-bind your work-specific (or personal-specific) DB location and org-roam-directory.

I am assuming you can code Elisp – no need to be an expert.

I hope what I am saying above makes sense to you.

Yup. What you’re saying makes total sense.
The issue is that I do not use bookmarklets, I use a single hotkey from the Org Roam Capture Firefox Extension.
It only sends one protocol and I do not want the added weight of an extra server and protocol to manage.

I’m instead in need of a way to hook the server in to the local variables that are live depending on the current buffer. We have control of the capture template, and can add arbitrary %(functions) to ensure that the right questions are being asked of Emacs, but the question remains: how do we query the current dir-locals?

That’s the actual request; I of course could just split everything in two, but I don’t want to do that at all. Hope that makes sense!

I’ll look into org-protocol-protocol-alist as suggested and see if it helps to keep the same hotkey, same protocol, same capture template.

Try saving the output of “current-directory” when you use the protocol, so that you can inspect it later (or do edebug). I have a feeling it is always one directory and it is not the directory that has .dir-locals.el — my hunch is that you cannot control the “current-directory” for org-protocol by the looks of your original report.

I’m still hard at work on this one.

I have pieces of solution collating together, but nothing joyous or automated.

In the meantime, a couple learnings:

  1. The server keeps the values from init.el and does not have any awareness of any clients’ dir-locals.el values. That includes the “default” client, if you do not run a --daemon and just open Emacs “normally” and happen to run (server-start). That causes a breakage that dir-locals do not jump over
  2. Correspondingly, running the ELISP below has been very useful for changing these values on the fly. Then you just set another one up to change them back.
    (server-eval-at "server" '(setq org-directory "~/work/"))
    (server-eval-at "server" '(setq org-roam-directory "~/work/Roam")))
  1. There’s probably some magic to read the dir-locals and send them to the server instead of using a hard-coded string. I’ve done it but I have no faith in the code samples I’m using so I won’t post them and aggravate someone else on my bugs.

It would be great if there was a hook that could run these every time I enter an org file specifically, but all of the available hooks look like they don’t help for that. The buffer-list-update-hook is closest but it runs every time you hit M-x or anything, so hooking into it spells disaster if anything crashes; you can’t save or do anything and I don’t like how often it runs “outside” of the context of the dir-locals from experimentation.

If there’s a better hook to fire those lines, I’m very interested to hear.