<#884 Adding interactive console mode to `osctrl-c...
# osctrl
g
#884 Adding interactive console mode to `osctrl-cli` Pull request opened by javuto feat(cli): add Metasploit-style interactive shell to osctrl-cli Problem
osctrl-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.
Copy code
$ 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/osctrl