install

package
v0.5.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jun 19, 2026 License: Apache-2.0 Imports: 22 Imported by: 0

Documentation

Overview

Package install wires a vault into the machine: it writes the per-vault contracts (.hebb/config.toml, the project-scoped .mcp.json), symlinks global skills and memory, and renders launchd jobs. Every operation is idempotent and parameterised by its target directories so it is fully testable and never hard-codes a home directory.

Index

Constants

View Source
const DefaultCodexStartupTimeoutSec = 20

DefaultCodexStartupTimeoutSec gives the pure-Go SQLite index time to open on a cold first run, above Codex's 10s default.

View Source
const DefaultMCPCommand = "hebb"

DefaultMCPCommand is the binary Claude launches for the MCP server. It is a bare name resolved on PATH (brew/npm install) rather than an absolute path, so the committed .mcp.json travels with a synced vault.

View Source
const DefaultMCPServerName = "hebb"

DefaultMCPServerName is the server key written into .mcp.json (and so the tool prefix Claude sees, e.g. mcp__hebb__search_vault).

View Source
const DefaultRepo = "cizer/hebb"

DefaultRepo is the GitHub repo hebb releases come from.

View Source
const GlobalWebLabel = "local.hebb.web"

GlobalWebLabel is the launchd label of the one machine-global web service. It has no vault slug (unlike per-vault jobs), so per-vault teardown never touches it.

View Source
const GlobalWebPort = 4321

GlobalWebPort is the port the single machine-global web service binds.

Variables

This section is empty.

Functions

func AnyFailed

func AnyFailed(checks []Check) bool

AnyFailed reports whether any check has status "fail".

func AnyRemoved

func AnyRemoved(r TeardownReport) bool

AnyRemoved reports whether the report contains an action that removed (or would remove) something - useful for exit codes / messaging.

func AssetName

func AssetName(version, goos, goarch string) string

AssetName is the release asset for a version/platform, matching GoReleaser and install.sh (e.g. hebb_0.1.1_darwin_arm64.tar.gz).

func BinaryFromArchive

func BinaryFromArchive(targz []byte) ([]byte, error)

BinaryFromArchive extracts the file named "hebb" from a .tar.gz release asset.

func Bootout

func Bootout(label string)

Bootout unloads a launchd job from the user's domain, best-effort: a job that is not loaded (or a machine without launchctl, e.g. Linux/CI) is fine.

func Bootstrap

func Bootstrap(plistPaths []string, run bool) ([]string, error)

Bootstrap (re)loads the given plists into the user's launchd domain. When run is false it returns the commands it would execute without running them, so an install can preview the bootstrap. A failed bootout (the job was not loaded) is ignored; only bootstrap failures are returned as errors.

func ClaudeProjectSlug

func ClaudeProjectSlug(absPath string) string

ClaudeProjectSlug reproduces Claude Code's per-project directory naming: every non-alphanumeric character in the absolute path becomes '-', with case preserved and no collapsing. So /Users/a.b/v becomes -Users-a-b-v.

func ClaudeSkillsDir added in v0.3.0

func ClaudeSkillsDir(home string) string

ClaudeSkillsDir is Claude Code's user-global (personal) skills directory.

func CodexSkillsDir added in v0.3.0

func CodexSkillsDir(home string) string

CodexSkillsDir is Codex's user-global skills directory.

func DefaultClaudeDesktopConfigPath

func DefaultClaudeDesktopConfigPath(home string) string

DefaultClaudeDesktopConfigPath is the macOS location of the Claude Desktop config under the given home dir.

func GlobalWebJob added in v0.5.0

func GlobalWebJob(hebbBin, home string) launchd.Job

GlobalWebJob is the single machine-global web service: one `hebb serve` (no --vault) that serves every registered vault on one loopback port, switchable in the UI. It replaces the per-vault web jobs, which all defaulted to the same port and collided.

func InstallSkills added in v0.3.0

func InstallSkills(skillsFS fs.FS, dir string) ([]string, error)

InstallSkills materialises the bundled skills (skillsFS rooted at the skills parent, each immediate subdirectory a skill with a SKILL.md) into dir. hebb owns the skills it ships: it writes and updates their files and leaves any other skill already in dir untouched. Unchanged files are skipped, so it is idempotent and cheap to re-run (e.g. after `hebb update`). Returns the names of the skills delivered.

func MCPToolNames

func MCPToolNames() []string

MCPToolNames are the tools the hebb MCP server exposes. They must match the tool names registered in package mcp.

func MaterializeAssets

func MaterializeAssets(fsys fs.FS, dataDir string) (int, error)

MaterializeAssets writes the embedded function assets (fsys) onto disk under dataDir, preserving the tree. It is idempotent (unchanged files are skipped) and returns the number of files written or updated. Files under automation/ are made executable, since go:embed does not preserve the executable bit.

func MemoryDir

func MemoryDir(vaultPath string) string

MemoryDir is the in-vault home for this vault's Claude Code memory. It lives under .hebb so agent memory still syncs and travels with the vault, but is hidden from Obsidian (a dotfolder) and excluded from the index (.hebb is in the default exclude list), so memory is never treated as vault content.

func NewerAvailable

func NewerAvailable(current, latest string) bool

NewerAvailable reports whether latest is a strictly higher release than current. A dev build (version containing "dev") or any unparseable version returns false, so a dev binary is never auto-replaced by a release.

func ParseVersion

func ParseVersion(s string) (maj, min, patch int, ok bool)

ParseVersion parses vMAJOR.MINOR.PATCH (leading v optional; any pre-release or build suffix is ignored). ok is false for non-release strings (e.g. a dev build like "0.0.0-dev (abc123)"), so callers can refuse to compare them.

func PythonPath added in v0.4.0

func PythonPath() string

PythonPath resolves an absolute python3 (launchd has a minimal PATH), falling back to the bare name if it cannot be located. Exported so `hebb digest` can resolve the interpreter the same way the rendered launchd jobs do.

func RemoveClaudeDesktopConfig

func RemoveClaudeDesktopConfig(configPath, name string) (string, error)

RemoveClaudeDesktopConfig deletes the mcpServers.<name> entry, preserving every other server and key. Returns "removed" or "absent".

func RemoveCodexConfig

func RemoveCodexConfig(configPath, name string) (string, error)

RemoveCodexConfig deletes the [mcp_servers.<name>] block from the Codex config at configPath, preserving every other server, comment, and key. Returns "removed" if a block was deleted, or "absent" if there was no config or no matching block. The inverse of WriteCodexConfig, used by `hebb reset`.

func RenderBootstrap added in v0.5.0

func RenderBootstrap(version string, trackLatest bool) []byte

RenderBootstrap returns the bootstrap.sh contents. By default it pins the hebb version when version is a clean release (so a clone gets a binary new enough to open the vault's index); a dev or unknown version leaves the pin off so install.sh fetches the latest release. When trackLatest is true it pins to "latest" so a clone always installs the newest release (config [bootstrap] track_latest).

func RenderCodexServer

func RenderCodexServer(name, command, vaultPath string) string

RenderCodexServer returns the TOML block for a [mcp_servers.<name>] stdio entry pinned to vaultPath. env is an inline table so the whole entry is one contiguous block (no trailing sub-table a merge could orphan).

func RenderMCPJSON

func RenderMCPJSON(serverName, command string) ([]byte, error)

RenderMCPJSON returns the canonical project-scoped .mcp.json for a vault. args is just ["mcp"]: hebb resolves the vault from the directory it is launched in (the vault root, where this file lives), keeping the committed file free of machine-specific absolute paths.

func ReplaceBinary

func ReplaceBinary(path string, data []byte) error

ReplaceBinary atomically replaces the file at path with data: it writes a temp file in the same directory, makes it executable, and renames it over the target. Replacing the path of a running binary is safe on Unix (the running process keeps the old inode).

func RestartServices added in v0.5.0

func RestartServices() ([]string, error)

RestartServices restarts every loaded hebb web service across all vaults on this machine via `launchctl kickstart -k` (kill and relaunch), so a replaced binary takes effect. Only the long-running web jobs are restarted; scheduled jobs pick up the new binary on their next run. It is a safe no-op (returns nil, nil) where launchctl is unavailable (e.g. Linux), so callers can invoke it unconditionally. Returns the labels restarted.

func Slugify

func Slugify(s string) string

Slugify turns a vault name into a label-safe token (lowercase, alphanumerics and single hyphens), used in launchd labels and the memory symlink path.

func StableBinPath added in v0.4.0

func StableBinPath(exePath string, candidates []string) string

StableBinPath is the testable core of StableHebbBin: given the executable path and a list of candidate stable symlinks, it returns a candidate that resolves to the same binary as exePath when exePath sits under a versioned Cellar dir, else exePath. Only Cellar paths are rewritten, because only they churn across upgrades; a self-managed or go-install binary is left alone even if a symlink happens to point at it.

func StableHebbBin added in v0.4.0

func StableHebbBin(exePath string) string

StableHebbBin returns the binary path to embed in launchd jobs for the running hebb executable. When exePath resolves into a versioned Homebrew Cellar dir (so the path churns on every upgrade and the TCC Full Disk Access grant would reset), it prefers a stable symlink such as /opt/homebrew/bin/hebb that resolves to the same binary. Otherwise it returns exePath unchanged.

func SymlinkMemory

func SymlinkMemory(vaultPath, claudeProjectsDir, projectSlug string) (string, error)

SymlinkMemory links <vault>/.hebb/memory into <claudeProjectsDir>/<projectSlug>/memory so Claude Code reads the vault's (synced) memory when opened there. The vault memory dir is created if absent. It is defensive: an existing real memory dir at the target is left untouched (status "conflict").

func UpdateManagedSkills added in v0.3.0

func UpdateManagedSkills(skillsFS fs.FS, dir string) ([]string, error)

UpdateManagedSkills re-applies the bundled skills to dir, but only if hebb already manages skills there (at least one bundled skill is present). When the dir is managed it installs the full bundle, so a new skill in a release is deployed and changed skills are refreshed; when it is not (an agent the user doesn't use, or one they installed with --no-skills) it does nothing, so an upgrade never forces skills onto an opted-out dir. Returns the skills applied.

func VaultJobs

func VaultJobs(vaultPath, slug, hebbBin, assetRoot, home string, port int, names []string, updateAuto bool, jobArgs map[string][]string, jobEnv map[string]map[string]string) []launchd.Job

VaultJobs builds launchd job specs for the named jobs of a vault. The web, update-check and daily-digest jobs are built in (they run the hebb binary; `hebb digest` no longer needs a materialised script). The action-review job is only included when its script exists under <assetRoot>/automation, so no broken plist is written if the automation script is absent. updateAuto makes the update-check job install updates rather than only reporting them. jobArgs carries the per-job extra arguments from config.toml's [job_args]; they are appended to the matching job's program. jobEnv carries per-job extra environment variables from config.toml's [job_env]; they are merged into the job's EnvVars after built-in env, with user-supplied keys overriding built-in keys of the same name (user wins). The merge produces a deterministic slice: built-in env order is preserved for unoverridden keys, then user-supplied extra keys sorted alphabetically. Unknown job names are skipped.

func VerifyChecksum

func VerifyChecksum(data []byte, checksums, assetName string) error

VerifyChecksum checks the sha256 of data against the entry for assetName in a GoReleaser checksums.txt body (lines of "<hex> <name>").

func WriteBootstrap added in v0.5.0

func WriteBootstrap(vaultPath, version string, trackLatest bool) (bool, error)

WriteBootstrap writes bootstrap.sh (executable) at the vault root. Idempotent: it rewrites only when the contents differ, so the version pin tracks the hebb that last installed the vault. Returns true if the file was created or changed.

func WriteClaudeDesktopConfig

func WriteClaudeDesktopConfig(configPath, name, command, vaultPath string) (string, error)

WriteClaudeDesktopConfig merges an mcpServers.<name> entry (pinned to vaultPath via HEBB_VAULT) into the Claude Desktop config, creating it if absent. Other servers and top-level keys are preserved. Idempotent; returns "wrote", "updated", or "unchanged".

func WriteCodexConfig

func WriteCodexConfig(configPath, name, command, vaultPath string) (string, error)

WriteCodexConfig merges the hebb server block into the Codex config at configPath, creating the file (and parent dir) if absent. It is idempotent and non-destructive. Returns "created", "updated", or "unchanged".

func WriteMCPJSON

func WriteMCPJSON(vaultPath, serverName, command string) (bool, error)

WriteMCPJSON writes .mcp.json at the vault root, idempotently. It returns true if the file was created or its contents changed.

func WriteProjectSettings

func WriteProjectSettings(vaultPath, serverName string) (bool, error)

WriteProjectSettings merges hebb's MCP wiring into <vault>/.claude/settings.json: it enables the project-scoped MCP server and pre-approves its tools so the agent is not prompted for each call. Any existing settings are preserved; only the relevant arrays are extended. Returns true if the file was created or changed.

Types

type Check

type Check struct {
	Name   string
	Status string
	Detail string
}

Check is the result of one diagnostic. Status is "ok", "warn", or "fail".

func Doctor

func Doctor(opts Options) []Check

Doctor inspects a vault and its install and returns a check per facet. It is read-only: it never creates or repairs anything. Home/AssetRoot/LaunchdDir gate the home-side checks; when unset, those checks are omitted.

type InstallMethod

type InstallMethod int

InstallMethod is how the running binary was installed, which decides whether `hebb update` may self-replace it.

const (
	SelfManaged InstallMethod = iota // hebb owns the binary (e.g. install.sh)
	Homebrew                         // managed by `brew upgrade`
	GoInstall                        // managed by `go install ...@latest`
)

func DetectInstallMethod

func DetectInstallMethod(exePath string) InstallMethod

DetectInstallMethod classifies the binary at exePath (symlinks resolved).

func (InstallMethod) AdviseCommand

func (m InstallMethod) AdviseCommand() string

AdviseCommand returns the command a user should run to update a binary hebb does not own, or "" when hebb may self-replace.

func (InstallMethod) String

func (m InstallMethod) String() string

type Options

type Options struct {
	VaultPath  string
	MCPName    string
	MCPCommand string
	Home       string // base dir containing .claude (e.g. ~); "" disables memory wiring
	HebbBin    string // path to the hebb binary, for the web launchd job
	LaunchdDir string // target LaunchAgents dir; "" disables launchd rendering
	Load       bool   // if true, bootstrap rendered jobs via launchctl
	MCPJSON    bool   // if true, write a per-vault .mcp.json + settings (plugin-less wiring)
	SkipSkills bool   // if true, do not install agent skills into ~/.claude/skills

	// RegistryPath is the machine-global vault registry to register this vault
	// in (so one web server can enumerate it). Empty skips registration.
	RegistryPath string

	// HebbVersion is stamped into the committed bootstrap.sh as the pinned
	// release for clones to install (empty / dev leaves it unpinned -> latest).
	HebbVersion string

	// Agent config paths, used only by Doctor to re-verify wiring drift
	// read-only. Empty means "use the conventional default under Home"; when
	// that default file is absent the check stays silent (never-wired is silent).
	CodexConfig         string // Codex config.toml (default: <home>/.codex/config.toml)
	ClaudeDesktopConfig string // Claude Desktop config (default: macOS app support dir)

	// Asset source. The binary is standalone: Assets carries the embedded
	// function content (automation/, vault-template/), materialised to DataDir on
	// install so launchd jobs can find their scripts. AssetRoot is a development
	// override - point it at a repo checkout to use its automation/ straight from
	// source and skip materialisation.
	Assets    fs.FS
	DataDir   string
	AssetRoot string
}

Options configures an install run. Directory fields are explicit so install is fully testable and never hard-codes a home directory. An empty Home disables the home-side wiring (vault-local steps still run).

type Report

type Report struct {
	Steps []Step
}

Report is the ordered list of actions an install performed.

func Run

func Run(opts Options) (Report, error)

Run performs the file-level install for a vault and returns a report of every action. It does not build the index (the caller owns the engine/db). Steps:

  • vault config: .hebb/config.toml (always)
  • plugin-less wiring (if MCPJSON): .mcp.json + <vault>/.claude/settings.json (the hebb plugin normally provides the MCP server instead)
  • assets: materialise embedded automation scripts to DataDir (unless --asset-root points at a live repo checkout), for launchd jobs
  • memory (if Home): symlink <vault>/.hebb/memory into the Claude project dir
  • launchd (if requested): render the vault's jobs

Skills are delivered by the hebb Claude Code plugin (see plugin/), not by install. Every step is idempotent.

func Scaffold

func Scaffold(tmpl fs.FS, vaultPath string) (Report, error)

Scaffold materialises the vault template tree (tmpl) into vaultPath, creating the directory if needed. It is defensive: it refuses to scaffold into a directory that already has contents, so it never clobbers an existing vault. An absent or empty target is fine. Returns a report of the action.

tmpl is the template filesystem rooted at its contents (e.g. the embedded assets sub-FS for "vault-template", or os.DirFS of a checkout's vault-template/). The per-vault contracts (.hebb/config.toml, .mcp.json) are not part of the template; install writes those afterwards.

func VaultLocal

func VaultLocal(vaultPath string) (Report, error)

VaultLocal initialises the per-vault config (.hebb/config.toml, created with defaults if absent, never clobbered). It is idempotent. The project-scoped .mcp.json is written separately and only on request (Options.MCPJSON): the hebb plugin normally provides the MCP server.

type Step

type Step struct {
	Name   string
	Status string // created | exists | wrote | unchanged | symlinked | loaded ...
}

Step records the outcome of one install action for reporting.

type TeardownOptions

type TeardownOptions struct {
	VaultPath     string
	Home          string // base dir holding .claude; "" disables the memory step
	LaunchdDir    string // "" -> <home>/Library/LaunchAgents
	CodexConfig   string // "" -> <home>/.codex/config.toml
	DesktopConfig string // "" -> the macOS Claude Desktop config under Home
	MCPName       string // server/block name (default "hebb")
	RegistryPath  string // "" -> <home>/.config/hebb/vaults.toml via core.RegistryPath
	Force         bool   // false = dry run
	KeepIndex     bool   // default false -> clear .hebb/index.db (cheap to rebuild)
}

TeardownOptions configures `hebb reset`: un-wire a vault from this machine. Directory fields are explicit so it is hermetically testable. Force=false is a dry run (report only, mutate nothing).

type TeardownReport

type TeardownReport struct {
	Forced bool
	Steps  []TeardownStep
}

TeardownReport is the outcome of Teardown.

func Teardown

func Teardown(opts TeardownOptions) (TeardownReport, error)

Teardown removes only the machine-side wiring `hebb install` created. It NEVER touches vault content: markdown notes, .hebb/memory, and .hebb/config.toml are always left intact. Steps (each tolerant of being absent):

  • memory symlink in <home>/.claude/projects/<slug>/memory (the link only)
  • launchd plists local.hebb.<slug>.* (+ best-effort bootout)
  • the [mcp_servers.<name>] block in ~/.codex/config.toml
  • the opt-in per-vault .mcp.json (only if hebb-generated) and the hebb entry in <vault>/.claude/settings.json
  • .hebb/index.db (derived; unless KeepIndex)

type TeardownStep

type TeardownStep struct {
	Target string // what
	Status string // "removed" | "would remove" | "absent" | "skipped: <why>"
}

TeardownStep is one action in the teardown plan/report.

type Updater

type Updater struct {
	Repo         string
	APIBase      string // default https://api.github.com
	DownloadBase string // default https://github.com
	HTTP         *http.Client
	UseGH        bool
}

Updater fetches release metadata and assets. URLs are fields so tests can point them at a local server; defaults target GitHub. When UseGH is set and the gh CLI is available and authed, it is used (works for a private repo), mirroring install.sh.

func NewUpdater

func NewUpdater() Updater

NewUpdater returns an Updater with GitHub defaults and gh enabled.

func (Updater) DownloadBinary

func (u Updater) DownloadBinary(tag, goos, goarch string) ([]byte, error)

DownloadBinary fetches the asset and checksums for tag/platform, verifies the checksum, and returns the extracted hebb binary.

func (Updater) LatestTag

func (u Updater) LatestTag() (string, error)

LatestTag returns the latest release tag (e.g. "v0.1.1").

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL