Skip to content

Commit b6e477d

Browse files
committed
feat: replace cortex-gui with full-featured desktop from cortex-app
- Replace stub implementations with full Linear integration - Replace stub implementations with full workspace_scripts support - Export linear and workspace_scripts modules from cortex-engine - Fix create_default_client imports to use cortex_common - Update dependency paths to point to cortex-cli workspace - Re-enable all Tauri commands in mod.rs and lib.rs This brings feature parity with the cortex-app GUI: - Full Linear API integration (teams, issues, OAuth) - Workspace scripts (setup, dev, archive) - All 55+ Tauri commands now functional
1 parent fad324f commit b6e477d

14 files changed

Lines changed: 355 additions & 114 deletions

File tree

cortex-engine/src/billing_client.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ use reqwest::Client;
1414
use serde::{Deserialize, Serialize};
1515
use tracing::{debug, error, info};
1616

17-
use crate::api_client::create_default_client;
17+
use cortex_common::create_default_client;
1818
use crate::error::{CortexError, Result};
1919

2020
// ============================================================================
@@ -289,7 +289,7 @@ impl BillingClient {
289289
pub fn new() -> Result<Self> {
290290
let base_url =
291291
std::env::var("CORTEX_API_URL").unwrap_or_else(|_| DEFAULT_API_URL.to_string());
292-
let client = create_default_client()?;
292+
let client = create_default_client().map_err(CortexError::internal)?;
293293

294294
Ok(Self {
295295
client,
@@ -300,7 +300,7 @@ impl BillingClient {
300300

301301
/// Creates a billing client with explicit base URL.
302302
pub fn with_base_url(base_url: String) -> Result<Self> {
303-
let client = create_default_client()?;
303+
let client = create_default_client().map_err(CortexError::internal)?;
304304

305305
Ok(Self {
306306
client,

cortex-engine/src/lib.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ pub mod summarization;
4545
pub mod acp;
4646
pub mod api_client;
4747
pub mod github;
48+
pub mod linear;
4849
pub mod mcp;
4950

5051
// === NETWORK DISCOVERY ===
@@ -119,6 +120,7 @@ pub mod unified_exec;
119120
pub mod validation;
120121
pub mod version_utils;
121122
pub mod workspace;
123+
pub mod workspace_scripts;
122124

123125
// Background terminal management (different from TUI terminal)
124126
pub mod terminal;

cortex-engine/src/linear/client.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ use serde::{Deserialize, Serialize};
77
use std::time::{Duration, Instant};
88
use tokio::sync::Mutex;
99

10-
use crate::api_client::create_default_client;
10+
use cortex_common::create_default_client;
1111

1212
/// Linear GraphQL endpoint.
1313
const LINEAR_GRAPHQL_ENDPOINT: &str = "https://api.linear.app/graphql";
@@ -32,7 +32,7 @@ pub struct LinearClient {
3232
impl LinearClient {
3333
/// Create a new authenticated Linear client.
3434
pub fn new(token: &str) -> Result<Self> {
35-
let client = create_default_client().context("Failed to create HTTP client")?;
35+
let client = create_default_client().map_err(|e| anyhow::anyhow!(e))?;
3636

3737
Ok(Self {
3838
client,

cortex-gui/src-tauri/Cargo.toml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,8 +26,8 @@ tauri-plugin-fs = "2"
2626
tauri-plugin-shell = "2"
2727

2828
# Workspace dependencies
29-
cortex-engine = { workspace = true }
30-
cortex-protocol = { workspace = true }
29+
cortex-engine = { path = "../../cortex-engine" }
30+
cortex-protocol = { path = "../../cortex-protocol" }
3131

3232
# Async runtime
3333
tokio.workspace = true
Lines changed: 170 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -1,90 +1,206 @@
1-
//! Linear integration commands - DISABLED
2-
//!
3-
//! This module is disabled because cortex_engine::linear is not available in cortex-cli.
4-
//! The linear module exists in cortex-app but was not migrated to cortex-cli.
5-
//!
6-
//! To re-enable this feature, the linear module would need to be copied from cortex-app
7-
//! and all its dependencies resolved in cortex-cli's cortex-engine.
1+
//! Linear integration commands.
2+
3+
use cortex_engine::linear::client::{Issue, IssueDetails, LinearClient, Team};
4+
use keyring::Entry;
5+
use serde::Deserialize;
6+
use std::env;
7+
use tauri::State;
88

99
use crate::error::CommandError;
10+
use crate::state::AppState;
11+
12+
const SERVICE_NAME: &str = "cortex-linear";
13+
const USER_NAME: &str = "auth-token";
14+
15+
/// Get Linear token from keychain or environment.
16+
fn get_linear_token() -> Result<String, CommandError> {
17+
if let Ok(token) = env::var("LINEAR_API_KEY") {
18+
return Ok(token);
19+
}
20+
21+
let entry = Entry::new(SERVICE_NAME, USER_NAME)
22+
.map_err(|e| CommandError::EngineError(format!("Failed to access keychain: {}", e)))?;
1023

11-
/// Placeholder for linear_set_token - feature disabled
24+
match entry.get_password() {
25+
Ok(token) => Ok(token),
26+
Err(keyring::Error::NoEntry) => Err(CommandError::ValidationError(
27+
"Linear token not found. Please sign in.".to_string(),
28+
)),
29+
Err(e) => Err(CommandError::EngineError(format!(
30+
"Failed to retrieve token: {}",
31+
e
32+
))),
33+
}
34+
}
35+
36+
/// Store Linear token in keychain.
1237
#[tauri::command]
13-
pub async fn linear_set_token(_token: String) -> Result<(), CommandError> {
14-
Err(CommandError::EngineError(
15-
"Linear integration is not available in this build".to_string(),
16-
))
38+
pub async fn linear_set_token(token: String) -> Result<(), CommandError> {
39+
let entry = Entry::new(SERVICE_NAME, USER_NAME)
40+
.map_err(|e| CommandError::EngineError(format!("Failed to access keychain: {}", e)))?;
41+
42+
entry
43+
.set_password(&token)
44+
.map_err(|e| CommandError::EngineError(format!("Failed to store token: {}", e)))?;
45+
46+
Ok(())
1747
}
1848

19-
/// Placeholder for linear_logout - feature disabled
49+
/// Remove Linear token from keychain.
2050
#[tauri::command]
2151
pub async fn linear_logout() -> Result<(), CommandError> {
22-
Err(CommandError::EngineError(
23-
"Linear integration is not available in this build".to_string(),
24-
))
52+
let entry = Entry::new(SERVICE_NAME, USER_NAME)
53+
.map_err(|e| CommandError::EngineError(format!("Failed to access keychain: {}", e)))?;
54+
55+
match entry.delete_credential() {
56+
Ok(_) => Ok(()),
57+
Err(keyring::Error::NoEntry) => Ok(()),
58+
Err(e) => Err(CommandError::EngineError(format!(
59+
"Failed to delete token: {}",
60+
e
61+
))),
62+
}
2563
}
2664

27-
/// Placeholder for linear_check_auth - feature disabled
65+
/// Check if user is authenticated with Linear.
2866
#[tauri::command]
2967
pub async fn linear_check_auth() -> Result<bool, CommandError> {
30-
Err(CommandError::EngineError(
31-
"Linear integration is not available in this build".to_string(),
32-
))
68+
match get_linear_token() {
69+
Ok(_) => Ok(true),
70+
Err(_) => Ok(false),
71+
}
3372
}
3473

35-
/// Placeholder for linear_auth_url - feature disabled
74+
/// Get Linear OAuth URL.
3675
#[tauri::command]
3776
pub async fn linear_auth_url(
38-
_client_id: String,
39-
_redirect_uri: String,
40-
_state: String,
77+
client_id: String,
78+
redirect_uri: String,
79+
state: String,
4180
) -> Result<String, CommandError> {
42-
Err(CommandError::EngineError(
43-
"Linear integration is not available in this build".to_string(),
81+
Ok(format!(
82+
"{}?client_id={}&redirect_uri={}&response_type=code&state={}&scope=read,write",
83+
cortex_engine::linear::client::LINEAR_OAUTH_AUTHORIZE,
84+
client_id,
85+
redirect_uri,
86+
state
4487
))
4588
}
4689

47-
/// Placeholder for linear_exchange_token - feature disabled
90+
/// Exchange OAuth code for token.
4891
#[tauri::command]
4992
pub async fn linear_exchange_token(
50-
_code: String,
51-
_client_id: String,
52-
_client_secret: String,
53-
_redirect_uri: String,
93+
code: String,
94+
client_id: String,
95+
client_secret: String,
96+
redirect_uri: String,
5497
) -> Result<String, CommandError> {
55-
Err(CommandError::EngineError(
56-
"Linear integration is not available in this build".to_string(),
57-
))
98+
let client = reqwest::Client::new();
99+
100+
let params = [
101+
("code", code),
102+
("client_id", client_id),
103+
("client_secret", client_secret),
104+
("redirect_uri", redirect_uri),
105+
("grant_type", "authorization_code".to_string()),
106+
];
107+
108+
let response = client
109+
.post(cortex_engine::linear::client::LINEAR_OAUTH_TOKEN)
110+
.form(&params)
111+
.send()
112+
.await
113+
.map_err(|e| CommandError::EngineError(format!("Failed to send token request: {}", e)))?;
114+
115+
if !response.status().is_success() {
116+
let body = response.text().await.unwrap_or_default();
117+
return Err(CommandError::EngineError(format!(
118+
"Token exchange failed: {}",
119+
body
120+
)));
121+
}
122+
123+
#[derive(Deserialize)]
124+
struct TokenResponse {
125+
access_token: String,
126+
}
127+
128+
let token_res: TokenResponse = response
129+
.json()
130+
.await
131+
.map_err(|e| CommandError::EngineError(format!("Failed to parse token response: {}", e)))?;
132+
133+
linear_set_token(token_res.access_token.clone()).await?;
134+
135+
Ok(token_res.access_token)
58136
}
59137

60-
/// Placeholder for linear_get_teams - feature disabled
138+
/// Get all teams.
61139
#[tauri::command]
62-
pub async fn linear_get_teams() -> Result<Vec<String>, CommandError> {
63-
Err(CommandError::EngineError(
64-
"Linear integration is not available in this build".to_string(),
65-
))
140+
pub async fn linear_get_teams(_state: State<'_, AppState>) -> Result<Vec<Team>, CommandError> {
141+
let token = get_linear_token()?;
142+
let client = LinearClient::new(&token)
143+
.map_err(|e| CommandError::EngineError(format!("Failed to create Linear client: {}", e)))?;
144+
145+
client
146+
.get_teams()
147+
.await
148+
.map_err(|e| CommandError::EngineError(format!("Failed to get teams: {}", e)))
66149
}
67150

68-
/// Placeholder for linear_get_issues - feature disabled
151+
/// Get issues for a team.
69152
#[tauri::command]
70-
pub async fn linear_get_issues(_team_id: String) -> Result<Vec<String>, CommandError> {
71-
Err(CommandError::EngineError(
72-
"Linear integration is not available in this build".to_string(),
73-
))
153+
pub async fn linear_get_issues(
154+
_state: State<'_, AppState>,
155+
team_id: String,
156+
) -> Result<Vec<Issue>, CommandError> {
157+
let token = get_linear_token()?;
158+
let client = LinearClient::new(&token)
159+
.map_err(|e| CommandError::EngineError(format!("Failed to create Linear client: {}", e)))?;
160+
161+
client
162+
.get_issues(&team_id)
163+
.await
164+
.map_err(|e| CommandError::EngineError(format!("Failed to get issues: {}", e)))
74165
}
75166

76-
/// Placeholder for linear_get_issue_details - feature disabled
167+
/// Get issue details.
77168
#[tauri::command]
78-
pub async fn linear_get_issue_details(_issue_id: String) -> Result<String, CommandError> {
79-
Err(CommandError::EngineError(
80-
"Linear integration is not available in this build".to_string(),
81-
))
169+
pub async fn linear_get_issue_details(
170+
_state: State<'_, AppState>,
171+
issue_id: String,
172+
) -> Result<IssueDetails, CommandError> {
173+
let token = get_linear_token()?;
174+
let client = LinearClient::new(&token)
175+
.map_err(|e| CommandError::EngineError(format!("Failed to create Linear client: {}", e)))?;
176+
177+
client
178+
.get_issue_details(&issue_id)
179+
.await
180+
.map_err(|e| CommandError::EngineError(format!("Failed to get issue details: {}", e)))
82181
}
83182

84-
/// Placeholder for linear_create_session_from_issue - feature disabled
183+
/// Create a session from a Linear issue.
85184
#[tauri::command]
86-
pub async fn linear_create_session_from_issue(_issue_id: String) -> Result<String, CommandError> {
87-
Err(CommandError::EngineError(
88-
"Linear integration is not available in this build".to_string(),
89-
))
185+
pub async fn linear_create_session_from_issue(
186+
_state: State<'_, AppState>,
187+
issue_id: String,
188+
) -> Result<String, CommandError> {
189+
let token = get_linear_token()?;
190+
let client = LinearClient::new(&token)
191+
.map_err(|e| CommandError::EngineError(format!("Failed to create Linear client: {}", e)))?;
192+
193+
let issue = client
194+
.get_issue_details(&issue_id)
195+
.await
196+
.map_err(|e| CommandError::EngineError(format!("Failed to get issue details: {}", e)))?;
197+
198+
let prompt = format!(
199+
"I'm working on Linear issue {}: {}\n\nDescription:\n{}\n\nPlease help me implement this.",
200+
issue.identifier,
201+
issue.title,
202+
issue.description.unwrap_or_default()
203+
);
204+
205+
Ok(prompt)
90206
}

cortex-gui/src-tauri/src/commands/mod.rs

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ pub mod context;
88
pub mod files;
99
pub mod git;
1010
pub mod github;
11-
// pub mod linear; // DISABLED: cortex_engine::linear not available in cortex-cli
11+
pub mod linear;
1212
pub mod mcp;
1313
pub mod message;
1414
pub mod plan;
@@ -17,12 +17,12 @@ pub mod share;
1717
pub mod submit;
1818
pub mod terminal;
1919
pub mod tools;
20-
// pub mod workspace_scripts; // DISABLED: cortex_engine::workspace_scripts not available in cortex-cli
20+
pub mod workspace_scripts;
2121

2222
// Re-export command handlers for registration in lib.rs
2323
pub use message::{interrupt_session, submit_message};
2424
pub use session::{create_session, delete_session, get_session, list_sessions};
2525
pub use tools::approve_tool;
26-
// pub use workspace_scripts::{ // DISABLED: cortex_engine::workspace_scripts not available in cortex-cli
27-
// check_scripts_exist, run_archive_script, run_dev_script, run_setup_script,
28-
// };
26+
pub use workspace_scripts::{
27+
check_scripts_exist, run_archive_script, run_dev_script, run_setup_script,
28+
};

0 commit comments

Comments
 (0)