class

Engram::Cli

Inherits Reference < Object

The engram executable: subcommand dispatch, option parsing, and all user-facing output. Every other file under src/engram/ is a library; this is the only place that touches STDIN/STDOUT/STDERR/ARGV/exit codes.

Constants

CONFIG_FILE = File.join(".agents", "engram.yml")
MEMORIES_SUBDIR = File.join(".agents", "memories")

Constructors

new(stdout : IO, stderr : IO)

Builds a CLI instance writing normal output to stdout and errors/warnings to stderr.

Source

Class methods

db_integrity_ok?(db_path : String) : Bool

PRAGMA integrity_check against db_path; true (nothing to check yet) if the file doesn't exist.

Source
db_path_for(repo_root : String) : String

The per-clone SQLite cache path: <git dir>/engram.db.

Source
embedder_reachable?(config : EmbedderConfig) : Bool

Best-effort TCP reachability check for config's endpoint host:port (no request body sent).

Source
ensure_config_stub(path : String) : Bool

Writes a fully-commented-out .agents/engram.yml stub if the file doesn't already exist; returns whether it was written.

Source
ensure_gitignore_note(repo_root : String) : Bool

Appends an idempotent, marker-guarded note to repo_root/.gitignore explaining that .git/engram.db never needs an entry (git never tracks .git/ itself). Returns whether it was written.

Source
ensure_schema(db_path : String) : Nil

Guarantees the sqlite schema exists at db_path before a read-only command (search, recent) opens its own Search connection — Search never creates tables itself, only Store does. On a fresh repo where init/sync never ran, this keeps first-run search/recent a clean "No memories found." (exit 0) instead of leaking sqlite's raw "no such table: memories" as an uncaught exception (exit 2).

Source
executable?(path : String) : Bool

True if path has any executable bit set (owner, group, or other).

Source
find_repo_root(start : String) : String

Walks up from start looking for a .git entry (directory or worktree pointer file); the directory containing it is the repo root. Raises Engram::EnvironmentError if none is found — engram's per-clone cache lives under .git/, so it has nowhere to go outside a git repo.

Source
fts5_available?

True if this sqlite build supports FTS5 (creates a throwaway in-memory virtual table to check).

Source
git_dir_for(repo_root : String) : String

The real git directory for repo_root's .git entry: itself if a directory, or the target of a worktree/submodule gitdir: pointer file. In a linked worktree this is the private per-worktree gitdir (e.g. .git/worktrees/<name>) — exactly right for db_path_for's per-clone cache, which is deliberately worktree-private, but wrong for anything that needs git's actual hooks directory. Use hooks_dir_for for that.

Source
hooks_dir_for(repo_root : String) : String

Resolves repo_root's effective hooks directory the same way git itself resolves it when deciding whether to run a hook: by shelling out to git rev-parse --path-format=absolute --git-path hooks. This — not git_dir_for plus a manually-joined "hooks" — honors core.hooksPath (which can point anywhere, under any name) and, in a linked worktree, resolves to the shared common-dir hooks rather than the worktree's own private gitdir. A hook installed anywhere else is silently never run by git, so every path that installs or checks hooks must go through this.

Source
hooks_installed(hooks_dir : String) : Array(String)

Which of the three managed hooks currently exist, are executable, and carry the engram marker under the effective hooks_dir (from hooks_dir_for — never a manually-joined <git_dir>/hooks). All three must hold: git silently skips a non-executable hook, so a marker-only match would have doctor call a dead hook file "installed" when it will never actually run.

Source
hooks_with_missing_binary(hooks_dir : String, installed_names : Array(String)) : Array(String)

Of installed_names (already confirmed present/executable/marker-carrying by hooks_installed), which ones bake in an absolute engram path that no longer exists on disk — a hook that looks "installed" but would actually fail to run engram at all. Only checks paths resolve_engram_path would actually produce (absolute, starting with "/"); the rare bare-command fallback (when the OS can't report Process.executable_path) can't be verified this way and is left alone rather than flagged as false-stale.

Source
load_embedder(repo_root : String) : Embedder | Nil

Loads .agents/engram.yml's embedder config (if any) and wraps it in a real HTTP-backed Embedder.

Source
memories_dir_for(repo_root : String) : String

.agents/memories under repo_root.

Source
record_sync_meta(store : Store, embedder : Embedder | Nil) : Nil

Records embedder-on/off and last-sync-time in engram_meta after a sync, so memory_status (MCP) and doctor can report real state instead of guessing.

Source
run(argv : Array(String), stdout : IO = STDOUT, stderr : IO = STDERR) : Int32

Runs engram with argv and returns the process exit code (0/1/2 per docs/SPEC.md).

Source
search_embedder_proc(repo_root : String) : Search::Embedder | Nil

A one-shot query-embedding proc for Search, or nil when no embedder is configured. Reuses Embedder's own HTTP transport; Search already swallows any exception it raises.

Source
split_csv(value : String) : Array(String)

Splits a --topics a,b / --supersedes 1,2 style comma list into trimmed, non-empty parts.

Source
split_positional_args(args : Array(String), valued_flags : Array(String), boolean_flags : Array(String)) : Tuple(Array(String), Array(String))

Splits args into (option tokens, positional tokens) for commands whose positional text (a search query, a show id) must be taken literally even when it begins with '-' — a bare OptionParser would otherwise reject it as an unknown flag. valued_flags consume the following token as their value; boolean_flags stand alone; anything else is positional, including tokens that merely look like flags. A literal -- ends flag scanning early: every token after it is positional even if it exactly matches a flag name.

Source

Instance methods

run(argv : Array(String)) : Int32

Dispatches argv's first element as a subcommand and returns the resulting exit code.

Source