Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
3694819
add financial pricing / token calculation md
abdalla1912mohamed Jan 28, 2026
047c3f7
rebase
abdalla1912mohamed Feb 9, 2026
33149c8
rebase , apply fmt clippy
abdalla1912mohamed Feb 9, 2026
57df1e9
apply fmt , clippy
abdalla1912mohamed Feb 9, 2026
8fe2b80
remove mds
abdalla1912mohamed Feb 9, 2026
0df35c9
fix cli check
abdalla1912mohamed Feb 10, 2026
b383c01
Revert unrelated changes to tui/Cargo.toml and .gitignore
abdalla1912mohamed Feb 10, 2026
64771bc
remove extra comments
abdalla1912mohamed Feb 10, 2026
21abb04
create rfc extend commands
abdalla1912mohamed Feb 10, 2026
80cbe5e
disable dynamic loading, static load init.md form expected locations
abdalla1912mohamed Feb 10, 2026
be8823d
refactor
abdalla1912mohamed Feb 10, 2026
4aa7387
remove mds
abdalla1912mohamed Feb 10, 2026
f47ca71
add docs
abdalla1912mohamed Feb 10, 2026
25eb326
remove rfcs
abdalla1912mohamed Feb 10, 2026
5e49bb8
refactor & simplify logic
abdalla1912mohamed Feb 10, 2026
88ebeb1
apply clippy
abdalla1912mohamed Feb 10, 2026
c89c22d
fix cli timeout error
abdalla1912mohamed Feb 10, 2026
f0309b5
reduce init.md
abdalla1912mohamed Feb 10, 2026
d36570a
simplify
abdalla1912mohamed Feb 10, 2026
181f477
add custom commands
abdalla1912mohamed Feb 10, 2026
6f28776
apply fmt , clippy
abdalla1912mohamed Feb 10, 2026
bde5987
fix clippy
abdalla1912mohamed Feb 10, 2026
3d7da01
fix cli
abdalla1912mohamed Feb 10, 2026
e622bc7
add custom slash commands
abdalla1912mohamed Feb 10, 2026
69c4ba5
enhance ui
abdalla1912mohamed Feb 10, 2026
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
17 changes: 17 additions & 0 deletions cli/src/commands/agent/run/mode_interactive.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,14 @@ use stakpak_shared::models::integrations::openai::{
ChatMessage, MessageContent, Role, ToolCall, ToolCallResultStatus,
};
use stakpak_shared::models::llm::{LLMTokenUsage, PromptTokensDetails};

/// Bundled infrastructure analysis prompt (embedded at compile time)
///
/// This adaptive prompt guides the agent to detect and analyze infrastructure
/// based on available cloud provider credentials (AWS, GCP, Azure) and IaC tools
/// (Terraform, Kubernetes, etc.). It instructs the agent to focus analysis on
/// what's actually present in the environment.
const INIT_PROMPT: &str = include_str!("../../../../../libs/api/src/local/prompts/init.md");
use stakpak_shared::telemetry::{TelemetryEvent, capture_event};
use stakpak_tui::{InputEvent, LoadingOperation, OutputEvent};
use std::sync::Arc;
Expand Down Expand Up @@ -125,6 +133,8 @@ pub struct RunInteractiveConfig {
pub enabled_tools: EnabledToolsConfig,
pub model: Model,
pub agents_md: Option<AgentsMdInfo>,
/// When true, send init_prompt_content as first user message on session start (stakpak init)
pub send_init_prompt_on_start: bool,
}

pub async fn run_interactive(
Expand Down Expand Up @@ -187,6 +197,11 @@ pub async fn run_interactive(

let auth_display_info_for_tui = ctx.get_auth_display_info();
let model_for_tui = model.clone();

// Use bundled init prompt (loaded at module level as const)
let init_prompt_content_for_tui = Some(INIT_PROMPT.to_string());

let send_init_prompt_on_start = config.send_init_prompt_on_start;
let tui_handle = tokio::spawn(async move {
let latest_version = get_latest_cli_version().await;
stakpak_tui::run_tui(
Expand All @@ -205,6 +220,8 @@ pub async fn run_interactive(
model_for_tui,
editor_command,
auth_display_info_for_tui,
init_prompt_content_for_tui,
send_init_prompt_on_start,
)
.await
.map_err(|e| e.to_string())
Expand Down
7 changes: 7 additions & 0 deletions cli/src/commands/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,9 @@ pub enum Commands {
/// Get current account
Account,

/// Start a new session with init.md prompt loaded and sent automatically
Init,

/// MCP commands
#[command(subcommand)]
Mcp(McpCommands),
Expand Down Expand Up @@ -429,6 +432,10 @@ impl Commands {
let data = client.get_my_account().await?;
println!("{}", data.to_text());
}
Commands::Init => {
// Handled in main: starts interactive session with init prompt sent on start
unreachable!("stakpak init is handled before Commands::run()")
}
Commands::Version => {
println!(
"stakpak v{} (https://github.com/stakpak/agent)",
Expand Down
5 changes: 5 additions & 0 deletions cli/src/commands/watch/executor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -193,6 +193,8 @@ mod tests {

#[cfg(unix)]
use std::os::unix::fs::PermissionsExt;
#[cfg(unix)]
use std::thread;

/// Create a test script that exits with the given code.
#[cfg(unix)]
Expand All @@ -215,6 +217,9 @@ mod tests {
fs::set_permissions(&script_path, perms).expect("Failed to set permissions");
}

// Allow the OS to release file locks before execution (avoids ETXTBSY on some platforms).
thread::sleep(Duration::from_millis(10));

script_path
}

Expand Down
552 changes: 276 additions & 276 deletions cli/src/main.rs

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,8 @@ If the target topic cannot be found:
- Build container images for the deployment target architecture (most likely amd64, unless the deployment target is arm-based). This is especially important when running on apple silicon.
- Always use Python to do any math, calculations, or analysis that involves number. Python will produce more accurate and precise results.

## Custom Slash Commands
To create a custom slash command, write a markdown file to `.stakpak/commands/Usercmd_{command-name}.md` (e.g., `Usercmd_deploy-staging.md` → `/deploy-staging`). The file content becomes the prompt sent when the command is invoked. Use `$ARGUMENTS` as a placeholder for user input passed after the command name. Create a command when the user explicitly asks for a shortcut or workflow, or when you notice them repeatedly requesting the same task.
# Identity
When asked about what you can support or do always search documentation first

Expand Down
82 changes: 82 additions & 0 deletions libs/api/src/local/prompts/init.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
# Infrastructure Analysis

Analyze the infrastructure based on detected credentials and configurations. Focus only on what's present.

## Phase 1: Detection

Detect in parallel:

| Category | Check For |
|----------|-----------|
| **Cloud** | AWS (`~/.aws/`), GCP (`~/.config/gcloud/`), Azure (`~/.azure/`) |
| **IaC** | Terraform (`*.tf`), CloudFormation, Pulumi, CDK |
| **Containers** | Kubernetes manifests, Helm charts, Dockerfiles, ECS/GKE/EKS/AKS |

## Phase 2: Analysis (only for detected technologies)

### Cloud Resources
- **Inventory**: Compute, storage, databases, networking
- **Security**: IAM policies, public exposure, encryption, secrets management
- **Cost**: Monthly estimates, unused resources, optimization opportunities

### Infrastructure as Code
- Provider/version constraints, state backend, modules, hardcoded secrets

### Containers & Orchestration
- Workloads, services, RBAC, resource limits, deprecated APIs
- Dockerfile best practices (multi-stage, non-root, base images)

## Phase 3: Deliverables

1. **Executive Summary** (2-3 paragraphs)
2. **Resource Inventory** (tables)
3. **Security Findings** (prioritized: Critical → High → Medium → Low)
4. **Cost Analysis** (current spend + savings opportunities)
5. **Action Items** (prioritized recommendations)

## Phase 4: Generate AGENTS.md (REQUIRED)

Create `AGENTS.md` in the current directory with:

```markdown
# AGENTS.md

## Overview
- **Cloud**: [Provider(s), Region(s)]
- **IaC**: [Terraform/CloudFormation/etc.]
- **Orchestration**: [K8s/ECS/etc.]

## Key Components
[Compute, Databases, Storage, Networking summary]

## Deployment
\`\`\`bash
# Infrastructure
[IaC commands]

# Application
[Deploy commands]
\`\`\`

## Rollback
\`\`\`bash
[Rollback commands]
\`\`\`

## Costs
- **Monthly**: ~$X,XXX
- **Optimizations**: [List opportunities]

## Security
[Key findings and status]

## DR & Monitoring
- **Backups**: [Strategy]
- **Alerts**: [Setup]
```

## Rules
- **Read-only**: Do not modify infrastructure
- **Efficient**: Skip providers not in use
- **Cross-reference**: IaC should match live state
- **Always create AGENTS.md**
30 changes: 23 additions & 7 deletions tui/src/app.rs
Original file line number Diff line number Diff line change
Expand Up @@ -35,11 +35,12 @@ pub struct AppState {
pub text_area: TextArea,
pub text_area_state: TextAreaState,
pub cursor_visible: bool,
pub helpers: Vec<HelperCommand>,
pub helpers: Vec<HelperEntry>,
pub custom_commands: Vec<CustomCommand>,
pub show_helper_dropdown: bool,
pub helper_selected: usize,
pub helper_scroll: usize,
pub filtered_helpers: Vec<HelperCommand>,
pub filtered_helpers: Vec<HelperEntry>,
pub filtered_files: Vec<String>,
pub file_search: FileSearch,
pub file_search_tx: Option<mpsc::Sender<(String, usize)>>,
Expand Down Expand Up @@ -201,6 +202,8 @@ pub struct AppState {
pub model: Model,
/// Auth display info: (config_provider, auth_provider, subscription_name) for local providers
pub auth_display_info: (Option<String>, Option<String>, Option<String>),
/// Content of init prompt loaded from init.md in current directory (for /init)
pub init_prompt_content: Option<String>,

// ========== Misc State ==========
pub ctrl_c_pressed_once: bool,
Expand Down Expand Up @@ -265,17 +268,19 @@ pub struct AppStateOptions<'a> {
pub auth_display_info: (Option<String>, Option<String>, Option<String>),
/// Agent board ID for task tracking (from AGENT_BOARD_AGENT_ID env var)
pub board_agent_id: Option<String>,
/// Content of init prompt loaded from init.md (passed from CLI)
pub init_prompt_content: Option<String>,
}

impl AppState {
pub fn get_helper_commands() -> Vec<HelperCommand> {
// Use unified command system
crate::services::commands::commands_to_helper_commands()
/// Built-in + custom commands merged. Custom commands from .stakpak/commands/*.md
pub fn get_helper_commands(custom_commands: &[CustomCommand]) -> Vec<HelperEntry> {
crate::services::commands::get_helper_commands(custom_commands)
}

/// Initialize file search channels and spawn worker
fn init_file_search_channels(
helpers: &[HelperCommand],
helpers: &[HelperEntry],
) -> (
mpsc::Sender<(String, usize)>,
mpsc::Receiver<FileSearchResult>,
Expand Down Expand Up @@ -307,9 +312,11 @@ impl AppState {
editor_command,
auth_display_info,
board_agent_id,
init_prompt_content,
} = options;

let helpers = Self::get_helper_commands();
let custom_commands = crate::services::commands::scan_custom_commands();
let helpers = Self::get_helper_commands(&custom_commands);
let (file_search_tx, result_rx) = Self::init_file_search_channels(&helpers);

AppState {
Expand All @@ -322,6 +329,7 @@ impl AppState {
stay_at_bottom: true,
content_changed_while_scrolled_up: false,
helpers: helpers.clone(),
custom_commands,
show_helper_dropdown: false,
helper_selected: 0,
helper_scroll: 0,
Expand Down Expand Up @@ -504,6 +512,7 @@ impl AppState {
billing_info: None,
auth_display_info,
subagent_pause_info: HashMap::new(),
init_prompt_content,
}
}

Expand Down Expand Up @@ -627,6 +636,13 @@ impl AppState {
// Update filtered_helpers from async worker
self.filtered_helpers = result.filtered_helpers;

// Dynamic reload: when slash triggered, update custom_commands and helpers
if let Some(custom) = result.custom_commands {
self.custom_commands = custom;
self.helpers =
crate::services::commands::get_helper_commands(&self.custom_commands);
}

// Reset selection index if it's out of bounds
if !self.filtered_helpers.is_empty()
&& self.helper_selected >= self.filtered_helpers.len()
Expand Down
51 changes: 50 additions & 1 deletion tui/src/app/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -99,10 +99,12 @@ impl RenderMetrics {

/// Async file_search result struct
pub struct FileSearchResult {
pub filtered_helpers: Vec<HelperCommand>,
pub filtered_helpers: Vec<HelperEntry>,
pub filtered_files: Vec<String>,
pub cursor_position: usize,
pub input: String,
/// Fresh custom commands when slash triggered (for dynamic reload)
pub custom_commands: Option<Vec<CustomCommand>>,
}

#[derive(Debug, Clone)]
Expand All @@ -111,6 +113,53 @@ pub struct HelperCommand {
pub description: &'static str,
}

/// User-defined command loaded from .stakpak/commands/*.md or ~/.stakpak/commands/*.md
#[derive(Debug, Clone)]
pub struct CustomCommand {
pub id: String,
pub description: String,
/// Cached file content (loaded at init to keep execute_command sync)
pub content: String,
}

/// Unified entry for the helper dropdown: built-in or custom command.
#[derive(Debug, Clone)]
pub enum HelperEntry {
Builtin(HelperCommand),
Custom {
/// The command ID used for execution (e.g., "/security-review")
command: String,
/// The display string shown in dropdown (e.g., "/usercmd/security-review")
display: String,
description: String,
},
}

impl HelperEntry {
/// Returns the command ID used for execution
pub fn command(&self) -> &str {
match self {
HelperEntry::Builtin(h) => h.command,
HelperEntry::Custom { command, .. } => command.as_str(),
}
}

/// Returns the display string for the dropdown
pub fn display(&self) -> &str {
match self {
HelperEntry::Builtin(h) => h.command,
HelperEntry::Custom { display, .. } => display.as_str(),
}
}

pub fn description(&self) -> &str {
match self {
HelperEntry::Builtin(h) => h.description,
HelperEntry::Custom { description, .. } => description.as_str(),
}
}
}

#[derive(Debug, Clone)]
pub struct AttachedImage {
pub placeholder: String,
Expand Down
13 changes: 13 additions & 0 deletions tui/src/event_loop.rs
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,8 @@ pub async fn run_tui(
model: Model,
editor_command: Option<String>,
auth_display_info: (Option<String>, Option<String>, Option<String>),
init_prompt_content: Option<String>,
send_init_prompt_on_start: bool,
) -> io::Result<()> {
let _guard = TerminalGuard;

Expand Down Expand Up @@ -98,6 +100,7 @@ pub async fn run_tui(
editor_command,
auth_display_info,
board_agent_id,
init_prompt_content,
});

// Set mouse_capture_enabled based on terminal detection (matches the execute logic above)
Expand All @@ -124,6 +127,16 @@ pub async fn run_tui(
let _ = internal_tx.try_send(InputEvent::RefreshBoardTasks);
}

// When started via `stakpak init`, add init prompt as user message and send to backend
if send_init_prompt_on_start
&& let Some(prompt) = state.init_prompt_content.clone()
&& !prompt.trim().is_empty()
{
state.messages.push(Message::user(prompt.clone(), None));
crate::services::message::invalidate_message_lines_cache(&mut state);
let _ = output_tx.try_send(OutputEvent::UserMessage(prompt, None, Vec::new()));
}

let internal_tx_thread = internal_tx.clone();
// Create atomic pause flag for input thread
let input_paused = Arc::new(AtomicBool::new(false));
Expand Down
Loading