This implementation allows each platform to choose how to launch Steam (on Linux, we use the snap as the examples suggested before), and keeps Steam alive after an activity exits so that save sync, game updates, etc. can continue to run. Change written by Codex 5.2 on medium: Consider this GitHub issue https://github.com/aarmea/shepherd-launcher/issues/4. On Linux, an activity that uses the "steam" type should launch Steam via the snap as shown in the example configuration in this repository. Go ahead and implement the feature. I'm expecting one of the tricky bits to be killing the activity while keeping Steam alive, as we can no longer just kill the Steam snap cgroup. |
||
|---|---|---|
| .. | ||
| src | ||
| Cargo.toml | ||
| README.md | ||
shepherd-api
Protocol types for Shepherd IPC communication.
Overview
This crate defines the stable API between the Shepherd service (shepherdd) and its clients (launcher UI, HUD overlay, admin tools). It contains:
- Commands - Requests from clients to the service
- Responses - Service replies to commands
- Events - Asynchronous notifications from service to clients
- Shared types - Entry views, session info, reason codes, etc.
Purpose
shepherd-api establishes the contract between components, ensuring:
- Stability - Versioned protocol with backward compatibility
- Type safety - Strongly typed messages prevent protocol errors
- Decoupling - Clients and service can evolve independently
API Version
use shepherd_api::API_VERSION;
// Current API version
assert_eq!(API_VERSION, 1);
Key Types
Commands
Commands are requests sent by clients to the service:
use shepherd_api::Command;
// Request available entries
let cmd = Command::ListEntries;
// Request to launch an entry
let cmd = Command::Launch {
entry_id: "minecraft".into()
};
// Request to stop current session
let cmd = Command::StopCurrent {
mode: StopMode::Graceful
};
// Subscribe to real-time events
let cmd = Command::SubscribeEvents;
Available commands:
GetState- Get full service state snapshotListEntries- List all entries with availabilityLaunch { entry_id }- Launch an entryStopCurrent { mode }- Stop the current sessionReloadConfig- Reload configuration (admin only)SubscribeEvents- Subscribe to event streamGetHealth- Get service health statusSetVolume { level }- Set system volumeGetVolume- Get current volume
Events
Events are pushed from the service to subscribed clients:
use shepherd_api::{Event, EventPayload};
// Events received by clients
match event.payload {
EventPayload::StateChanged(snapshot) => { /* Update UI */ }
EventPayload::SessionStarted(info) => { /* Show HUD */ }
EventPayload::WarningIssued { threshold, remaining, severity, message } => { /* Alert user */ }
EventPayload::SessionExpired { session_id } => { /* Time's up */ }
EventPayload::SessionEnded { session_id, reason } => { /* Return to launcher */ }
EventPayload::PolicyReloaded { entry_count } => { /* Refresh entry list */ }
EventPayload::VolumeChanged(info) => { /* Update volume display */ }
}
Entry Views
Entries as presented to UIs:
use shepherd_api::EntryView;
let view: EntryView = /* from service */;
if view.enabled {
// Entry can be launched
println!("Max run time: {:?}", view.max_run_if_started_now);
} else {
// Entry unavailable, show reasons
for reason in &view.reasons {
match reason {
ReasonCode::OutsideTimeWindow { next_window_start } => { /* ... */ }
ReasonCode::QuotaExhausted { used, quota } => { /* ... */ }
ReasonCode::CooldownActive { available_at } => { /* ... */ }
ReasonCode::SessionActive { entry_id, remaining } => { /* ... */ }
// ...
}
}
}
Session Info
Information about active sessions:
use shepherd_api::{SessionInfo, SessionState};
let session: SessionInfo = /* from snapshot */;
match session.state {
SessionState::Launching => { /* Show spinner */ }
SessionState::Running => { /* Show countdown */ }
SessionState::Warned => { /* Highlight urgency */ }
SessionState::Expiring => { /* Terminating... */ }
SessionState::Ended => { /* Session over */ }
}
Reason Codes
Structured explanations for unavailability:
OutsideTimeWindow- Not within allowed time windowQuotaExhausted- Daily time limit reachedCooldownActive- Must wait after previous sessionSessionActive- Another session is runningUnsupportedKind- Host doesn't support this entry typeDisabled- Entry explicitly disabled in config
Design Philosophy
- Service is authoritative - Clients display state, service enforces policy
- Structured reasons - UIs can explain "why is this unavailable?"
- Event-driven - Clients subscribe and react to changes
- Serializable - All types derive
Serialize/Deserializefor JSON transport
Dependencies
serde- Serialization/deserializationchrono- Timestamp typesshepherd-util- ID types