Thanks to visit codestin.com
Credit goes to github.com

Skip to content
Merged
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
24 changes: 2 additions & 22 deletions crates/forge_main/src/cli.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
use std::path::PathBuf;

use clap::{Parser, Subcommand, ValueEnum};
use forge_domain::ConversationId;

#[derive(Parser)]
#[command(version = env!("CARGO_PKG_VERSION"))]
Expand Down Expand Up @@ -60,19 +59,12 @@ pub struct Cli {
/// Generate a new conversation ID and exit.
///
/// When enabled, generates a new unique conversation ID and prints it to
/// stdout. This ID can be used with --resume --conversation-id to
/// manage multiple terminal sessions with separate conversation
/// stdout. This ID can be used with the FORGE_CONVERSATION_ID environment
/// variable to manage multiple terminal sessions with separate conversation
/// contexts.
#[arg(long, default_value_t = false)]
pub generate_conversation_id: bool,

/// Resume an existing conversation by providing its conversation ID.
///
/// The conversation ID must be a valid UUID format. You can generate a new
/// conversation ID using --generate-conversation-id.
#[arg(long, value_parser = parse_conversation_id)]
pub resume: Option<ConversationId>,

/// Top-level subcommands
#[command(subcommand)]
pub subcommands: Option<TopLevelCommand>,
Expand All @@ -89,18 +81,6 @@ pub struct Cli {
/// The worktree name will be used as the branch name.
#[arg(long)]
pub sandbox: Option<String>,
/// Specify an agent to execute tasks with.
///
/// Allows selecting a specific agent to handle the conversation and task
/// execution. Agents provide specialized capabilities and knowledge
/// domains. Example: --agent sage, --agent planner, --agent
/// "code-reviewer"
#[arg(long, short = 'a')]
pub agent: Option<String>,
}

fn parse_conversation_id(s: &str) -> std::result::Result<ConversationId, String> {
ConversationId::parse(s).map_err(|e| e.to_string())
}

impl Cli {
Expand Down
96 changes: 96 additions & 0 deletions crates/forge_main/src/env.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
use forge_api::ConversationId;

// Environment variable names
pub const FORGE_CONVERSATION_ID: &str = "FORGE_CONVERSATION_ID";
pub const FORGE_ACTIVE_AGENT: &str = "FORGE_ACTIVE_AGENT";

/// Get conversation ID from FORGE_CONVERSATION_ID environment variable
pub fn get_conversation_id_from_env() -> Option<ConversationId> {
std::env::var(FORGE_CONVERSATION_ID)
.ok()
.and_then(|env_id| forge_domain::ConversationId::parse(&env_id).ok())
}

/// Get agent ID from FORGE_ACTIVE_AGENT environment variable
pub fn get_agent_from_env() -> Option<String> {
std::env::var(FORGE_ACTIVE_AGENT).ok()
}

#[cfg(test)]
mod tests {
use pretty_assertions::assert_eq;

use super::*;

#[test]
fn test_get_conversation_id_from_env_with_valid_id() {
let fixture_env_value = "01234567-89ab-cdef-0123-456789abcdef";
unsafe {
std::env::set_var(FORGE_CONVERSATION_ID, fixture_env_value);
}

let actual = get_conversation_id_from_env();
let expected = forge_domain::ConversationId::parse(fixture_env_value).ok();

assert_eq!(actual, expected);
unsafe {
std::env::remove_var(FORGE_CONVERSATION_ID);
}
}

#[test]
fn test_get_conversation_id_from_env_with_invalid_id() {
let fixture_env_value = "invalid-uuid";
unsafe {
std::env::set_var(FORGE_CONVERSATION_ID, fixture_env_value);
}

let actual = get_conversation_id_from_env();
let expected = None;

assert_eq!(actual, expected);
unsafe {
std::env::remove_var(FORGE_CONVERSATION_ID);
}
}

#[test]
fn test_get_conversation_id_from_env_not_set() {
unsafe {
std::env::remove_var(FORGE_CONVERSATION_ID);
}

let actual = get_conversation_id_from_env();
let expected = None;

assert_eq!(actual, expected);
}

#[test]
fn test_get_agent_from_env_with_value() {
let fixture_env_value = "sage";
unsafe {
std::env::set_var(FORGE_ACTIVE_AGENT, fixture_env_value);
}

let actual = get_agent_from_env();
let expected = Some("sage".to_string());

assert_eq!(actual, expected);
unsafe {
std::env::remove_var(FORGE_ACTIVE_AGENT);
}
}

#[test]
fn test_get_agent_from_env_not_set() {
unsafe {
std::env::remove_var(FORGE_ACTIVE_AGENT);
}

let actual = get_agent_from_env();
let expected = None;

assert_eq!(actual, expected);
}
}
1 change: 1 addition & 0 deletions crates/forge_main/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ mod cli;
mod completer;
mod conversation_selector;
mod editor;
mod env;
mod info;
mod input;
mod model;
Expand Down
9 changes: 4 additions & 5 deletions crates/forge_main/src/ui.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ use tokio_stream::StreamExt;

use crate::cli::{Cli, McpCommand, TopLevelCommand, Transport};
use crate::conversation_selector::ConversationSelector;
use crate::env::{get_agent_from_env, get_conversation_id_from_env};
use crate::info::{Info, get_usage};
use crate::input::Console;
use crate::model::{Command, ForgeCommandManager};
Expand Down Expand Up @@ -100,7 +101,6 @@ impl<A: API + 'static, F: Fn() -> A> UI<A, F> {

// Reset previously set CLI parameters by the user
self.cli.conversation = None;
self.cli.resume = None;

self.display_banner()?;
self.trace_user();
Expand Down Expand Up @@ -741,8 +741,7 @@ impl<A: API + 'static, F: Fn() -> A> UI<A, F> {
serde_json::from_str(ForgeFS::read_utf8(path.as_os_str()).await?.as_str())
.context("Failed to parse Conversation")?;
conversation
} else if let Some(conversation_id) = self.cli.resume {
// Use the explicitly provided conversation ID
} else if let Some(conversation_id) = get_conversation_id_from_env() {
// Check if conversation with this ID already exists
if let Some(conversation) = self.api.conversation(&conversation_id).await? {
conversation
Expand All @@ -765,7 +764,7 @@ impl<A: API + 'static, F: Fn() -> A> UI<A, F> {
self.spinner.stop(None)?;

let mut sub_title = conversation.id.into_string();
if let Some(ref agent) = self.cli.agent {
if let Some(ref agent) = get_agent_from_env() {
sub_title.push_str(format!(" [via {agent}]").as_str());
}

Expand All @@ -782,7 +781,7 @@ impl<A: API + 'static, F: Fn() -> A> UI<A, F> {
}
};

if let Some(ref agent) = self.cli.agent {
if let Some(ref agent) = get_agent_from_env() {
self.state.operating_agent = AgentId::new(agent)
}

Expand Down
174 changes: 174 additions & 0 deletions shell-plugin/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,174 @@
# Forge ZSH Plugin

A powerful ZSH plugin that provides intelligent command transformation, file tagging, and conversation management for the Forge AI assistant.

## Features

- **Smart Command Transformation**: Convert `:command` syntax into forge executions
- **Agent Selection**: Tab completion for available agents using `:agent_name`
- **File Tagging**: Interactive file selection with `@[filename]` syntax
- **Syntax Highlighting**: Visual feedback for commands and tagged files
- **Conversation Continuity**: Automatic session management across commands
- **Interactive Completion**: Fuzzy finding for files and agents

## Prerequisites

Before using this plugin, ensure you have the following tools installed:

- **fzf** - Command-line fuzzy finder
- **fd** - Fast file finder (alternative to find)
- **forge** - The Forge CLI tool

### Installation of Prerequisites

```bash
# macOS (using Homebrew)
brew install fzf fd

# Ubuntu/Debian
sudo apt install fzf fd-find

# Arch Linux
sudo pacman -S fzf fd
```

## Usage

### Starting a Conversation

Begin any command with `:` followed by your prompt:

```bash
: Get the current time
```

This automatically starts a new conversation with the default Forge agent.

### Using Specific Agents

Specify an agent by name after the colon:

```bash
:sage How does caching work in this system?
:muse Create a deployment strategy for my app
```

**Tab Completion**: Type `:` followed by partial agent name and press `TAB` for interactive selection.

### File Tagging

Tag files in your commands using the `@[filename]` syntax:

```bash
: Review this code @[src/main.rs]
: Explain the configuration in @[config.yaml]
```

**Interactive Selection**: Type `@` and press `TAB` to search and select files interactively using fuzzy finder.

### Conversation Continuity

Commands within the same session maintain context:

```bash
# First command
: My project uses React and TypeScript

# Second command (remembers previous context)
: How can I optimize the build process?
```

The plugin automatically manages conversation IDs to maintain context across related commands.

### Session Management

#### Resetting Sessions

Clear the current conversation context:

```bash
:reset
```

This will:

- Clear the current conversation ID
- Reset the session state
- Display a confirmation message with timestamp

#### Session Status

The plugin automatically displays session information including:

- Conversation ID when starting new sessions
- Active agent information
- Reset confirmations with timestamps

## Syntax Highlighting

The plugin provides visual feedback through syntax highlighting:

- **Tagged Files** (`@[filename]`): Displayed in **green bold**
- **Agent Commands** (`:agent`): Agent names in **yellow bold**
- **Command Text**: Remaining text in **white bold**

## Configuration

Customize the plugin behavior by setting these variables before loading the plugin:

```bash
# Custom forge binary location
export FORGE_BIN="/path/to/custom/forge"

# Custom reset command (default: "reset")
export FORGE_RESET_COMMAND="clear"
```

### Available Configuration Variables

- `FORGE_BIN`: Path to the forge executable (default: `forge`)
- Internal pattern matching for conversation syntax (`:`)
- Reset command keyword (default: `reset`)

## Advanced Features

### Command History

All transformed commands are properly saved to ZSH history, allowing you to:

- Navigate command history with arrow keys
- Search previous forge commands with `Ctrl+R`
- Reuse complex commands with file tags

### Keyboard Shortcuts

- **Tab**: Interactive completion for files (`@`) and agents (`:`)
- **Enter**: Transform and execute `:commands`
- **Ctrl+C**: Interrupt running forge commands

## Examples

### Basic Usage

```bash
: What's the weather like?
:sage Explain the MVC pattern
:planner Help me structure this project
```

### With File Tagging

```bash
: Review this implementation @[src/auth.rs]
: Debug the issue in @[logs/error.log] @[config/app.yml]
```

### Session Flow

```bash
: I'm working on a Rust web API
: What are the best practices for error handling?
: Show me an example with @[src/errors.rs]
:reset
: New conversation starts here
```
Loading
Loading