Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions cli/src/code_index.rs
Original file line number Diff line number Diff line change
Expand Up @@ -318,6 +318,7 @@ async fn build_local_code_index(
.map(|api_key| StakpakConfig {
api_key,
api_endpoint: app_config.api_endpoint.clone(),
rulebook_base_url: app_config.rulebook_base_url.clone(),
});

let client = AgentClient::new(AgentClientConfig {
Expand Down Expand Up @@ -689,6 +690,7 @@ async fn execute_code_index_update(
.map(|api_key| StakpakConfig {
api_key,
api_endpoint: app_config.api_endpoint.clone(),
rulebook_base_url: app_config.rulebook_base_url.clone(),
});

let client = AgentClient::new(AgentClientConfig {
Expand Down
2 changes: 2 additions & 0 deletions cli/src/commands/acp/server.rs
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,7 @@ impl StakpakAcpAgent {
let stakpak = stakpak_api_key.map(|api_key| StakpakConfig {
api_key,
api_endpoint: config.api_endpoint.clone(),
rulebook_base_url: config.rulebook_base_url.clone(),
});

let client = AgentClient::new(AgentClientConfig {
Expand Down Expand Up @@ -1641,6 +1642,7 @@ impl acp::Agent for StakpakAcpAgent {
let stakpak = Some(StakpakConfig {
api_key: api_key.clone(),
api_endpoint: config.api_endpoint.clone(),
rulebook_base_url: config.rulebook_base_url.clone(),
});
let new_client = AgentClient::new(AgentClientConfig {
stakpak,
Expand Down
8 changes: 7 additions & 1 deletion cli/src/commands/agent/run/mode_async.rs
Original file line number Diff line number Diff line change
Expand Up @@ -233,8 +233,14 @@ pub async fn run_async(ctx: AppConfig, mut config: RunAsyncConfig) -> Result<Asy
let mut client_config = AgentClientConfig::new().with_providers(providers);

if let Some(api_key) = ctx.get_stakpak_api_key() {
let rulebook_base_url = ctx
.rulebook_base_url
.clone()
.unwrap_or(ctx.api_endpoint.clone());
client_config = client_config.with_stakpak(
stakpak_api::StakpakConfig::new(api_key).with_endpoint(ctx.api_endpoint.clone()),
stakpak_api::StakpakConfig::new(api_key)
.with_endpoint(ctx.api_endpoint.clone())
.with_rulebook_base_url(rulebook_base_url),
);
}

Expand Down
25 changes: 22 additions & 3 deletions cli/src/commands/agent/run/mode_interactive.rs
Original file line number Diff line number Diff line change
Expand Up @@ -368,9 +368,16 @@ pub async fn run_interactive(
let mut client_config = AgentClientConfig::new().with_providers(providers);

if let Some(ref key) = api_key_for_client {
// Use rulebook_base_url from config if set, otherwise fall back to api_endpoint
let rulebook_base_url = ctx_clone
.rulebook_base_url
.clone()
.unwrap_or(api_endpoint_for_client.clone());

client_config = client_config.with_stakpak(
stakpak_api::StakpakConfig::new(key.clone())
.with_endpoint(api_endpoint_for_client.clone()),
.with_endpoint(api_endpoint_for_client.clone())
.with_rulebook_base_url(rulebook_base_url),
);
}

Expand Down Expand Up @@ -1643,7 +1650,13 @@ pub async fn run_interactive(
if let Some(api_key) = new_config.get_stakpak_api_key() {
new_client_config = new_client_config.with_stakpak(
stakpak_api::StakpakConfig::new(api_key)
.with_endpoint(new_config.api_endpoint.clone()),
.with_endpoint(new_config.api_endpoint.clone())
.with_rulebook_base_url(
new_config
.rulebook_base_url
.clone()
.unwrap_or(new_config.api_endpoint.clone()),
),
);
}

Expand Down Expand Up @@ -1710,7 +1723,13 @@ pub async fn run_interactive(

if let Some(api_key) = ctx.get_stakpak_api_key() {
final_client_config = final_client_config.with_stakpak(
stakpak_api::StakpakConfig::new(api_key).with_endpoint(ctx.api_endpoint.clone()),
stakpak_api::StakpakConfig::new(api_key)
.with_endpoint(ctx.api_endpoint.clone())
.with_rulebook_base_url(
ctx.rulebook_base_url
.clone()
.unwrap_or(ctx.api_endpoint.clone()),
),
);
}

Expand Down
1 change: 1 addition & 0 deletions cli/src/commands/agent/run/profile_switch.rs
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ pub async fn validate_profile_switch(
.map(|api_key| StakpakConfig {
api_key,
api_endpoint: new_config.api_endpoint.clone(),
rulebook_base_url: new_config.rulebook_base_url.clone(),
});

let client = AgentClient::new(AgentClientConfig {
Expand Down
1 change: 1 addition & 0 deletions cli/src/commands/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -239,6 +239,7 @@ async fn build_agent_client(config: &AppConfig) -> Result<AgentClient, String> {
let stakpak = config.get_stakpak_api_key().map(|api_key| StakpakConfig {
api_key,
api_endpoint: config.api_endpoint.clone(),
rulebook_base_url: config.rulebook_base_url.clone(),
});

AgentClient::new(AgentClientConfig {
Expand Down
1 change: 1 addition & 0 deletions cli/src/commands/sessions/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,7 @@ async fn build_storage(config: &AppConfig) -> Result<Arc<dyn SessionStorage>, St
let stakpak = config.get_stakpak_api_key().map(|api_key| StakpakConfig {
api_key,
api_endpoint: config.api_endpoint.clone(),
rulebook_base_url: None,
});
AgentClient::build_session_storage(stakpak, None, Some(config.profile_name.clone())).await
}
Expand Down
11 changes: 11 additions & 0 deletions cli/src/config/app.rs
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,9 @@ pub struct AppConfig {
pub collect_telemetry: Option<bool>,
/// Editor command
pub editor: Option<String>,
/// Base URL for downloading rulebooks/skills/playbooks
/// If not set, api_endpoint is used for content downloads
pub rulebook_base_url: Option<String>,
/// Recently used model IDs (most recent first)
pub recent_models: Vec<String>,
}
Expand Down Expand Up @@ -155,6 +158,11 @@ impl AppConfig {
api_key: std::env::var("STAKPAK_API_KEY")
.ok()
.or(profile_config.api_key),
rulebook_base_url: std::env::var("STAKPAK_RULEBOOK_BASE_URL").ok().or(
profile_config
.rulebook_base_url
.or(settings.rulebook_base_url),
),
mcp_server_host: None,
machine_name: settings.machine_name,
auto_append_gitignore: settings.auto_append_gitignore,
Expand Down Expand Up @@ -988,6 +996,7 @@ impl From<AppConfig> for Settings {
anonymous_id: config.anonymous_id,
collect_telemetry: config.collect_telemetry,
editor: config.editor,
rulebook_base_url: config.rulebook_base_url,
}
}
}
Expand Down Expand Up @@ -1015,6 +1024,8 @@ impl From<AppConfig> for ProfileConfig {
eco_model: None,
smart_model: None,
recovery_model: None,
// New field for rulebook base URL
rulebook_base_url: config.rulebook_base_url,
}
}
}
Expand Down
7 changes: 7 additions & 0 deletions cli/src/config/file.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@ impl Default for ConfigFile {
anonymous_id: Some(uuid::Uuid::new_v4().to_string()),
collect_telemetry: Some(true),
editor: Some("nano".to_string()),
// Default rulebook base URL (optional - will fall back to api_endpoint)
rulebook_base_url: None,
},
}
}
Expand All @@ -48,6 +50,8 @@ impl ConfigFile {
anonymous_id: Some(uuid::Uuid::new_v4().to_string()),
collect_telemetry: Some(true),
editor: Some("nano".to_string()),
// Default rulebook base URL (optional - will fall back to api_endpoint)
rulebook_base_url: None,
},
}
}
Expand Down Expand Up @@ -93,13 +97,16 @@ impl ConfigFile {
let existing_anonymous_id = self.settings.anonymous_id.clone();
let existing_collect_telemetry = self.settings.collect_telemetry;
let existing_editor = self.settings.editor.clone();
let existing_rulebook_base_url = self.settings.rulebook_base_url.clone();

self.settings = Settings {
machine_name: config.machine_name,
auto_append_gitignore: config.auto_append_gitignore,
anonymous_id: config.anonymous_id.or(existing_anonymous_id),
collect_telemetry: config.collect_telemetry.or(existing_collect_telemetry),
editor: config.editor.or(existing_editor),
// Only set rulebook_base_url if config explicitly provides it
rulebook_base_url: config.rulebook_base_url.or(existing_rulebook_base_url),
};
}

Expand Down
8 changes: 8 additions & 0 deletions cli/src/config/profile.rs
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,9 @@ pub struct SubagentConfig {
pub struct ProfileConfig {
/// API endpoint URL
pub api_endpoint: Option<String>,
/// Base URL for downloading rulebooks/skills/playbooks
/// If not set, api_endpoint is used for content downloads
pub rulebook_base_url: Option<String>,
/// API key for authentication
pub api_key: Option<String>,
/// Provider type (remote or local)
Expand Down Expand Up @@ -367,6 +370,10 @@ impl ProfileConfig {
.api_endpoint
.clone()
.or_else(|| other.and_then(|config| config.api_endpoint.clone())),
rulebook_base_url: self
.rulebook_base_url
.clone()
.or_else(|| other.and_then(|config| config.rulebook_base_url.clone())),
api_key: self
.api_key
.clone()
Expand Down Expand Up @@ -607,6 +614,7 @@ impl From<OldAppConfig> for ProfileConfig {
fn from(old_config: OldAppConfig) -> Self {
ProfileConfig {
api_endpoint: Some(old_config.api_endpoint),
rulebook_base_url: old_config.rulebook_base_url,
api_key: old_config.api_key,
..ProfileConfig::default()
}
Expand Down
7 changes: 7 additions & 0 deletions cli/src/config/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,12 +27,18 @@ pub struct Settings {
pub collect_telemetry: Option<bool>,
/// Preferred external editor (e.g. vim, nano, code)
pub editor: Option<String>,
/// Base URL for downloading rulebooks/skills/playbooks
/// If not set, api_endpoint is used for content downloads
pub rulebook_base_url: Option<String>,
}

/// Legacy configuration format for migration purposes.
#[derive(Deserialize, Clone)]
pub(crate) struct OldAppConfig {
pub api_endpoint: String,
/// Base URL for downloading rulebooks/skills/playbooks
/// If not set, api_endpoint is used for content downloads
pub rulebook_base_url: Option<String>,
pub api_key: Option<String>,
pub machine_name: Option<String>,
pub auto_append_gitignore: Option<bool>,
Expand All @@ -46,6 +52,7 @@ impl From<OldAppConfig> for Settings {
anonymous_id: Some(uuid::Uuid::new_v4().to_string()),
collect_telemetry: Some(true),
editor: Some("nano".to_string()),
rulebook_base_url: old_config.rulebook_base_url,
}
}
}
7 changes: 6 additions & 1 deletion cli/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -405,9 +405,14 @@ async fn main() {
let mut client_config = AgentClientConfig::new().with_providers(providers);

if let Some(api_key) = config.get_stakpak_api_key() {
let rulebook_base_url = config
.rulebook_base_url
.clone()
.unwrap_or(config.api_endpoint.clone());
client_config = client_config.with_stakpak(
stakpak_api::StakpakConfig::new(api_key)
.with_endpoint(config.api_endpoint.clone()),
.with_endpoint(config.api_endpoint.clone())
.with_rulebook_base_url(rulebook_base_url),
);
}

Expand Down
11 changes: 11 additions & 0 deletions libs/api/src/client/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -34,20 +34,30 @@ pub struct StakpakConfig {
pub api_key: String,
/// Stakpak API endpoint (default: https://apiv2.stakpak.dev)
pub api_endpoint: String,
/// Base URL for downloading rulebooks/skills/playbooks
/// If not set, api_endpoint is used for content downloads
pub rulebook_base_url: Option<String>,
}

impl StakpakConfig {
pub fn new(api_key: impl Into<String>) -> Self {
Self {
api_key: api_key.into(),
api_endpoint: DEFAULT_STAKPAK_ENDPOINT.to_string(),
rulebook_base_url: None,
}
}

pub fn with_endpoint(mut self, endpoint: impl Into<String>) -> Self {
self.api_endpoint = endpoint.into();
self
}

/// Set rulebook base URL
pub fn with_rulebook_base_url(mut self, url: impl Into<String>) -> Self {
self.rulebook_base_url = Some(url.into());
self
}
}

/// Configuration for creating an AgentClient
Expand Down Expand Up @@ -185,6 +195,7 @@ impl AgentClient {
StakpakApiClient::new(&StakpakApiConfig {
api_key: stakpak.api_key.clone(),
api_endpoint: stakpak.api_endpoint.clone(),
rulebook_base_url: stakpak.rulebook_base_url.clone(),
})
.map_err(|e| format!("Failed to create Stakpak API client: {}", e))?,
)
Expand Down
13 changes: 10 additions & 3 deletions libs/api/src/stakpak/client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ use uuid::Uuid;
pub struct StakpakApiClient {
client: reqwest::Client,
base_url: String,
rulebook_base_url: String,
}

/// API error response format
Expand Down Expand Up @@ -64,9 +65,16 @@ impl StakpakApiClient {
.with_timeout(std::time::Duration::from_secs(300)),
)?;

// Use rulebook_base_url if provided, otherwise fall back to api_endpoint
let rulebook_base_url = config
.rulebook_base_url
.clone()
.unwrap_or_else(|| config.api_endpoint.clone());

Ok(Self {
client,
base_url: config.api_endpoint.clone(),
rulebook_base_url,
})
}

Expand Down Expand Up @@ -239,11 +247,10 @@ impl StakpakApiClient {

// =========================================================================
// Rulebook APIs
// =========================================================================

/// List all rulebooks
pub async fn list_rulebooks(&self) -> Result<Vec<ListRuleBook>, String> {
let url = format!("{}/v1/rules", self.base_url);
let url = format!("{}/v1/rules", self.rulebook_base_url);
let response = self
.client
.get(&url)
Expand All @@ -263,7 +270,7 @@ impl StakpakApiClient {
/// Get a rulebook by URI
pub async fn get_rulebook_by_uri(&self, uri: &str) -> Result<RuleBook, String> {
let encoded_uri = urlencoding::encode(uri);
let url = format!("{}/v1/rules/{}", self.base_url, encoded_uri);
let url = format!("{}/v1/rules/{}", self.rulebook_base_url, encoded_uri);
let response = self
.client
.get(&url)
Expand Down
18 changes: 18 additions & 0 deletions libs/api/src/stakpak/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,9 @@ pub struct StakpakApiConfig {
pub api_key: String,
/// API endpoint URL (default: https://apiv2.stakpak.dev)
pub api_endpoint: String,
/// Base URL for downloading rulebooks/skills/playbooks
/// If not set, api_endpoint is used for content downloads
pub rulebook_base_url: Option<String>,
}

impl StakpakApiConfig {
Expand All @@ -28,6 +31,7 @@ impl StakpakApiConfig {
Self {
api_key: api_key.into(),
api_endpoint: "https://apiv2.stakpak.dev".to_string(),
rulebook_base_url: None,
}
}

Expand All @@ -36,6 +40,20 @@ impl StakpakApiConfig {
self.api_endpoint = endpoint.into();
self
}

/// Set rulebook base URL
pub fn with_rulebook_base_url(mut self, url: impl Into<String>) -> Self {
self.rulebook_base_url = Some(url.into());
self
}

/// Set rulebook base URL from environment variable
pub fn with_rulebook_base_url_from_env(mut self) -> Self {
if let Ok(url) = std::env::var("STAKPAK_RULEBOOK_BASE_URL") {
self.rulebook_base_url = Some(url);
}
self
}
}

impl Default for StakpakApiConfig {
Expand Down
1 change: 1 addition & 0 deletions libs/api/src/stakpak/storage.rs
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ impl StakpakStorage {
let client = StakpakApiClient::new(&StakpakApiConfig {
api_key: api_key.to_string(),
api_endpoint: api_endpoint.to_string(),
rulebook_base_url: None,
})
.map_err(StorageError::Connection)?;

Expand Down