Steve Crutchfield56 downloadsType a local path inside backticks; Marco Polo validates, autocompletes, and produces a clickable link to open or reveal it in your file manager.
⛵ From Venice to Cathay to ~/notes
Type a filesystem path inside backticks. Marco Polo validates it live, autocompletes as it is typed, and turns valid paths into clickable links that reveal or open them in the file manager.
Desktop only (uses Node's fs/os and Electron's shell, which do not exist on
mobile). The defaults work on macOS, Windows, and Linux, and the open/reveal commands can be
overridden with anything.
Easier to use than to describe. Just start typing:
and you'll get a clickable link with autocomplete as you type.
These also work:
Configurable color for valid paths; bad paths show up in red until corrected.
Autocomplete dropdown: up/down to browse, Enter/Tab to accept, Esc to dismiss. Directories sort first.
Command "Marco Polo: Insert local path…" opens a drill-down picker and inserts it as a backtick span.
Per-link override: append #open or #reveal inside the backticks to override the default file action:
Needs file system access to validate paths and identify subfolders, but Marco Polo will never read your files or write anything.
~, $VAR, and ${VAR}. Exported shell variables (like $SHARE) resolve
when shell-env sourcing is on — see the environment section below.Marco Polo: Insert local path… opens a drill-down picker (Enter a folder to
go in, or choose "Insert …" to drop the current path) and inserts it as a backtick span.#open or #reveal inside the backticks to override the
default file action for one link, e.g. `~/notes/today.md#open`. While the keyword
is being typed it shows red; once it is a complete keyword it renders muted and is
ignored for validation.A span is decorated (green) only when it is unambiguous enough to treat as a path, so ordinary inline code and regexes rarely light up by accident. With the deepest existing prefix shown green and any remainder red:
/ alone is not decorated./some-name (one component, no second slash) is decorated only if it exists. A
non-existent single component stays plain./some-name/ (trailing slash, nothing after) is decorated only if it exists — this
could just as easily be a regex, so it stays conservative./x/y, /a/b/c) is treated as a path and
decorated only if its first component exists: the existing prefix is green, the rest is
red. Genuine paths light up, while a multi-segment
regex like /\d+/g or a nonexistent root like /bad/x stays plain.~ and $VAR with no slash are decorated only if they resolve.Marco Polo only touches your filesystem, on a read-only basis, to (1) check whether a path exists and (2) to list subfolder names for autocomplete. It never reads the contents of your files, and never creates, changes, or deletes anything. It opens or reveals a path only when you select a link, handing the path to your system file manager (the same as double-clicking it in Finder/Explorer).
Shell access is used only if you opt in, to (1) read exported shell variables for expansion in a path (via printenv, so variables like $SHARE resolve) and (2) to run custom open/reveal commands if you chose to set them in settings.
Marco Polo will never read or access any of your files itself.
The expansion of ~ and $VAR is done by pure string substitution in JavaScript against
an environment map. A shell is never invoked on the path, so user text can never be
executed. This matters because the conventional way to expand variables — handing the text
to a shell — turns any span into a command-injection hole (for example zsh's ${(e)...}
performs command substitution, so $(rm -rf ~) would run). Marco Polo avoids that security
issue entirely by not letting span text enter a shell-evaluation context.
Defenses, in order:
~, $, or / are considered at all.$VAR, ${VAR}, and a leading ~/~/ are substituted, from
an environment map. No eval, no shell, no command substitution is possible.fs.stat) before
anything is clickable. Non-existent paths do nothing.shell API, which takes a
path argument directly and does not parse a command line.Two places do touch a shell, both by explicit opt-in and neither with span text:
{path}
token is single-quote escaped before substitution, and is only ever filled with a path
that already passed the existence gate. Only put commands you trust there.printenv in your login shell and parses the output as
inert KEY=VALUE data. Your path text is never passed to that shell. The shell does run
your own dotfiles, which is your trusted code.Only ~ and ~/... (the current user, via os.homedir()) expand. ~otheruser is left
untouched, so it fails the existence gate and never resolves. Resolving another account's
home would mean querying the system password database for an arbitrary username — extra
lookup surface for almost no benefit on a personal machine — so it is deliberately omitted.
Likewise an unknown $VAR is left as written rather than collapsing to empty, so $NOPE/x
never silently becomes /x (since that different, possibly-valid path was not intended).
$SHARE)A GUI app inherits the login environment, not an interactive shell's exported variables, so
a $FOO defined only in .zshrc is normally absent from process.env. With shell-env
sourcing enabled (the default, off on Windows), Marco Polo runs your login shell once at
startup — $SHELL -ilc printenv — and merges its exported variables over process.env, so
$SHARE and friends resolve. Notes:
export SHARE=...). A bare SHARE=... will not
appear, because printenv lists the environment, not shell-local variables.-ilc runs an interactive login shell, so exports in .zshrc as well as
.zprofile/.zshenv are captured.Marco Polo: Refresh environment variables
to re-source without restarting.process.env if it fails.# build, then symlink into the vault's plugin folder
npm install
npm run build # one-off production build -> main.js
# or: npm run dev # watch mode
# link the folder into a vault (adjust the vault path)
ln -s "$PWD" "/path/to/Vault/.obsidian/plugins/marco-polo"
Then enable Marco Polo under Settings -> Community plugins. Reload Obsidian (or use the Hot-Reload plugin) after each build.
open -a "Path Finder" {path}, Linux xdg-open {path}, Windows
explorer {path}. (The blank default works on every OS. A custom command, by contrast,
is specific to that operating system's tools — so if your vault syncs to a machine running
a different OS, a custom command set on one won't run on the other. Leave the commands
blank for portable behavior.)