Modules#

An overview of the packages that make up ocom. The API reference below is generated automatically from the source docstrings.

Application (ocom.tui.app)#

The Textual application entry point.

Main Textual application for ocom.

final class ocom.tui.app.OcomApp(config=None)[source]#

Bases: App[None]

Network tools manager TUI application.

Create the app, loading config from disk when none is given.

Parameters:

config (AppConfig | None)

TITLE: str | None = 'ocom'#

A class variable to set the default title for the application.

To update the title while the app is running, you can set the [title][textual.app.App.title] attribute. See also [the Screen.TITLE attribute][textual.screen.Screen.TITLE].

SUB_TITLE: str | None = 'Network Tools Manager'#

A class variable to set the default sub-title for the application.

To update the sub-title while the app is running, you can set the [sub_title][textual.app.App.sub_title] attribute. See also [the Screen.SUB_TITLE attribute][textual.screen.Screen.SUB_TITLE].

CSS_PATH: ClassVar[CSSPathType | None] = PosixPath('/home/runner/work/ocom/ocom/src/ocom/tui/styles/app.tcss')#

File paths to load CSS from.

on_mount()[source]#

Push the main screen on startup.

Return type:

None

ocom.tui.app.run()[source]#

Run the ocom application.

Return type:

None

Configuration (ocom.core.config)#

Pydantic-settings based configuration.

Configuration management for ocom.

class ocom.core.config.AppConfig(_case_sensitive=None, _nested_model_default_partial_update=None, _env_prefix=None, _env_prefix_target=None, _env_file=PosixPath('.'), _env_file_encoding=None, _env_ignore_empty=None, _env_nested_delimiter=None, _env_nested_max_split=None, _env_parse_none_str=None, _env_parse_enums=None, _cli_prog_name=None, _cli_parse_args=None, _cli_settings_source=None, _cli_parse_none_str=None, _cli_hide_none_type=None, _cli_avoid_json=None, _cli_enforce_required=None, _cli_use_class_docs_for_groups=None, _cli_exit_on_error=None, _cli_prefix=None, _cli_flag_prefix_char=None, _cli_implicit_flags=None, _cli_ignore_unknown_args=None, _cli_kebab_case=None, _cli_shortcuts=None, _secrets_dir=None, _build_sources=None, *, general=<factory>, openvpn=<factory>, spoofdpi=<factory>, warp=<factory>, tailscale=<factory>, goodbyedpi=<factory>)[source]#

Bases: BaseSettings

Main application configuration.

Create a new model by parsing and validating input data from keyword arguments.

Raises [ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.

self is explicitly positional-only to allow self as a field name.

Parameters:
  • _case_sensitive (bool | None)

  • _nested_model_default_partial_update (bool | None)

  • _env_prefix (str | None)

  • _env_prefix_target (EnvPrefixTarget | None)

  • _env_file (DotenvType | None)

  • _env_file_encoding (str | None)

  • _env_ignore_empty (bool | None)

  • _env_nested_delimiter (str | None)

  • _env_nested_max_split (int | None)

  • _env_parse_none_str (str | None)

  • _env_parse_enums (bool | None)

  • _cli_prog_name (str | None)

  • _cli_parse_args (bool | list[str] | tuple[str, ...] | None)

  • _cli_settings_source (CliSettingsSource[Any] | None)

  • _cli_parse_none_str (str | None)

  • _cli_hide_none_type (bool | None)

  • _cli_avoid_json (bool | None)

  • _cli_enforce_required (bool | None)

  • _cli_use_class_docs_for_groups (bool | None)

  • _cli_exit_on_error (bool | None)

  • _cli_prefix (str | None)

  • _cli_flag_prefix_char (str | None)

  • _cli_implicit_flags (bool | Literal['dual', 'toggle'] | None)

  • _cli_ignore_unknown_args (bool | None)

  • _cli_kebab_case (bool | Literal['all', 'no_enums'] | None)

  • _cli_shortcuts (Mapping[str, str | list[str]] | None)

  • _secrets_dir (PathType | None)

  • _build_sources (tuple[tuple[PydanticBaseSettingsSource, ...], dict[str, Any]] | None)

  • general (GeneralConfig)

  • openvpn (OpenVPNConfig)

  • spoofdpi (SpoofDPIConfig)

  • warp (WarpConfig)

  • tailscale (TailscaleConfig)

  • goodbyedpi (GoodbyeDPIConfig)

model_config: ClassVar[SettingsConfigDict] = {'arbitrary_types_allowed': True, 'case_sensitive': False, 'cli_avoid_json': False, 'cli_enforce_required': False, 'cli_exit_on_error': True, 'cli_flag_prefix_char': '-', 'cli_hide_none_type': False, 'cli_ignore_unknown_args': False, 'cli_implicit_flags': False, 'cli_kebab_case': False, 'cli_parse_args': None, 'cli_parse_none_str': None, 'cli_prefix': '', 'cli_prog_name': None, 'cli_shortcuts': None, 'cli_use_class_docs_for_groups': False, 'enable_decoding': True, 'env_file': None, 'env_file_encoding': None, 'env_ignore_empty': False, 'env_nested_delimiter': None, 'env_nested_max_split': None, 'env_parse_enums': None, 'env_parse_none_str': None, 'env_prefix': '', 'env_prefix_target': 'variable', 'extra': 'ignore', 'json_file': None, 'json_file_encoding': None, 'nested_model_default_partial_update': False, 'protected_namespaces': ('model_validate', 'model_dump', 'settings_customise_sources'), 'secrets_dir': None, 'toml_file': None, 'validate_default': True, 'yaml_config_section': None, 'yaml_file': None, 'yaml_file_encoding': None}#

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

classmethod settings_customise_sources(settings_cls, init_settings, env_settings, dotenv_settings, file_secret_settings)[source]#

Configure settings sources.

Values passed explicitly (init_settings) take priority so that load(path) can supply file contents directly; the default config at get_config_path() is used as a fallback when it exists.

Returns:

The ordered tuple of settings sources to read from.

Parameters:
  • settings_cls (type[BaseSettings])

  • init_settings (PydanticBaseSettingsSource)

  • env_settings (PydanticBaseSettingsSource)

  • dotenv_settings (PydanticBaseSettingsSource)

  • file_secret_settings (PydanticBaseSettingsSource)

Return type:

tuple[PydanticBaseSettingsSource, …]

classmethod load(path=None)[source]#

Load configuration from TOML file.

Parameters:
  • path – Path to config file. Uses default if None.

  • path (Path | None)

Returns:

Loaded AppConfig, or defaults if file doesn’t exist.

Return type:

AppConfig

save(path=None)[source]#

Save configuration to TOML file.

Parameters:
  • path – Path to config file. Uses default if None.

  • path (Path | None)

Return type:

None

class ocom.core.config.GeneralConfig(*, refresh_interval=2, auto_connect=False)[source]#

Bases: BaseModel

General application configuration.

Create a new model by parsing and validating input data from keyword arguments.

Raises [ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.

self is explicitly positional-only to allow self as a field name.

Parameters:
  • refresh_interval (int)

  • auto_connect (bool)

model_config: ClassVar[ConfigDict] = {}#

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

class ocom.core.config.GoodbyeDPIConfig(*, enabled=True, mode=9, block_quic=True)[source]#

Bases: BaseModel

GoodbyeDPI-specific configuration (Windows only).

Create a new model by parsing and validating input data from keyword arguments.

Raises [ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.

self is explicitly positional-only to allow self as a field name.

Parameters:
model_config: ClassVar[ConfigDict] = {}#

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

class ocom.core.config.OpenVPNConfig(*, enabled=True, config_dirs=['~/.openvpn', '~/vpn-configs'], default_config='')[source]#

Bases: BaseModel

OpenVPN-specific configuration.

Create a new model by parsing and validating input data from keyword arguments.

Raises [ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.

self is explicitly positional-only to allow self as a field name.

Parameters:
model_config: ClassVar[ConfigDict] = {}#

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

class ocom.core.config.SpoofDPIConfig(*, enabled=True, dns_addr='8.8.8.8:53', dns_mode='https', port=8080, system_proxy=False)[source]#

Bases: BaseModel

SpoofDPI-specific configuration.

Create a new model by parsing and validating input data from keyword arguments.

Raises [ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.

self is explicitly positional-only to allow self as a field name.

Parameters:
  • enabled (bool)

  • dns_addr (str)

  • dns_mode (Literal['udp', 'https', 'system'])

  • port (int)

  • system_proxy (bool)

model_config: ClassVar[ConfigDict] = {}#

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

class ocom.core.config.TailscaleConfig(*, enabled=True)[source]#

Bases: BaseModel

Tailscale-specific configuration.

Create a new model by parsing and validating input data from keyword arguments.

Raises [ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.

self is explicitly positional-only to allow self as a field name.

Parameters:

enabled (bool)

model_config: ClassVar[ConfigDict] = {}#

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

class ocom.core.config.WarpConfig(*, enabled=True, mode='warp')[source]#

Bases: BaseModel

Cloudflare WARP-specific configuration.

Create a new model by parsing and validating input data from keyword arguments.

Raises [ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.

self is explicitly positional-only to allow self as a field name.

Parameters:
  • enabled (bool)

  • mode (Literal['warp', 'doh', 'proxy'])

model_config: ClassVar[ConfigDict] = {}#

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

ocom.core.config.get_config_dir()[source]#

Get the config directory (Linux-style ~/.config/ocom on all platforms).

Returns:

The per-user config directory path.

Return type:

Path

ocom.core.config.get_config_path()[source]#

Get the path to the config file.

Returns:

The path to the config.toml file.

Return type:

Path

Core (ocom.core)#

Shared abstractions: the BaseTool interface and the subprocess ProcessManager.

Base tool abstraction for network/privacy tools.

class ocom.core.tool.BaseTool[source]#

Bases: ABC

Abstract base class for all network/privacy tools.

Subclasses must implement all abstract methods to integrate a new tool into the ocom TUI.

Initialize the tool in the UNAVAILABLE state.

property status: ToolStatus#

Current tool status.

property error_message: str | None#

Error message if status is ERROR.

property current_config: str | None#

Currently active config file, if any.

async check_available()[source]#

Check if the tool is installed on the system.

Default implementation checks if self.command exists in PATH.

Returns:

True if the tool is available, False otherwise.

Return type:

bool

abstractmethod async start(config)[source]#

Start the tool with the given configuration.

Parameters:
  • config – Tool configuration including selected config file and options.

  • config (ToolConfig)

Returns:

True if started successfully, False otherwise.

Return type:

bool

abstractmethod async stop()[source]#

Stop the tool.

Returns:

True if stopped successfully, False otherwise.

Return type:

bool

abstractmethod async refresh_status()[source]#

Refresh and return the current status.

This is called periodically to update the UI.

Returns:

Current ToolStatus.

Return type:

ToolStatus

get_config_files(config)[source]#

Get list of available config files.

Override this for tools that use config files.

Parameters:
  • config – Tool configuration with directories to scan.

  • config (ToolConfig)

Returns:

List of config file paths.

Return type:

list[str]

get_status_text()[source]#

Get human-readable status text for display.

Can be overridden for tool-specific status details.

Returns:

A human-readable string describing the current status.

Return type:

str

class ocom.core.tool.ToolConfig(enabled=True, config_file=None, config_dirs=<factory>, extra_args=<factory>, options=<factory>)[source]#

Bases: object

Configuration for a specific tool instance.

Parameters:
class ocom.core.tool.ToolStatus(*values)[source]#

Bases: Enum

Status of a network tool.

property is_transitioning: bool#

Check if status is a transitional state.

property can_start: bool#

Check if tool can be started from this state.

property can_stop: bool#

Check if tool can be stopped from this state.

Process management for running network tools.

class ocom.core.process.ProcessManager[source]#

Bases: object

Manages subprocess lifecycle for network tools.

Provides utilities for: - Running commands and capturing output - Starting long-running processes - Monitoring process output - Graceful shutdown

static find_command(command)[source]#

Find the full path to a command.

Parameters:
  • command – Command name to find.

  • command (str)

Returns:

Full path to the command, or None if not found.

Return type:

str | None

async static run_command(args, *, timeout=30.0, check=False)[source]#

Run a command and wait for completion.

Parameters:
  • args – Command and arguments as a list.

  • timeout – Maximum time to wait in seconds.

  • check – Raise exception if command fails.

  • args (list[str])

  • timeout (float | None)

  • check (bool)

Returns:

ProcessResult with output and return code.

Raises:
Return type:

ProcessResult

async static stop_process(proc, *, timeout=5.0)[source]#

Gracefully stop a process.

Uses terminate() first (cross-platform), then kill() if needed.

Parameters:
  • proc – Process to stop.

  • timeout – Time to wait for graceful shutdown.

  • proc (Process)

  • timeout (float)

Returns:

True if process was stopped.

Return type:

bool

static is_process_running(proc)[source]#

Check if a process is still running.

Returns:

True if the process exists and has not exited.

Parameters:

proc (Process | None)

Return type:

bool

async static check_port_in_use(port, host='127.0.0.1')[source]#

Check if a port is in use (for proxy tools).

Parameters:
  • port – Port number to check.

  • host – Host to check on.

  • port (int)

  • host (str)

Returns:

True if port is in use.

Return type:

bool

class ocom.core.process.ProcessResult(returncode, stdout, stderr)[source]#

Bases: object

Result of a process execution.

Parameters:
property success: bool#

Check if process completed successfully.

ocom.core.process.is_admin()[source]#

Check if the current process has elevated privileges.

On Windows: checks if running as Administrator. On Unix: checks if running as root (uid 0).

Returns:

True if running with elevated privileges.

Return type:

bool

Tools (ocom.tools)#

Concrete tool implementations (OpenVPN, SpoofDPI, GoodbyeDPI, WARP, Tailscale).

Network tool implementations.

class ocom.tools.GoodbyeDPITool[source]#

Bases: BaseTool

GoodbyeDPI anti-censorship tool for Windows.

GoodbyeDPI is a DPI bypass utility that works at the packet level. It modifies packets to evade Deep Packet Inspection. Requires Administrator privileges on Windows.

Initialize the GoodbyeDPI tool with its default mode.

get_status_text()[source]#

Get GoodbyeDPI-specific status text.

Returns:

A short human-readable status string.

Return type:

str

async refresh_status()[source]#

Refresh GoodbyeDPI status.

Returns:

The current ToolStatus.

Return type:

ToolStatus

async start(config)[source]#

Start GoodbyeDPI.

Parameters:
  • config – Can contain options for mode (1-9), block_quic (bool).

  • config (ToolConfig)

Returns:

True if started successfully.

Return type:

bool

async stop()[source]#

Stop GoodbyeDPI.

Returns:

True if the process was stopped.

Return type:

bool

class ocom.tools.OpenVPNTool[source]#

Bases: BaseTool

OpenVPN connection manager.

Manages OpenVPN connections using .ovpn configuration files. Requires sudo on Unix, Administrator on Windows.

Initialize the OpenVPN tool with an empty output buffer.

get_config_files(config)[source]#

Find all .ovpn files in configured directories.

Parameters:
  • config – Tool configuration listing directories to scan.

  • config (ToolConfig)

Returns:

A sorted list of matching config file paths.

Return type:

list[str]

async refresh_status()[source]#

Refresh OpenVPN status.

Returns:

The current ToolStatus.

Return type:

ToolStatus

async start(config)[source]#

Start OpenVPN with the specified config file.

Parameters:
  • config – Must have config_file set to an .ovpn path.

  • config (ToolConfig)

Returns:

True if connection initiated successfully.

Return type:

bool

async stop()[source]#

Stop the OpenVPN connection.

Returns:

True if the connection was stopped.

Return type:

bool

class ocom.tools.SpoofDPITool[source]#

Bases: BaseTool

SpoofDPI anti-censorship proxy.

SpoofDPI is a simple DPI bypass tool that runs as a local proxy. It modifies packets to evade Deep Packet Inspection.

Initialize the SpoofDPI tool with its default proxy port.

get_status_text()[source]#

Get SpoofDPI-specific status text.

Returns:

A short human-readable status string.

Return type:

str

async refresh_status()[source]#

Refresh SpoofDPI status.

Returns:

The current ToolStatus.

Return type:

ToolStatus

async start(config)[source]#

Start SpoofDPI proxy.

Parameters:
  • config – Can contain options for dns_addr, dns_mode, port, system_proxy.

  • config (ToolConfig)

Returns:

True if the proxy started successfully.

Return type:

bool

async stop()[source]#

Stop SpoofDPI proxy.

Returns:

True if the proxy was stopped.

Return type:

bool

class ocom.tools.TailscaleTool[source]#

Bases: BaseTool

Tailscale mesh VPN connection manager.

Uses the tailscale CLI to bring the node up/down and read status. Tailscale runs as a system daemon (tailscaled), so we just send it commands - much like the WARP tool.

Mesh-only: this manages the plain tailscale up/down lifecycle and does not configure an exit node. In mesh mode Tailscale only routes the 100.64.0.0/10 CGNAT range and does not seize the default route, so it coexists with OpenVPN/WARP - hence conflicts_with is empty. If exit node support is added later (full-tunnel), revisit the conflict list.

Initialize the tool in the UNAVAILABLE state.

async check_available()[source]#

Check if the tailscale CLI is installed and read daemon status.

Returns:

True if the CLI is available.

Return type:

bool

async refresh_status()[source]#

Check Tailscale connection status via tailscale status –json.

Returns:

The current ToolStatus.

Return type:

ToolStatus

async start(config)[source]#

Bring the Tailscale node up.

Parameters:
  • config – Unused for mesh mode; reserved for future options (e.g. exit node, accept-routes).

  • config (ToolConfig)

Returns:

True if the node came up successfully.

Return type:

bool

async stop()[source]#

Bring the Tailscale node down.

Returns:

True if the node was brought down successfully.

Return type:

bool

class ocom.tools.WarpTool[source]#

Bases: BaseTool

Cloudflare WARP connection manager.

Uses warp-cli to connect/disconnect from Cloudflare’s WARP service. WARP runs as a system daemon, so we just send commands to it.

Initialize the tool in the UNAVAILABLE state.

async check_available()[source]#

Check if warp-cli is installed and daemon is running.

Returns:

True if the CLI is available.

Return type:

bool

async refresh_status()[source]#

Check WARP connection status.

Returns:

The current ToolStatus.

Return type:

ToolStatus

async start(config)[source]#

Connect to WARP.

Parameters:
  • config – Can contain mode option (warp, doh, proxy).

  • config (ToolConfig)

Returns:

True if connection initiated.

Return type:

bool

async stop()[source]#

Disconnect from WARP.

Returns:

True if disconnected successfully.

Return type:

bool

User interface (ocom.tui)#

Textual screens and widgets.

Main dashboard screen.

final class ocom.tui.screens.main.ConfigSelectorScreen(tool, configs, *, name=None, id=None, classes=None)[source]#

Bases: ModalScreen[str | None]

Modal screen for selecting a config file.

Store the tool and its available config files.

Parameters:
BINDINGS: ClassVar[list[Binding | tuple[str, str] | tuple[str, str, str]]] = [Binding(key='escape', action='cancel', description='Cancel', show=True, key_display=None, priority=False, tooltip='', id=None, system=False, group=None)]#

A list of key bindings.

on_mount()[source]#

Focus the option list when mounted.

Return type:

None

on_button_pressed(event)[source]#

Handle button press.

Parameters:

event (Pressed)

Return type:

None

on_option_list_option_selected(event)[source]#

Handle config selection.

Parameters:

event (OptionSelected)

Return type:

None

action_cancel()[source]#

Cancel and close modal.

Return type:

None

can_focus: bool = False#

Widget may receive focus.

can_focus_children: bool = True#

Widget’s children may receive focus.

final class ocom.tui.screens.main.PasswordPromptScreen(tool, *, name=None, id=None, classes=None)[source]#

Bases: ModalScreen[str | None]

Modal screen for entering sudo password.

Store the tool that requires authentication.

Parameters:
BINDINGS: ClassVar[list[Binding | tuple[str, str] | tuple[str, str, str]]] = [Binding(key='escape', action='cancel', description='Cancel', show=True, key_display=None, priority=False, tooltip='', id=None, system=False, group=None)]#

A list of key bindings.

on_mount()[source]#

Focus the password input.

Return type:

None

on_button_pressed(event)[source]#

Handle button press.

Parameters:

event (Pressed)

Return type:

None

on_input_submitted(_event)[source]#

Handle Enter key in password field.

Parameters:

_event (Submitted)

Return type:

None

action_cancel()[source]#

Cancel and close modal.

Return type:

None

can_focus: bool = False#

Widget may receive focus.

can_focus_children: bool = True#

Widget’s children may receive focus.

Tool card widget for displaying tool status.

final class ocom.tui.widgets.tool_card.ToolCard(tool, *, name=None, id=None, classes=None)[source]#

Bases: Static

A card widget displaying a single tool’s status and controls.

Create a card bound to tool, forwarding widget kwargs.

Parameters:
final class ToolAction(tool, action)[source]#

Bases: Message

Message sent when a tool action is requested.

Store the target tool and requested action.

Parameters:
handler_name: ClassVar[str] = 'on_tool_card_tool_action'#

Name of the default message handler.

on_mount()[source]#

Update UI on mount.

Return type:

None

watch_status(_new_status)[source]#

React to status changes.

Parameters:

_new_status (ToolStatus)

Return type:

None

on_button_pressed(event)[source]#

Handle button presses.

Parameters:

event (Pressed)

Return type:

None

refresh_status(new_status)[source]#

Update the card with a new status.

Parameters:

new_status (ToolStatus)

Return type:

None

can_focus: bool = False#

Widget may receive focus.

can_focus_children: bool = True#

Widget’s children may receive focus.

Log panel widget for displaying tool output.

final class ocom.tui.widgets.log_panel.LogPanel(content='', *, expand=False, shrink=False, markup=True, name=None, id=None, classes=None, disabled=False)[source]#

Bases: Static

A panel for displaying logs from running tools.

Initialize a Widget.

Parameters:
  • *children – Child widgets.

  • name – The name of the widget.

  • id – The ID of the widget in the DOM.

  • classes – The CSS classes for the widget.

  • disabled – Whether the widget is disabled or not.

  • markup – Enable content markup?

  • content (VisualType)

  • expand (bool)

  • shrink (bool)

  • markup (bool)

  • name (str | None)

  • id (str | None)

  • classes (str | None)

  • disabled (bool)

add_log(tool_name, message)[source]#

Add a log entry from a tool.

Named add_log rather than log to avoid shadowing the log logger attribute that Textual’s MessagePump provides on every widget.

Parameters:
  • tool_name (str)

  • message (str)

Return type:

None

log_system(message)[source]#

Add a system log entry.

Parameters:

message (str)

Return type:

None

clear()[source]#

Clear all log entries.

Return type:

None

can_focus: bool = False#

Widget may receive focus.

can_focus_children: bool = True#

Widget’s children may receive focus.