Open the project you're working on in Xcode, from whatever coding agent you're working in.
It's the terminal xed you already know, with one addition: it figures out
what to open. xed . hands the directory to Xcode and hopes for the best.
This looks for the closest .xcworkspace, falls back to .xcodeproj, then to
Package.swift, and opens that.
/xed
Opened ./Xtraktr.xcworkspace in Xcode.
The whole thing is one dependency-free bash script — bin/xed-open.
Everything else in this repo is a thin wrapper that teaches one agent to call
it. If your agent can run a shell command, it can use this.
Requires macOS with Xcode installed (not just the Command Line Tools).
Put the script on your PATH:
git clone https://github.com/Kilo-Loco/xed.git && ./xed/install.shThen xed-open works in any agent's terminal, and in your own. That's the whole
integration — the wrappers below just save you from typing it.
/plugin marketplace add Kilo-Loco/xed
/plugin install xed@xed
Self-contained, so it doesn't need install.sh or anything on your PATH.
Restart Claude Code and /xed is there.
./install.sh --codexThat installs both forms, since which one your version supports differs:
~/.codex/prompts/xed.md→ invoke as/prompts:xed. HonorsCODEX_HOME. OpenAI has deprecated custom prompts in favor of skills.~/.agents/skills/xed/SKILL.md→ invoke as$xed, or let Codex trigger it when you ask to open something in Xcode. Note this is~/.agents/, not~/.codex/— skill discovery uses the shared.agents/skillsconvention and ignoresCODEX_HOME. Override the location withXED_SKILLS_DIRif you need to; check it into.agents/skills/in a repo to share it with a team.
Restart Codex afterward. Both call xed-open, so install.sh must have put it
on your PATH.
| Command | What happens |
|---|---|
xed-open |
Resolves the best target in the current directory and opens it |
xed-open SomeDir |
Same, but resolves inside SomeDir |
xed-open Sources/App.swift |
Opens that file — no resolution, passed to xed |
xed-open -l 42 Sources/App.swift |
Opens the file and jumps to line 42 |
xed-open -b |
Opens the resolved target but leaves Xcode in the background |
xed-open -p App.xcodeproj File.swift |
Straight passthrough to xed |
xed-open --branch main |
Resolves inside the worktree that has main checked out |
xed-open --branch main --pull |
Fast-forwards that worktree first, then opens it |
If you work in git worktrees, --branch <name> opens the checkout that has that
branch, wherever it is:
/xed --branch main
Opened /Users/you/code/MyApp/MyApp.xcworkspace in Xcode.
The main clone is a worktree as far as git is concerned, so --branch main
finds it from inside a feature worktree without any setup. Target resolution
then works exactly as it does anywhere else.
Whatever is on disk in that worktree is what Xcode shows, uncommitted changes
and all. Worktrees are independent checkouts, so a dirty main is not a
conflict — it's just what main currently looks like on your machine.
Being behind the remote is easier to miss, so that one gets a note:
/xed --branch main
xed: note — 'main' is 3 commits behind origin/main as of your last fetch.
Opened /Users/you/code/MyApp/MyApp.xcworkspace in Xcode.
The note is stderr, never fatal, and read from refs already on disk. It does
not fetch: a network round trip, an offline failure mode, and a mutation of repo
state are all too much to hide behind "open my editor". That's the tradeoff the
as of your last fetch wording is admitting to — fetch first if you just want
the number to be right, or use --pull if you want the branch itself brought up
to date. With nothing fetched at all, git has no idea it's behind and
neither does this.
Add --pull and it fast-forwards the checkout to its upstream before opening:
/xed --branch main --pull
xed: pulled 3 commits into main from origin/main.
Opened /Users/you/code/MyApp/MyApp.xcworkspace in Xcode.
Opt-in, because unlike everything else here it touches the network and moves a branch. Three rules keep it from being something you regret asking for:
- Fast-forward only. It never merges and never rebases, so it can't invent a commit or drop you into a conflicted tree to untangle from inside Xcode. A branch that has diverged is reported and left exactly as it was.
- Skipped on a dirty checkout, before it fetches anything.
--ff-onlywould often succeed there anyway, but moving the branch under your uncommitted work isn't what "open my project" should do. - Never fatal. You asked to open a project. A pull that couldn't happen — dirty tree, no upstream, unreachable remote, diverged branch — says why on stderr, and the project still opens.
It works without --branch too, on whatever checkout you're already in.
It only ever opens a checkout that already exists. If no worktree has that
branch, it says so rather than running git worktree add behind your back —
creating a checkout is a bigger decision than opening one. git worktree list
shows what's available, and if the branch exists but isn't checked out anywhere
it hands you the git worktree add line to run. A branch that doesn't exist at
all is reported as that instead — being sent to inspect your worktrees over a
typo is worse than no advice.
Worktrees on a detached HEAD have no branch and never match, and one whose directory you deleted without pruning is named as the stale entry it is rather than reported as a missing branch.
xed-open main — no flag — is the mistake everyone makes once. When it names no
existing path but does name a branch you have checked out somewhere, you get
pointed at --branch main instead of at a file that isn't there. It's only ever
a hint: a bare name is never resolved as a branch, and a real directory called
main still wins.
--branch replaces the path argument rather than joining it, and doesn't
combine with -p.
Every flag xed(1) supports — -c, -b, -l, -p — works. Resolution only
kicks in when you didn't name a file yourself.
The one exception is -w/--wait, which is stripped (with a note on stderr).
It tells xed to block until the file is closed in Xcode, which is useful as a
shell primitive but would hang the agent session that invoked it. Use xed -w
directly in a terminal if you need that.
Searching outward from the directory you gave it, up to three levels deep:
- Shallowest wins. A project at the repo root beats a workspace buried in a subdirectory.
- Within one level, workspace beats project beats package. This is the
CocoaPods and Tuist case, where opening the
.xcodeprojis the wrong answer and silently gives you a target that won't build. Foo.xcodeproj/project.xcworkspaceis never a candidate. Neither is anything under.git,.build,DerivedData,Pods,Carthage,node_modules, or.swiftpm.- Ties break on name. Two workspaces in
MyApp/?MyApp.xcworkspacewins. No name match, and it lists what it found and asks instead of guessing.
If nothing turns up, it says so rather than opening an empty Xcode window.
bin/xed-open the script — this is the actual product
test/run.sh its test suite — no dependencies, run it directly
install.sh puts it on PATH, installs agent files
integrations/codex/ Codex CLI prompt + skill
skills/xed/SKILL.md Claude Code skill
.claude-plugin/ Claude Code plugin + marketplace manifests
Claude Code's two directories sit at the repo root rather than under
integrations/ because /plugin marketplace add looks for them there. That's a
mechanical requirement of its plugin loader, not a statement about which agent
matters.
Wrappers are thin on purpose — a wrapper's whole job is to run xed-open and
report one line back. integrations/codex/ is the
template: a prompt file for agents that only take instructions, and a skill file
for agents that can trigger on intent.
Two things are worth getting right, and both are visible in the existing wrappers:
- Tell the agent to stop after reporting. Left to its own judgment, an agent that just opened a project will often start reading it.
- Say what to do when it fails. Without that,
command not foundbecomes an agent that reimplements the resolution logic inline, badly.
If your agent supports shell pre-execution — Claude Code's ```!, Gemini
CLI's !{...}, OpenCode's !`…` — use it. Xcode opens immediately instead of
after a round trip through the model.
That speed has a catch worth knowing about, and it's why the two wrappers here
differ. Pre-executed commands run before the agent reads the file, so if the
agent can also decide on its own to load the wrapper, merely judging it relevant
launches Xcode. The Claude Code skill therefore sets
disable-model-invocation: true and stays a /xed you type. The Codex skill has
no pre-execution — Codex reads it and then chooses to run xed-open — so
letting it trigger on intent is safe there. Pre-execution and intent-triggering
are a bad pair for anything with a side effect.
PRs for other agents are welcome.
MIT © Kilo Loco