For extension and plugin authors, shell scripts, and Makefiles.

Constly IDE contract

Contract version: 1 — ships in Constly 4.7.0.

This is the public, stable way for another program — a VS Code extension, a JetBrains plugin, a shell script, a Makefile — to open a file in Constly and put the cursor somewhere useful. It is a command line. There is no daemon, no socket, no port, and nothing to configure.

Constly’s own copy of this document lives at docs/IDE-CONTRACT.md; the design that produced it is docs/DESIGN-ide-extensions.md §2, and the grammar line below is pinned byte-for-byte against it by docs/doc-anchors.tsv so the two cannot drift apart quietly.


The command line

constly [--goto=<line>[:<col>]] [--from=<ide-id>[:<port>]] [--] <path>...
constly --version
Element Rule
<path>... One or more paths; all are opened. A relative path is made absolute against the caller’s working directory — meaningful for a terminal user, and for a second launch, which carries the caller’s cwd to the running instance. Paths are made absolute, not canonical: the spelling you pass is the spelling Constly shows, recents remember, and the session file keeps. On macOS a cold start through open … --args has a working directory of /, so pass absolute paths there.
--goto=L[:C] 1-based line and optional column (UTF-16 code units). Applies to the first path. 0 means 1; a value past the end of the file lands on the last line; a malformed value is ignored.
--from=id[:port] Who is handing the file over. id matches ^[a-z][a-z0-9-]{0,31}$ — for VS Code and its forks, the editor’s own URI scheme (vscode, vscode-insiders, vscodium, cursor, windsurf, …); jetbrains for the JetBrains IDEs, with an optional numeric REST port. It describes the hand-off, so unlike --goto it applies to every path in the launch. Recorded per tab and refreshed on every hand-off, including when the tab is already open. Malformed values are ignored. Constly does not act on it yet; the return trip (“Back to your IDE”) is what will.
Single-token flags Prefer --flag=value. --goto L:C and --from id are accepted too, but only by Constly 4.7.0 and later — see “There is no version gate” below, which is the reason to send the = form. In that two-token form a value Constly cannot parse is still treated as the flag’s value while another path is on the line (--goto a.md b.md opens b.md alone — a.md is the value and never opens), and becomes a path itself only when nothing else would open (--goto a.md opens a.md with no caret): one more reason to send --flag=value.
--version Prints Constly <version> to stdout and exits 0, without opening, focusing or flashing a window, and without disturbing a running instance. macOS and Linux; the Windows build is a GUI-subsystem binary with no console. For humans and scripts.
Unknown - / -- flags Ignored. The contract is forward compatible by construction.
-- Ends flag parsing, so a path may begin with -.

Nothing is passed to a shell. Spawn Constly with an argument array.

Where to find the binary

Platform Path
macOS /Applications/Constly.app/Contents/MacOS/constly (the binary inside the bundle)
Windows constly.exe in the install location
Linux constly on PATH (deb/rpm), or the binary from the tar

On macOS, running that inner binary reaches an already-running Constly through its single-instance channel, flags included. open -a Constly.app does not: macOS drops --args when the app is already running.

There is no version gate

Do not probe for a version. Do not read the registry, dpkg, rpm or Info.plist, and do not call --version to decide what to send. Send the flags unconditionally: a Constly older than 4.7.0 skips any argument beginning with - and opens the file anyway, so the only observable difference on an old install is that the cursor stays where it was.

One thing does differ before 4.7.0: older builds open only the first path of a launch. Send one launch per path, sequentially, and a multi-file hand-off behaves identically on both.


Opening one file twice, on purpose

macOS has two ways to hand a file to an application, and a cold start from an IDE uses both at once:

open -a /Applications/Constly.app /abs/notes.md --args --goto=3:1 -- /abs/notes.md

The document arrives through LaunchServices (no caret) and the argument list arrives as argv (with the caret), in whichever order the system chooses. Constly folds the two into one tab and applies the caret from whichever arrival carried it. You do not need to work around this; the duplicate is the supported way to get a caret on a cold start.


What Constly does with the file


Saving: the filesystem is the channel

Constly does not autosave to disk. Unsaved work is protected by Constly’s own session drafts, which survive a quit, a crash and an update — but they are not the file. Your IDE sees Constly’s edits when the user presses ⌘S / Ctrl+S in Constly, or uses Back to your IDE, which saves first.

Tell your users this plainly. It is the single thing that most often reads as “the bridge is broken” when it is working exactly as designed.

The other direction is symmetrical, and it is the caller’s job:

When the file changes on disk under Constly, a clean tab either reloads silently (if the user has opted into that) or offers a Reload / Always / Dismiss banner; a tab with unsaved edits always gets the banner, and a save that would overwrite an external change asks first. If the file is moved or deleted, the tab says so and the next save asks where to put the bytes instead of silently recreating the file — Constly never detaches a tab from its file on its own.

Encoding, line endings and byte-order marks are preserved: Constly rewrites only the ranges that changed, so a round trip through it does not reformat a file.


Worked examples

# Open a file at line 42, column 9, from VS Code.
constly --goto=42:9 --from=vscode -- /home/ada/notes/plan.md

# Open a file at line 42, from IntelliJ, telling Constly the REST port.
constly --goto=42 --from=jetbrains:63342 -- /home/ada/notes/plan.md

# Three files: three launches, in order.
constly --from=cursor -- /abs/a.md
constly --from=cursor -- /abs/b.md
constly --from=cursor -- /abs/c.md

# macOS, nothing running yet: document + argv, one tab, caret on line 3.
open -a /Applications/Constly.app /abs/a.md --args --goto=3:1 -- /abs/a.md

# macOS, already running: use the inner binary, which reaches it.
/Applications/Constly.app/Contents/MacOS/constly --goto=3:1 -- /abs/a.md

Not in version 1