Skip to content

Commit 88aaaf4

Browse files
author
RageLtMan
committed
Add User-Supplied Base URL for Playbooks/Skills
Allow users to specify a custom base URL for downloading rulebooks, skills, and playbooks. This enables private/internal repositories, custom CDNs, and air-gapped environments while maintaining full backward compatibility with Stakpak's default API. Changes: - New config field: rulebook_base_url (optional, defaults to api_endpoint) - New env var: STAKPAK_RULEBOOK_BASE_URL - Separate control plane (api_endpoint) from data plane (rulebook_base_url) - Backward compatible: defaults to https://apiv2.stakpak.dev if not set - All existing configs continue to work without modification Environment Variables: - STAKPAK_RULEBOOK_BASE_URL - Override rulebook base URL globally Config File Examples: [settings] rulebook_base_url = "https://rules.example.com" [profiles.corporate] rulebook_base_url = "http://local-rules:8080"
1 parent c1aad7d commit 88aaaf4

15 files changed

Lines changed: 119 additions & 13 deletions

File tree

cli/src/code_index.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -318,6 +318,7 @@ async fn build_local_code_index(
318318
.map(|api_key| StakpakConfig {
319319
api_key,
320320
api_endpoint: app_config.api_endpoint.clone(),
321+
rulebook_base_url: app_config.rulebook_base_url.clone(),
321322
});
322323

323324
let client = AgentClient::new(AgentClientConfig {
@@ -692,6 +693,7 @@ async fn execute_code_index_update(
692693
.map(|api_key| StakpakConfig {
693694
api_key,
694695
api_endpoint: app_config.api_endpoint.clone(),
696+
rulebook_base_url: app_config.rulebook_base_url.clone(),
695697
});
696698

697699
let client = AgentClient::new(AgentClientConfig {

cli/src/commands/acp/server.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,7 @@ impl StakpakAcpAgent {
120120
let stakpak = stakpak_api_key.map(|api_key| StakpakConfig {
121121
api_key,
122122
api_endpoint: config.api_endpoint.clone(),
123+
rulebook_base_url: config.rulebook_base_url.clone(),
123124
});
124125

125126
let client = AgentClient::new(AgentClientConfig {
@@ -1643,6 +1644,7 @@ impl acp::Agent for StakpakAcpAgent {
16431644
let stakpak = Some(StakpakConfig {
16441645
api_key: api_key.clone(),
16451646
api_endpoint: config.api_endpoint.clone(),
1647+
rulebook_base_url: config.rulebook_base_url.clone(),
16461648
});
16471649
let new_client = AgentClient::new(AgentClientConfig {
16481650
stakpak,

cli/src/commands/agent/run/mode_async.rs

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -229,8 +229,14 @@ pub async fn run_async(ctx: AppConfig, mut config: RunAsyncConfig) -> Result<Asy
229229
let mut client_config = AgentClientConfig::new().with_providers(providers);
230230

231231
if let Some(api_key) = ctx.get_stakpak_api_key() {
232+
let rulebook_base_url = ctx
233+
.rulebook_base_url
234+
.clone()
235+
.unwrap_or(ctx.api_endpoint.clone());
232236
client_config = client_config.with_stakpak(
233-
stakpak_api::StakpakConfig::new(api_key).with_endpoint(ctx.api_endpoint.clone()),
237+
stakpak_api::StakpakConfig::new(api_key)
238+
.with_endpoint(ctx.api_endpoint.clone())
239+
.with_rulebook_base_url(rulebook_base_url),
234240
);
235241
}
236242
// Pass unified model as smart_model for AgentClient compatibility

cli/src/commands/agent/run/mode_interactive.rs

Lines changed: 22 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -350,9 +350,16 @@ pub async fn run_interactive(
350350
let mut client_config = AgentClientConfig::new().with_providers(providers);
351351

352352
if let Some(ref key) = api_key_for_client {
353+
// Use rulebook_base_url from config if set, otherwise fall back to api_endpoint
354+
let rulebook_base_url = ctx_clone
355+
.rulebook_base_url
356+
.clone()
357+
.unwrap_or(api_endpoint_for_client.clone());
358+
353359
client_config = client_config.with_stakpak(
354360
stakpak_api::StakpakConfig::new(key.clone())
355-
.with_endpoint(api_endpoint_for_client.clone()),
361+
.with_endpoint(api_endpoint_for_client.clone())
362+
.with_rulebook_base_url(rulebook_base_url),
356363
);
357364
}
358365
// Pass unified model as smart_model for AgentClient compatibility
@@ -1614,7 +1621,13 @@ pub async fn run_interactive(
16141621
if let Some(api_key) = new_config.get_stakpak_api_key() {
16151622
new_client_config = new_client_config.with_stakpak(
16161623
stakpak_api::StakpakConfig::new(api_key)
1617-
.with_endpoint(new_config.api_endpoint.clone()),
1624+
.with_endpoint(new_config.api_endpoint.clone())
1625+
.with_rulebook_base_url(
1626+
new_config
1627+
.rulebook_base_url
1628+
.clone()
1629+
.unwrap_or(new_config.api_endpoint.clone()),
1630+
),
16181631
);
16191632
}
16201633
// Pass unified model as smart_model for AgentClient compatibility
@@ -1685,7 +1698,13 @@ pub async fn run_interactive(
16851698

16861699
if let Some(api_key) = ctx.get_stakpak_api_key() {
16871700
final_client_config = final_client_config.with_stakpak(
1688-
stakpak_api::StakpakConfig::new(api_key).with_endpoint(ctx.api_endpoint.clone()),
1701+
stakpak_api::StakpakConfig::new(api_key)
1702+
.with_endpoint(ctx.api_endpoint.clone())
1703+
.with_rulebook_base_url(
1704+
ctx.rulebook_base_url
1705+
.clone()
1706+
.unwrap_or(ctx.api_endpoint.clone()),
1707+
),
16891708
);
16901709
}
16911710
// Pass unified model as smart_model for AgentClient compatibility

cli/src/commands/agent/run/profile_switch.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ pub async fn validate_profile_switch(
3636
.map(|api_key| StakpakConfig {
3737
api_key,
3838
api_endpoint: new_config.api_endpoint.clone(),
39+
rulebook_base_url: new_config.rulebook_base_url.clone(),
3940
});
4041

4142
let client = AgentClient::new(AgentClientConfig {

cli/src/commands/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -209,6 +209,7 @@ async fn build_agent_client(config: &AppConfig) -> Result<AgentClient, String> {
209209
let stakpak = config.get_stakpak_api_key().map(|api_key| StakpakConfig {
210210
api_key,
211211
api_endpoint: config.api_endpoint.clone(),
212+
rulebook_base_url: config.rulebook_base_url.clone(),
212213
});
213214

214215
AgentClient::new(AgentClientConfig {

cli/src/config/app.rs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,9 @@ pub struct AppConfig {
6060
pub collect_telemetry: Option<bool>,
6161
/// Editor command
6262
pub editor: Option<String>,
63+
/// Base URL for downloading rulebooks/skills/playbooks
64+
/// If not set, api_endpoint is used for content downloads
65+
pub rulebook_base_url: Option<String>,
6366
/// Recently used model IDs (most recent first)
6467
pub recent_models: Vec<String>,
6568
}
@@ -154,6 +157,11 @@ impl AppConfig {
154157
api_key: std::env::var("STAKPAK_API_KEY")
155158
.ok()
156159
.or(profile_config.api_key),
160+
rulebook_base_url: std::env::var("STAKPAK_RULEBOOK_BASE_URL").ok().or(
161+
profile_config
162+
.rulebook_base_url
163+
.or(settings.rulebook_base_url),
164+
),
157165
mcp_server_host: None,
158166
machine_name: settings.machine_name,
159167
auto_append_gitignore: settings.auto_append_gitignore,
@@ -964,6 +972,7 @@ impl From<AppConfig> for Settings {
964972
anonymous_id: config.anonymous_id,
965973
collect_telemetry: config.collect_telemetry,
966974
editor: config.editor,
975+
rulebook_base_url: config.rulebook_base_url,
967976
}
968977
}
969978
}
@@ -990,6 +999,8 @@ impl From<AppConfig> for ProfileConfig {
990999
eco_model: None,
9911000
smart_model: None,
9921001
recovery_model: None,
1002+
// New field for rulebook base URL
1003+
rulebook_base_url: config.rulebook_base_url,
9931004
}
9941005
}
9951006
}

cli/src/config/file.rs

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,8 @@ impl Default for ConfigFile {
3131
// DEFAULT: Telemetry is OPT-IN, never OPT-OUT
3232
collect_telemetry: Some(false),
3333
editor: Some("nano".to_string()),
34+
// Default rulebook base URL (optional - will fall back to api_endpoint)
35+
rulebook_base_url: None,
3436
},
3537
}
3638
}
@@ -51,6 +53,8 @@ impl ConfigFile {
5153
// DEFAULT: Telemetry is OPT-IN, never OPT-OUT
5254
collect_telemetry: Some(false),
5355
editor: Some("nano".to_string()),
56+
// Default rulebook base URL (optional - will fall back to api_endpoint)
57+
rulebook_base_url: None,
5458
},
5559
}
5660
}
@@ -96,6 +100,7 @@ impl ConfigFile {
96100
let existing_anonymous_id = self.settings.anonymous_id.clone();
97101
let existing_collect_telemetry = self.settings.collect_telemetry;
98102
let existing_editor = self.settings.editor.clone();
103+
let existing_rulebook_base_url = self.settings.rulebook_base_url.clone();
99104

100105
self.settings = Settings {
101106
machine_name: config.machine_name,
@@ -105,6 +110,8 @@ impl ConfigFile {
105110
// Only set collect_telemetry if config explicitly provides it
106111
collect_telemetry: config.collect_telemetry.or(existing_collect_telemetry),
107112
editor: config.editor.or(existing_editor),
113+
// Only set rulebook_base_url if config explicitly provides it
114+
rulebook_base_url: config.rulebook_base_url.or(existing_rulebook_base_url),
108115
};
109116
}
110117

@@ -184,4 +191,4 @@ impl From<OldAppConfig> for ConfigFile {
184191
settings,
185192
}
186193
}
187-
}
194+
}

cli/src/config/profile.rs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,9 @@ use super::warden::WardenConfig;
3939
pub struct ProfileConfig {
4040
/// API endpoint URL
4141
pub api_endpoint: Option<String>,
42+
/// Base URL for downloading rulebooks/skills/playbooks
43+
/// If not set, api_endpoint is used for content downloads
44+
pub rulebook_base_url: Option<String>,
4245
/// API key for authentication
4346
pub api_key: Option<String>,
4447
/// Provider type (remote or local)
@@ -355,6 +358,10 @@ impl ProfileConfig {
355358
.api_endpoint
356359
.clone()
357360
.or_else(|| other.and_then(|config| config.api_endpoint.clone())),
361+
rulebook_base_url: self
362+
.rulebook_base_url
363+
.clone()
364+
.or_else(|| other.and_then(|config| config.rulebook_base_url.clone())),
358365
api_key: self
359366
.api_key
360367
.clone()
@@ -591,6 +598,7 @@ impl From<OldAppConfig> for ProfileConfig {
591598
fn from(old_config: OldAppConfig) -> Self {
592599
ProfileConfig {
593600
api_endpoint: Some(old_config.api_endpoint),
601+
rulebook_base_url: old_config.rulebook_base_url,
594602
api_key: old_config.api_key,
595603
..ProfileConfig::default()
596604
}

cli/src/config/types.rs

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,12 +29,18 @@ pub struct Settings {
2929
pub collect_telemetry: Option<bool>,
3030
/// Preferred external editor (e.g. vim, nano, code)
3131
pub editor: Option<String>,
32+
/// Base URL for downloading rulebooks/skills/playbooks
33+
/// If not set, api_endpoint is used for content downloads
34+
pub rulebook_base_url: Option<String>,
3235
}
3336

3437
/// Legacy configuration format for migration purposes.
3538
#[derive(Deserialize, Clone)]
3639
pub(crate) struct OldAppConfig {
3740
pub api_endpoint: String,
41+
/// Base URL for downloading rulebooks/skills/playbooks
42+
/// If not set, api_endpoint is used for content downloads
43+
pub rulebook_base_url: Option<String>,
3844
pub api_key: Option<String>,
3945
pub machine_name: Option<String>,
4046
pub auto_append_gitignore: Option<bool>,
@@ -50,6 +56,7 @@ impl From<OldAppConfig> for Settings {
5056
// DEFAULT: Telemetry is OPT-IN, never OPT-OUT
5157
collect_telemetry: Some(false),
5258
editor: Some("nano".to_string()),
59+
rulebook_base_url: old_config.rulebook_base_url,
5360
}
5461
}
55-
}
62+
}

0 commit comments

Comments
 (0)