GitHub
07/08/2026, 7:11 AMosctrl-cli is one-shot: every action re-invokes the binary, re-parses flags, and re-establishes the API/DB connection. There is no way to open a persistent operator session and explore the fleet the way the admin web UI allows from the terminal.
Change
Adds an osctrl-cli shell command (aliases console, interactive, repl) that opens one persistent session β keeping the --api or --db backend open β and exposes the full CLI surface through a context-based, Metasploit-style REPL. No re-invocation per action.
$ osctrl-cli --api-file osctrl-api.json shell
π‘οΈ osctrl-cli interactive shell v0.5.4
π‘ api mode
osctrl[dev]> use nodes
osctrl (nodes:dev)> list
Hostname UUID Platform Version Osquery IP LastSeen Status
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
host01 NODE-UUID-1 linux 5.15.0 5.12.1 10.0.0.5 1 hour ago active
mac01 NODE-UUID-2 darwin 23.0 5.12.1 10.0.0.6 2 days ago inactive
osctrl (nodes:dev)> back
osctrl> use queries
osctrl (queries:dev)> set uuids NODE-UUID-1
osctrl (queries:dev)> run SELECT * FROM osquery_info
β
query dispatched in dev
osctrl> exit
Architecture
β’ DataStore faΓ§ade (shell_store.go) branches `--api`/`--db` once and returns plain DTOs (nodeRow, queryRow, β¦), so command handlers stay mode-agnostic and decoupled from model JSON shapes.
β’ REPL core (shell.go) + command registry/help/completion (shell_commands.go) + per-module handlers (shell_modules.go).
β’ Custom readline (shell_readline.go) built on the already-vendored <http://golang.org/x/term|golang.org/x/term> (no offline readline lib was available): line editing, β/β history, Tab completion, Ctrl-C/Ctrl-D, plus a non-TTY fallback so commands can be piped in for scripting.
Surface
β’ Modules: nodes, queries, carves, environments, tags, users, audit, settings.
β’ Global: `help`/`?` (context-aware), use <module>, back, set env <name> / set <option> <value>, show environments, stats, exit.
β’ Actions: run query/carve (with set options), complete/expire/delete, delete node, tag node, add/edit/delete tags, add/edit/delete users, permissions, environment enroll/remove actions (extend/rotate/expire/notexpire), settings add/update/delete β destructive ops prompt for confirmation.
UX polish (shell_ui.go)
β’ ANSI colors (cyan headers, green/red status, magenta contexts), auto-disabled when stdout isn't a TTY; OSCTRL_CLI_FORCE_COLOR=1 forces them for piping into a color-aware pager.
β’ Braille spinner animates during in-flight fetches (TTY only; never pollutes piped output).
β’ Emoji accents for modules/banner/stats.
β’ Display-width math via <http://github.com/mattn/go-runewidth|github.com/mattn/go-runewidth> so colored cells and emoji align correctly.
Bug fixes rolled in
β’ nodes list showed nothing on under-configured DBs: inactive_hours defaulted to 0, making the active filter match nothing β now falls back to 24h; list also defaults to all so nodes are always visible.
β’ Scrambled output: gorm's trace logger was writing \r + SQL lines to stdout, overwriting the prompt β the shell now silences that logger (logger.Discard).
β’ Misaligned help module list: emoji glyphs had inconsistent display widths β icons normalized to width-2 glyphs and padded via `runewidth`; table separator now spans the full table width.
Validation
β’ go build ./..., go test ./cmd/cli, go vet, golangci-lint run ./cmd/cli/... β all clean (0 lint issues).
β’ New unit tests: store mapping (nodeToRow active/inactive, queryStatus, csvSplit, parseHidden) and UI helpers (ansiStrip, visibleWidth, paint, colorCell, spinGet non-TTY path).
β’ Smoke-tested in both piped (scripted commands) and PTY (interactive) modes against a seeded SQLite backend.
Files
β’ cmd/cli/main.go β register shell command.
β’ cmd/cli/shell.go β REPL loop, dispatch, prompt, ASCII table renderer, gorm silencing.
β’ cmd/cli/shell_commands.go β global commands, module registry, help, Tab completion.
β’ cmd/cli/shell_modules.go β per-module command handlers.
β’ cmd/cli/shell_readline.go β raw-mode line editor with history/completion.
β’ cmd/cli/shell_store.go β DataStore interface + `apiStore`/`dbStore` implementations + DTOs.
β’ cmd/cli/shell_store_test.go, cmd/cli/shell_ui_test.go β tests.
β’ cmd/cli/shell_ui.go β colors, spinner, emoji, runewidth widths.
No new external dependencies (term, tablewriter, runewidth were already in go.mod).
Known limitations
β’ Settings management and permission reads are --db only (the REST API does not expose them).
β’ Full environment add/update/flags/secret/quick-add still route through the environment CLI subcommand; the shell covers env list/show/delete and all enroll/remove URL actions.
β’ Query results in --db mode require the logging sink to be wired; --api mode works.
Security notes
β’ Destructive actions (delete node/query/carve/tag/user/env, settings delete) require an explicit yes/no confirmation.
β’ The shell reuses the existing cliWrapper backend init and the existing API client / GORM manager methods verbatim β no new privileged code paths, no new secret handling.
jmpsec/osctrlGitHub
07/08/2026, 7:24 AM