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
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -16,3 +16,4 @@ record.log.*

# Log files
*.log
docs/blog
2 changes: 1 addition & 1 deletion collector/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -870,7 +870,7 @@ async fn start_web_server_if_enabled(
return Ok(None);
}

let addr = format!("127.0.0.1:{}", port).parse()
let addr = format!("0.0.0.0:{}", port).parse()
.map_err(|e| format!("Invalid server address: {}", e))?;

let web_server = WebServer::new(event_sender, log_file).map_err(|e| format!("Failed to create web server: {}", e))?;
Expand Down
73 changes: 58 additions & 15 deletions collector/src/server/assets.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@

use rust_embed::RustEmbed;
use std::borrow::Cow;
use std::path::PathBuf;
use std::path::{Path, PathBuf};
use std::fs;
use mime_guess::from_path;

Expand All @@ -12,14 +12,29 @@ use mime_guess::from_path;
pub struct FrontendDist;

pub struct FrontendAssets {
temp_dir: PathBuf,
serve_dir: PathBuf,
/// Whether we own the directory (temp extraction) and should clean it up on drop.
owned: bool,
}

impl FrontendAssets {
pub fn new() -> Result<Self, Box<dyn std::error::Error + Send + Sync>> {
// Dev mode: serve directly from a disk directory if env var is set
if let Ok(dist_path) = std::env::var("AGENTSIGHT_FRONTEND_DIST") {
let dir = PathBuf::from(&dist_path);
if !dir.join("index.html").exists() {
return Err(format!(
"AGENTSIGHT_FRONTEND_DIST={} does not contain index.html",
dist_path
).into());
}
log::info!("📁 Dev mode: serving frontend from disk: {}", dir.display());
return Ok(Self { serve_dir: dir, owned: false });
}

let temp_dir = std::env::temp_dir().join(format!("agentsight-frontend-{}", uuid::Uuid::new_v4()));
fs::create_dir_all(&temp_dir)?;

// Extract all embedded assets to temp directory
for file_path in FrontendDist::iter() {
if let Some(content) = FrontendDist::get(&file_path) {
Expand All @@ -30,23 +45,23 @@ impl FrontendAssets {
fs::write(&full_path, &content.data)?;
}
}

log::info!("📁 Extracted frontend assets to: {}", temp_dir.display());
Ok(Self { temp_dir })
Ok(Self { serve_dir: temp_dir, owned: true })
}

/// Get any asset by path from the extracted temp directory
/// Get any asset by path from the serve directory
pub fn get(&self, path: &str) -> Option<Cow<'static, [u8]>> {
// Handle root path
let file_path = if path == "/" || path == "/index.html" {
self.temp_dir.join("index.html")
self.serve_dir.join("index.html")
} else {
// Remove leading slash for file lookup
let normalized_path = path.strip_prefix('/').unwrap_or(path);
self.temp_dir.join(normalized_path)
self.serve_dir.join(normalized_path)
};

// Try to read from temp directory
// Try to read from serve directory
if let Ok(content) = fs::read(&file_path) {
Some(Cow::Owned(content))
} else {
Expand All @@ -68,20 +83,48 @@ impl FrontendAssets {
from_path(file_path).first_or_octet_stream().to_string()
}

/// List all available assets from the embedded dist
/// List all available assets
pub fn list_all_assets(&self) -> Vec<String> {
FrontendDist::iter().map(|s| s.to_string()).collect()
if self.owned {
// Embedded mode: use RustEmbed iterator
FrontendDist::iter().map(|s| s.to_string()).collect()
} else {
// Dev mode: walk the disk directory
let mut files = Vec::new();
if let Ok(entries) = walkdir(&self.serve_dir, &self.serve_dir) {
files = entries;
}
files
}
}
}

impl Drop for FrontendAssets {
fn drop(&mut self) {
if self.temp_dir.exists() {
if let Err(e) = fs::remove_dir_all(&self.temp_dir) {
log::warn!("Failed to cleanup temp directory {}: {}", self.temp_dir.display(), e);
if !self.owned {
return;
}
if self.serve_dir.exists() {
if let Err(e) = fs::remove_dir_all(&self.serve_dir) {
log::warn!("Failed to cleanup temp directory {}: {}", self.serve_dir.display(), e);
} else {
log::info!("🧹 Cleaned up temp directory: {}", self.temp_dir.display());
log::info!("🧹 Cleaned up temp directory: {}", self.serve_dir.display());
}
}
}
}

/// Recursively list files under `dir`, returning paths relative to `root`.
fn walkdir(dir: &Path, root: &Path) -> std::io::Result<Vec<String>> {
let mut result = Vec::new();
for entry in fs::read_dir(dir)? {
let entry = entry?;
let path = entry.path();
if path.is_dir() {
result.extend(walkdir(&path, root)?);
} else if let Ok(rel) = path.strip_prefix(root) {
result.push(rel.to_string_lossy().into_owned());
}
}
Ok(result)
}
38 changes: 38 additions & 0 deletions docs/development.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
# Development Guide

**English** | [中文](development.zh-CN.md)

## Frontend Dev Mode (Disk-First Assets)

The collector binary embeds frontend assets via `RustEmbed` at compile time. By default, every frontend change requires recompiling the collector (`cargo build --release`) to take effect.

To speed up frontend development, set the `AGENTSIGHT_FRONTEND_DIST` environment variable to serve assets directly from disk. This way you only need to rebuild the frontend and restart the collector — no Rust recompilation needed.

### Usage

```sh
# 1. Build the frontend
make build-frontend

# 2. Start the collector with disk-based frontend assets
AGENTSIGHT_FRONTEND_DIST=./frontend/dist sudo -E ./target/release/agentsight record -c claude --binary-path <path>
```

After each frontend change:

```sh
make build-frontend
# Restart the collector — changes take effect immediately, no cargo build needed
```

### How it works

- On startup, the collector checks for the `AGENTSIGHT_FRONTEND_DIST` environment variable.
- **Set** — serves files directly from the specified directory, skipping the embedded asset extraction. The directory must contain `index.html`.
- **Not set** — falls back to the default behavior: extracts `RustEmbed` assets to a temp directory and cleans up on exit.

### Notes

- Use `sudo -E` to preserve the environment variable when running with sudo.
- The path can be relative (e.g., `./frontend/dist`) or absolute.
- In production, do not set this variable — the embedded assets will be used as usual.
38 changes: 38 additions & 0 deletions docs/development.zh-CN.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
# 开发指南

[English](development.md) | **中文**

## 前端开发模式(磁盘优先加载)

collector 二进制在编译时通过 `RustEmbed` 将前端资源内嵌。默认情况下,每次前端改动都需要重新编译 collector(`cargo build --release`)才能生效。

为加速前端开发,可设置 `AGENTSIGHT_FRONTEND_DIST` 环境变量,让 collector 直接从磁盘目录读取前端资源。这样只需重新构建前端并重启 collector,无需重新编译 Rust 代码。

### 使用方法

```sh
# 1. 构建前端
make build-frontend

# 2. 设置环境变量启动 collector
AGENTSIGHT_FRONTEND_DIST=./frontend/dist sudo -E ./collector/target/release/agentsight record -c claude --binary-path /opt/node-v22.20.0/bin/node
```

之后每次修改前端:

```sh
make build-frontend
# 重启 collector 即可生效,无需 cargo build
```

### 工作原理

- collector 启动时检查 `AGENTSIGHT_FRONTEND_DIST` 环境变量。
- **已设置** — 直接从指定目录读取文件,跳过内嵌资源解压流程。目录中必须包含 `index.html`。
- **未设置** — 使用默认行为:将 `RustEmbed` 内嵌资源解压到临时目录,退出时自动清理。

### 注意事项

- 使用 `sudo -E` 以在 sudo 下保留环境变量。
- 路径支持相对路径(如 `./frontend/dist`)和绝对路径。
- 生产环境中不要设置此变量,将正常使用内嵌资源。
42 changes: 41 additions & 1 deletion docs/usage.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,49 @@

**English** | [中文](usage.zh-CN.md)

## Building from Source

### 1. Clone the repository and initialize submodules

```sh
git clone https://github.com/eunomia-bpf/agentsight.git
cd agentsight
git submodule update --init --recursive
```

If you have already cloned the repository but the submodule directories (`libbpf/` and `bpftool/`) are empty, run:

```sh
git submodule update --init --recursive
```

### 2. Install system dependencies

```sh
make install
```

This installs the required build dependencies: libelf, zlib, clang, llvm, Node.js, and the Rust toolchain.

### 3. Build

```sh
make build
```

After a successful build, the agentsight binary is located at `collector/target/release/agentsight`.

You can also build individual components:

```sh
make build-bpf # eBPF C programs only
make build-rust # Rust collector only
make build-frontend # Frontend only
```

## Command-line parameters for monitoring Claude Code with agentsight

After successfully compiling from source, the agentsight binary is located in the `collector/target/release/` directory under the project root. Navigate to the source code root directory and run the following commands to test:
Navigate to the source code root directory and run the following commands to test:

```sh
sudo ./collector/target/release/agentsight ssl --http-parser --http-filter "request.path_prefix=/v1/rgstr | response.status_code=202 | request.method=HEAD | response.body=" --ssl-filter "data=0\r\n\r\n"
Expand Down
92 changes: 91 additions & 1 deletion docs/usage.zh-CN.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,49 @@

[English](usage.md) | **中文**

## 从源代码编译

### 1. 克隆仓库并初始化子模块

```sh
git clone https://github.com/eunomia-bpf/agentsight.git
cd agentsight
git submodule update --init --recursive
```

如果你已经克隆过仓库但尚未初始化子模块(`libbpf/` 和 `bpftool/` 目录为空),请执行:

```sh
git submodule update --init --recursive
```

### 2. 安装系统依赖

```sh
make install
```

这会安装编译所需的 libelf、zlib、clang、llvm、Node.js 和 Rust 工具链。

### 3. 编译

```sh
make build
```

编译成功后,agentsight 二进制程序生成在 `collector/target/release/agentsight`。

也可以单独编译各组件:

```sh
make build-bpf # 仅编译 eBPF C 程序
make build-rust # 仅编译 Rust collector
make build-frontend # 仅编译前端
```

## 使用agentsight监测Claude Code的命令行参数

使用源代码编译成功后,agentsight二进制程序生成位置在当前目录下的collector/target/release/目录下,进入源代码的根目录,然后执行如下命令来测试:
进入源代码的根目录,然后执行如下命令来测试:

```sh
sudo ./collector/target/release/agentsight ssl --http-parser --http-filter "request.path_prefix=/v1/rgstr | response.status_code=202 | request.method=HEAD | response.body=" --ssl-filter "data=0\r\n\r\n"
Expand All @@ -17,3 +57,53 @@ sudo ./collector/target/release/agentsight agent -c "claude" --http-parser --htt
```sh
sudo ./collector/target/release/agentsight agent -c claude --http-filter "request.path_prefix=/v1/rgstr | response.status_code=202 | request.method=HEAD | response.body=" --ssl-filter "data=0\r\n\r\n|data.type=binary"
```

## record 与 trace 子命令对比

agentsight 提供 `record` 和 `trace` 两个子命令,它们共用同一个底层执行逻辑,但面向不同的使用场景。

### record — 开箱即用的智能体录制

适用于快速录制 AI 智能体(Claude Code、Python AI 工具等)的行为,无需关心细节配置。

- `-c/--comm` 是**必填**参数,如 `-c claude`
- **自动开启**:SSL 监控 + 进程监控 + 系统监控 + Web 服务器(端口 7395)
- **内置过滤规则**:自动过滤掉注册请求(`/v1/rgstr`)、HEAD 请求、空响应体、202 状态码、二进制数据等噪音
- 默认**静默模式**(不输出到控制台),数据写入 `record.log`
- 默认开启**日志轮转**

典型用法:

```sh
sudo ./agentsight record -c claude --binary-path <path>
```

### trace — 完全可控的灵活监控

适用于需要自定义监控范围、过滤规则的调试和分析场景。

- **无必填参数**,所有功能独立开关
- SSL(`--ssl`)、进程(`--process`)默认开启,但可关闭
- 系统监控(`--system`)、stdio 捕获(`--stdio`)、Web 服务器(`--server`)默认**关闭**,需手动开启
- 过滤规则完全由用户通过 `--ssl-filter`、`--http-filter` 自定义
- 默认输出到控制台,可用 `-q` 静默

典型用法:

```sh
sudo ./agentsight trace --ssl true --process false --server true --http-filter "request.method=POST"
```

### 对比总结

| 维度 | record | trace |
|------|--------|-------|
| 定位 | 一键录制,预设优化 | 灵活定制,精细控制 |
| 必填参数 | `-c <comm>` | 无 |
| Web 服务器 | 始终开启 | 需 `--server true` |
| 系统监控 | 始终开启 | 需 `--system true` |
| 控制台输出 | 默认关闭 | 默认开启 |
| 过滤规则 | 内置预设 | 用户自定义 |
| 日志轮转 | 默认开启 | 需 `--rotate-logs` |

简单来说:**日常录制用 `record`,深度调试用 `trace`**。
5 changes: 4 additions & 1 deletion frontend/src/app/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

import type { Metadata } from 'next'
import './globals.css'
import { I18nProvider } from '@/i18n'

export const metadata: Metadata = {
title: 'Agent Tracer Frontend',
Expand All @@ -17,7 +18,9 @@ export default function RootLayout({
return (
<html lang="en">
<body className="antialiased">
{children}
<I18nProvider>
{children}
</I18nProvider>
</body>
</html>
)
Expand Down
Loading
Loading