Link support codes to agent sessions

- Server: Accept support_code param in WebSocket connection
- Server: Link code to session when agent connects, mark as connected
- Server: Mark code as completed when agent disconnects
- Agent: Accept support code from command line argument
- Agent: Send hostname and support_code in WebSocket params
- Portal: Trigger agent download with code in filename
- Portal: Show code reminder in download instructions
- Dashboard: Add machines list fetching (Access tab)
- Add TODO.md for feature tracking

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
2025-12-28 14:11:52 -07:00
parent 9af59158b2
commit 1d2ca47771
10 changed files with 447 additions and 21 deletions

View File

@@ -21,6 +21,10 @@ pub struct Config {
/// Optional hostname override
pub hostname_override: Option<String>,
/// Support code for one-time support sessions (set via command line)
#[serde(skip)]
pub support_code: Option<String>,
/// Capture settings
#[serde(default)]
pub capture: CaptureConfig,
@@ -119,6 +123,9 @@ impl Config {
let _ = config.save();
}
// support_code is always None when loading from file (set via CLI)
config.support_code = None;
return Ok(config);
}
@@ -137,6 +144,7 @@ impl Config {
api_key,
agent_id,
hostname_override: std::env::var("GURUCONNECT_HOSTNAME").ok(),
support_code: None, // Set via CLI
capture: CaptureConfig::default(),
encoding: EncodingConfig::default(),
};

View File

@@ -1,6 +1,12 @@
//! GuruConnect Agent - Remote Desktop Agent for Windows
//!
//! Provides screen capture, input injection, and remote control capabilities.
//!
//! Usage:
//! guruconnect-agent.exe [support_code]
//!
//! If a support code is provided, the agent will connect using that code
//! for a one-time support session.
mod capture;
mod config;
@@ -28,8 +34,25 @@ async fn main() -> Result<()> {
info!("GuruConnect Agent v{}", env!("CARGO_PKG_VERSION"));
// Parse command line arguments
let args: Vec<String> = std::env::args().collect();
let support_code = if args.len() > 1 {
let code = args[1].trim().to_string();
// Validate it looks like a 6-digit code
if code.len() == 6 && code.chars().all(|c| c.is_ascii_digit()) {
info!("Support code provided: {}", code);
Some(code)
} else {
info!("Invalid support code format, ignoring: {}", code);
None
}
} else {
None
};
// Load configuration
let config = config::Config::load()?;
let mut config = config::Config::load()?;
config.support_code = support_code;
info!("Loaded configuration for server: {}", config.server_url);
// Run the agent

View File

@@ -46,10 +46,13 @@ impl SessionManager {
pub async fn connect(&mut self) -> Result<()> {
self.state = SessionState::Connecting;
let hostname = self.config.hostname();
let transport = WebSocketTransport::connect(
&self.config.server_url,
&self.config.agent_id,
&self.config.api_key,
Some(&hostname),
self.config.support_code.as_deref(),
).await?;
self.transport = Some(transport);

View File

@@ -29,15 +29,35 @@ pub struct WebSocketTransport {
impl WebSocketTransport {
/// Connect to the server
pub async fn connect(url: &str, agent_id: &str, api_key: &str) -> Result<Self> {
// Append agent_id and API key as query parameters
pub async fn connect(
url: &str,
agent_id: &str,
api_key: &str,
hostname: Option<&str>,
support_code: Option<&str>,
) -> Result<Self> {
// Build query parameters
let mut params = format!("agent_id={}&api_key={}", agent_id, api_key);
if let Some(hostname) = hostname {
params.push_str(&format!("&hostname={}", urlencoding::encode(hostname)));
}
if let Some(code) = support_code {
params.push_str(&format!("&support_code={}", code));
}
// Append parameters to URL
let url_with_params = if url.contains('?') {
format!("{}&agent_id={}&api_key={}", url, agent_id, api_key)
format!("{}&{}", url, params)
} else {
format!("{}?agent_id={}&api_key={}", url, agent_id, api_key)
format!("{}?{}", url, params)
};
tracing::info!("Connecting to {} as agent {}", url, agent_id);
if let Some(code) = support_code {
tracing::info!("Using support code: {}", code);
}
let (ws_stream, response) = connect_async(&url_with_params)
.await