GitHub
09/04/2026, 7:22 AMpkg/apiclient
Moves the CLI's API client out of package main so other binaries can import it.
Groundwork for an MCP server (cmd/osctrl-mcp), which needs the same typed client
that osctrl-cli already has — and which we'd otherwise have to duplicate.
What moved
13 files from cmd/cli/api*.go → pkg/apiclient/ (tracked as git renames, history preserved):
• api.go → client.go, and api-{alerts,audit,carve,console,environment,extras,login,node,query,tag,user}.go → {name}.go
• api_extras_test.go → extras_test.go
The client only ever depended on pkg/*, stdlib, zerolog, and viper, so it moved cleanly.
Public surface
Five identifiers leaked through exported method signatures and are now exported —
cmd/cli names them in alert.go, shell_module_extras.go, and `shell_store_extras.go`:
alertRuleJSON → AlertRuleJSON, alertChannelJSON → AlertChannelJSON,
fileExplorerSessionResponse → FileExplorerSessionResponse, featuresResponse → FeaturesResponse,
consoleNodeInfo → ConsoleNodeInfo (a field type inside the already-public ConsoleSessionResponse).
Capitalized rather than renamed, to keep the diff mechanical.
loadAPIConfiguration / writeAPIConfiguration → LoadConfiguration / `WriteConfiguration`:
future consumers read the same osctrl-api.json, so these belong with the client.
Behavior changes
CreateAPI now returns (OsctrlAPI, error). It previously called log.Fatal() on a
malformed URL or unreadable cert pool — acceptable in a CLI, fatal in a long-lived server that
should return an error instead of exiting. The three call sites in cmd/cli/main.go propagate,
so the CLI's observable behavior is unchanged:
FTL ❌ error creating API client - invalid url - parse "ht <tp://bad> url": ...
projectName → apiclient.ConfigKey — the one package main const the client referenced,
inlined so the package stands alone.
Tests
api_extras_test.go mixed client round-trip tests with CLI shell/UI tests. The client tests
stayed with the client; TestStoreCastErrors, TestAPIStoreImplementsExtraSurfaces, and
TestColumnKeysAndIndent moved to cmd/cli/shell_store_extras_test.go. The last of those is a
pure type assertion, so it now builds a client against an unreachable URL rather than needing
the httptest server.
Verification
go build ./..., go vet, and gofmt -l all clean; all 38 packages pass. Smoke-tested the
rebuilt osctrl-cli binary.
Follow-up
The User-Agent is still hardcoded osctrl-cli-http-client/<version> (pkg/apiclient/client.go:57).
Consumers should be able to identify themselves so operators can distinguish traffic in the API
logs — left alone here to keep this a pure move.
jmpsec/osctrlGitHub
09/04/2026, 7:27 AM