Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 5 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ ExitBox automatically:
- **Full Git Support** — optional mode that mounts host `.gitconfig` and SSH agent for seamless git operations inside the container
- **GitHub CLI Authentication** — pre-flight vault import for `GITHUB_TOKEN` with automatic in-container export so `gh` and HTTPS git work transparently
- **RTK Token Optimizer (Experimental)** — optional [rtk](https://github.com/rtk-ai/rtk) integration reduces CLI output token consumption by 60-90%
- **Graphify Knowledge Graph (Experimental)** — optional [graphify](https://github.com/safishamsi/graphify) integration; when enabled, the `/graphify` skill is auto-installed into compatible agents and removed when disabled
- **Graphify Knowledge Graph (Experimental)** — optional [graphify](https://github.com/safishamsi/graphify) external tool; when selected, the `/graphify` skill is auto-installed into compatible agents and removed when deselected
- **External Tools** — configure third-party tools (GitHub CLI, etc.) via the setup wizard; packages are auto-installed at image build time
- **Supply-Chain Hardened Installs** — Claude Code and OpenCode installed via direct download with SHA-256 checksum verification
- **Alpine Base Image** — minimal ~5 MB base with 3-layer image hierarchy and incremental rebuilds
Expand Down Expand Up @@ -244,17 +244,17 @@ exitbox agents config claude # Open agent config in $EDITOR

### Graphify Knowledge Graph (Experimental)

[graphify](https://github.com/safishamsi/graphify) maps a project (code, docs, PDFs, images) into a queryable knowledge graph you invoke with `/graphify` inside an agent. When enabled, the `graphify` CLI is installed at image build time and the `/graphify` skill is registered into each compatible agent at container start; when disabled, the skill is removed.
[graphify](https://github.com/safishamsi/graphify) maps a project (code, docs, PDFs, images) into a queryable knowledge graph you invoke with `/graphify` inside an agent. It's an ExitBox **External Tool**: when selected, the `graphify` CLI is installed into the tools image layer and the `/graphify` skill is registered into each compatible agent at container start; when deselected, the skill is removed.

```bash
# Enable via the setup wizard:
# Select via the setup wizard:
exitbox setup
# → Settings → Graphify → Enable
# → Tools → External Tools → Graphify
```

Compatible agents: **Claude, Codex, OpenCode, Copilot, Pi**. (Cursor's graphify install is project-scoped and Kimi Code's skill path differs from graphify's `kimi` target, so they are not auto-wired; Qwen has no graphify platform.)

> **Note:** Graphify is experimental and gated at image build time like RTK — toggling it requires a rebuild (`exitbox run <agent> --update`).
> **Note:** Graphify is experimental. Selecting/deselecting it rebuilds the tools image layer (`exitbox run <agent> --update`).

### Agent Skills

Expand Down
1 change: 0 additions & 1 deletion cmd/run.go
Original file line number Diff line number Diff line change
Expand Up @@ -322,7 +322,6 @@ func runAgent(agentName string, passthrough []string) {
Keybindings: cfg.Settings.Keybindings.EnvValue(),
FullGitSupport: cfg.Settings.DefaultFlags.FullGitSupport,
RTK: cfg.Settings.RTK,
Graphify: cfg.Settings.Graphify,
}

exitCode, err := run.AgentContainer(rt, opts)
Expand Down
1 change: 0 additions & 1 deletion internal/config/defaults.go
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,6 @@ func DefaultConfig() *Config {
AutoUpdate: false,
StatusBar: true,
RTK: false,
Graphify: false,
DefaultWorkspace: "default",
DefaultFlags: DefaultFlags{
AutoResume: false,
Expand Down
1 change: 0 additions & 1 deletion internal/config/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,6 @@ type SettingsConfig struct {
AutoUpdate bool `yaml:"auto_update"`
StatusBar bool `yaml:"status_bar"`
RTK bool `yaml:"rtk"`
Graphify bool `yaml:"graphify"`
DefaultWorkspace string `yaml:"default_workspace,omitempty"`
DefaultFlags DefaultFlags `yaml:"default_flags"`
Keybindings KeybindingsConfig `yaml:"keybindings,omitempty"`
Expand Down
1 change: 0 additions & 1 deletion internal/image/base.go
Original file line number Diff line number Diff line change
Expand Up @@ -288,7 +288,6 @@ func buildBaseFull(ctx context.Context, rt container.Runtime, cmd, imageName str
args = append(args,
"--build-arg", fmt.Sprintf("EXITBOX_VERSION=%s", Version),
"--build-arg", fmt.Sprintf("INSTALL_RTK=%v", cfg.Settings.RTK),
"--build-arg", fmt.Sprintf("INSTALL_GRAPHIFY=%v", cfg.Settings.Graphify),
"-t", publishedName,
"-f", dockerfilePath,
buildCtx,
Expand Down
5 changes: 0 additions & 5 deletions internal/run/run.go
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,6 @@ type Options struct {
Keybindings string
FullGitSupport bool
RTK bool
Graphify bool
}

// AgentContainer runs an agent container interactively.
Expand Down Expand Up @@ -414,9 +413,6 @@ func AgentContainer(rt container.Runtime, opts Options) (int, error) {
if opts.RTK {
args = append(args, "-e", "EXITBOX_RTK=true")
}
if opts.Graphify {
args = append(args, "-e", "EXITBOX_GRAPHIFY=true")
}
if opts.Ollama {
args = append(args, ollamaEnvVars(opts.Agent)...)
}
Expand Down Expand Up @@ -541,7 +537,6 @@ func isReservedEnvVar(key string) bool {
"EXITBOX_VAULT_ENABLED": true,
"EXITBOX_VAULT_READONLY": true,
"EXITBOX_RTK": true,
"EXITBOX_GRAPHIFY": true,
"EXITBOX_IDE_PORT": true,
"CLAUDE_CODE_SSE_PORT": true,
"ENABLE_IDE_INTEGRATION": true,
Expand Down
6 changes: 6 additions & 0 deletions internal/wizard/roles.go
Original file line number Diff line number Diff line change
Expand Up @@ -194,6 +194,12 @@ var AllExternalTools = []ExternalTool{
{HostPath: ".config/gh", Description: "GitHub CLI auth"},
},
},
{
Name: "Graphify",
Description: "graphify — /graphify skill maps your project into a queryable knowledge graph (auto-installed into compatible agents)",
Packages: []string{"py3-pip"},
InstallStep: "RUN pip install --no-cache-dir --break-system-packages graphifyy && graphify --version\n",
},
}

// AllAgents defines the selectable agents, built from the agent registry.
Expand Down
5 changes: 0 additions & 5 deletions internal/wizard/tui.go
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,6 @@ type State struct {
ExternalTools []string // selected external tools (e.g. "GitHub CLI")
FullGitSupport bool // full git support (SSH agent + .gitconfig)
RTK bool // experimental: token-optimized CLI wrappers
Graphify bool // graphify knowledge-graph /graphify skill
}

// Model is the root bubbletea model for the wizard.
Expand Down Expand Up @@ -215,7 +214,6 @@ func NewModel() Model {
checked["setting:pass_env"] = true
checked["setting:read_only"] = false
checked["setting:rtk"] = false
checked["setting:graphify"] = false
kb := config.DefaultKeybindings()
return Model{
step: stepWelcome,
Expand Down Expand Up @@ -317,7 +315,6 @@ func NewModelFromConfig(cfg *config.Config) Model {
checked["setting:read_only"] = cfg.Settings.DefaultFlags.ReadOnly
checked["setting:full_git"] = cfg.Settings.DefaultFlags.FullGitSupport
checked["setting:rtk"] = cfg.Settings.RTK
checked["setting:graphify"] = cfg.Settings.Graphify

// On re-run, always start at the top menu so the user can choose
// between workspace management and general settings.
Expand Down Expand Up @@ -2055,7 +2052,6 @@ var settingsOptions = []struct {
{"setting:read_only", "Read-only workspace", "Mount workspace as read-only (agents cannot modify files)"},
{"setting:full_git", "Full Git support", "Mount SSH agent + .gitconfig into container (exposes git identity)"},
{"setting:rtk", "RTK token optimizer", "Experimental: use rtk to reduce CLI output tokens by 60-90%"},
{"setting:graphify", "Graphify knowledge graph", "Install the /graphify skill (maps your project into a queryable graph) in compatible agents"},
}

func (m Model) updateSettings(msg tea.Msg) (tea.Model, tea.Cmd) {
Expand All @@ -2082,7 +2078,6 @@ func (m Model) updateSettings(msg tea.Msg) (tea.Model, tea.Cmd) {
m.state.MakeDefault = m.checked["setting:make_default"]
m.state.FullGitSupport = m.checked["setting:full_git"]
m.state.RTK = m.checked["setting:rtk"]
m.state.Graphify = m.checked["setting:graphify"]
m.visitedSteps[stepSettings] = true
if !m.isFirstRun && m.topMenuChoice == 0 {
// Workspace management: Settings → Domains or Vault
Expand Down
1 change: 0 additions & 1 deletion internal/wizard/wizard.go
Original file line number Diff line number Diff line change
Expand Up @@ -148,7 +148,6 @@ func applyResult(state State, existingCfg *config.Config) error {
cfg.Settings.AutoUpdate = state.AutoUpdate
cfg.Settings.StatusBar = state.StatusBar
cfg.Settings.RTK = state.RTK
cfg.Settings.Graphify = state.Graphify
cfg.Settings.DefaultFlags = config.DefaultFlags{
NoFirewall: !state.EnableFirewall,
AutoResume: state.AutoResume,
Expand Down
11 changes: 0 additions & 11 deletions static/build/Dockerfile.base
Original file line number Diff line number Diff line change
Expand Up @@ -89,17 +89,6 @@ RUN if [ "$INSTALL_RTK" = "true" ]; then \
apk del musl-dev gcc; \
fi

# Install graphify — knowledge-graph "/graphify" skill for coding agents
# (conditional). The CLI (pip package "graphifyy", command "graphify") is installed
# globally so the non-root runtime user can run it; per-agent skill registration
# happens at container start (see setup_graphify in docker-entrypoint).
ARG INSTALL_GRAPHIFY=false
RUN if [ "$INSTALL_GRAPHIFY" = "true" ]; then \
apk add --no-cache py3-pip && \
pip install --no-cache-dir --break-system-packages graphifyy && \
graphify --version; \
fi

# Stub xdg-open: no browser in container, just print the URL so OAuth
# flows (OpenCode, Codex) show the link instead of failing with ENOENT.
RUN printf '#!/bin/sh\necho "Open this URL in your browser:"\necho "$1"\n' \
Expand Down
15 changes: 8 additions & 7 deletions static/build/docker-entrypoint
Original file line number Diff line number Diff line change
Expand Up @@ -1250,20 +1250,21 @@ graphify_skill_dir_for_agent() {
}

setup_graphify() {
# Register (enabled) or remove (disabled) the graphify "/graphify" skill for
# the current agent. Runs after apply_active_workspace_links so the agent's
# config dir is symlinked to the persistent workspace location.
# Register or remove the graphify "/graphify" skill for the current agent.
# graphify is an ExitBox "External Tool": when selected, its CLI is baked into
# the tools image layer, so the presence of the `graphify` command is the
# enabled signal. Runs after apply_active_workspace_links so the agent's config
# dir is symlinked to the persistent workspace location.
local platform skill_dir
platform="$(graphify_platform_for_agent "$AGENT")"
[[ -n "$platform" ]] || return 0
skill_dir="$(graphify_skill_dir_for_agent "$AGENT")"

if [[ "${EXITBOX_GRAPHIFY:-}" == "true" ]]; then
command -v graphify >/dev/null 2>&1 || return 0
if command -v graphify >/dev/null 2>&1; then
graphify install --platform "$platform" >/dev/null 2>&1 || true
else
# Disabled: remove a previously installed graphify skill (file-based so it
# works even when the CLI is no longer present after a rebuild).
# Not selected (CLI absent, e.g. after a tools-layer rebuild): remove any
# previously installed graphify skill. File-based so it works without the CLI.
[[ -n "$skill_dir" ]] && rm -rf "$skill_dir" 2>/dev/null
fi
}
Expand Down
22 changes: 12 additions & 10 deletions static/build/docker-entrypoint_test.sh
Original file line number Diff line number Diff line change
Expand Up @@ -1705,40 +1705,42 @@ test_setup_graphify() {
sdf="$(extract_func graphify_skill_dir_for_agent)"
sgf="$(extract_func setup_graphify)"

# Enabled + compatible agent → runs `graphify install --platform <X>`.
# setup_graphify redirects the command's stdout to /dev/null, so the stub
# records its args to a file instead.
# graphify CLI present (External Tool selected) + compatible agent → runs
# `graphify install --platform <X>`. setup_graphify redirects the command's
# stdout to /dev/null, so the stub records its args to a file instead.
local marker="$TEST_TMPDIR/gfy_args"
rm -f "$marker"
(
export HOME; HOME="$(mktemp -d "$TEST_TMPDIR/gfy1.XXXXXX")"
export AGENT="pi" EXITBOX_GRAPHIFY="true" GFY_MARKER="$marker"
export AGENT="pi" GFY_MARKER="$marker"
eval "$gpf"; eval "$sdf"; eval "$sgf"
graphify() { echo "$*" > "$GFY_MARKER"; }
setup_graphify
) 2>/dev/null
local enabled=""
[[ -f "$marker" ]] && enabled="$(cat "$marker")"
assert_contains "setup_graphify installs for compatible agent" "$enabled" "install --platform pi"
assert_contains "setup_graphify installs when graphify CLI present" "$enabled" "install --platform pi"

# Disabled → removes a previously installed skill dir.
# graphify CLI absent (not selected) → removes a previously installed skill dir.
local home2 removed
home2="$(mktemp -d "$TEST_TMPDIR/gfy2.XXXXXX")"
mkdir -p "$home2/.pi/agent/skills/graphify"
removed="$(
export HOME="$home2"
export AGENT="pi" EXITBOX_GRAPHIFY="false"
export AGENT="pi"
eval "$gpf"; eval "$sdf"; eval "$sgf"
# No graphify stub and graphify is not installed in the test env, so
# `command -v graphify` is false → the removal branch runs.
setup_graphify
[[ -d "$home2/.pi/agent/skills/graphify" ]] && echo PRESENT || echo GONE
)" 2>/dev/null
assert_eq "setup_graphify removes skill when disabled" "GONE" "$removed"
assert_eq "setup_graphify removes skill when CLI absent" "GONE" "$removed"

# Incompatible agent (qwen) → no graphify invocation even when enabled.
# Incompatible agent (qwen) → no graphify invocation even when CLI present.
local skipped
skipped="$(
export HOME; HOME="$(mktemp -d "$TEST_TMPDIR/gfy3.XXXXXX")"
export AGENT="qwen" EXITBOX_GRAPHIFY="true"
export AGENT="qwen"
eval "$gpf"; eval "$sdf"; eval "$sgf"
graphify() { echo "CALLED"; }
setup_graphify
Expand Down
Loading