Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
39 commits
Select commit Hold shift + click to select a range
3985700
deps: 添加 charmbracelet/huh 表单库依赖
wuchulonly Jan 12, 2026
744f7d9
feat(wizard): 添加 Wizard 核心框架
wuchulonly Jan 12, 2026
7cac55a
feat(wizard): 添加预定义 Wizard 模板
wuchulonly Jan 12, 2026
f6e17d0
feat(wizard): 添加 Lua 绑定支持
wuchulonly Jan 12, 2026
6ff4c41
feat(wizard): 添加 CLI 命令
wuchulonly Jan 12, 2026
2142ca9
feat(wizard): 集成到 Lua VM
wuchulonly Jan 12, 2026
81be767
test(wizard): 添加单元测试
wuchulonly Jan 12, 2026
7b48751
refactor(client): 清理 wizard 相关导入
wuchulonly Jan 12, 2026
7bee7d1
feat(ai): 添加 AI 客户端核心功能
wuchulonly Jan 12, 2026
c34147c
feat(ai): 添加 AI 命令行接口
wuchulonly Jan 12, 2026
59f1f59
feat(ai): 添加命令验证器
wuchulonly Jan 12, 2026
376eb9b
feat(ai): 添加 AI 命令补全器
wuchulonly Jan 12, 2026
5f2788b
feat(console): 集成 AI 补全到控制台
wuchulonly Jan 12, 2026
346d77d
feat(readline): 添加 AI 补全快捷键支持
wuchulonly Jan 12, 2026
9a509b1
feat(settings): 添加 AI 配置选项
wuchulonly Jan 12, 2026
8d8e925
feat(core): 集成 AI 补全引擎到 Console
wuchulonly Jan 12, 2026
54e8b75
feat(wizard): 增强 wizard 核心框架
wuchulonly Jan 13, 2026
d312eee
feat(wizard): 添加 YAML/JSON spec 文件支持
wuchulonly Jan 13, 2026
465ec7b
feat(wizard): 添加 Lua 脚本集成支持
wuchulonly Jan 13, 2026
6918f09
feat(wizard): 实现分组命令系统
wuchulonly Jan 13, 2026
d245904
feat(wizard): 添加 wizard 执行器框架
wuchulonly Jan 13, 2026
7f62646
feat(plugin): 支持插件注册 wizard 模板
wuchulonly Jan 13, 2026
5987e62
feat(readline): 增强补全引擎
wuchulonly Jan 13, 2026
77ccc37
chore(genhelp): 更新帮助文档生成器
wuchulonly Jan 13, 2026
72c644b
feat(wizard): 添加 build 系列执行器
wuchulonly Jan 13, 2026
bd30639
feat(wizard): 添加紧凑型两层选择器 UI
wuchulonly Jan 13, 2026
45d3a63
feat(ai): 增强 AI 补全引擎支持菜单作用域
wuchulonly Jan 13, 2026
6ccbd3e
feat(readline): 改进 AI 补全显示逻辑
wuchulonly Jan 13, 2026
a8927b4
feat(wizard): 添加动态选项和编辑模式支持
wuchulonly Jan 13, 2026
ff8b4ca
feat(wizard): 添加命令补全和动态选项提供者
wuchulonly Jan 13, 2026
0ac4064
feat(commands): 改进命令帮助文档和补全
wuchulonly Jan 13, 2026
0b0abf0
refactor(wizard): 重构表单组件并增强 AI 补全
wuchulonly Jan 14, 2026
0c4f3b6
feat(wizard): 添加可选分组支持
wuchulonly Jan 15, 2026
f1af94e
feat(wizard): 添加 Cobra 命令转 Wizard 功能
wuchulonly Jan 15, 2026
a6e99c6
feat(wizard): 添加全局 --wizard 标志支持
wuchulonly Jan 15, 2026
5e3c9c3
test(wizard): 添加可选分组和 Cobra 转换测试
wuchulonly Jan 15, 2026
2b1d1ad
refactor(wizard): 国际化和兼容性改进
wuchulonly Jan 17, 2026
e686e25
fix: 恢复原版依赖并完成国际化
wuchulonly Jan 17, 2026
fccd136
chore: 更新 go.mod 依赖
wuchulonly Jan 17, 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
103 changes: 86 additions & 17 deletions client/assets/settings.go
Original file line number Diff line number Diff line change
@@ -1,11 +1,8 @@
package assets

import (
"encoding/json"
"github.com/chainreactors/IoM-go/proto/client/clientpb"
"github.com/chainreactors/malice-network/helper/utils/configutil"
"io/ioutil"
"path/filepath"
"github.com/gookit/config/v2"
)

//var (
Expand All @@ -20,10 +17,24 @@ type Settings struct {
LocalRPCEnable bool `yaml:"localrpc_enable" config:"localrpc_enable" default:"false"`
LocalRPCAddr string `yaml:"localrpc_addr" config:"localrpc_addr" default:"127.0.0.1:15004"`
Github *GithubSetting `yaml:"github" config:"github"`
AI *AISettings `yaml:"ai" config:"ai"`

//VtApiKey string `yaml:"vt_api_key" config:"vt_api_key" default:""`
}

// AISettings holds configuration for AI assistant integration
type AISettings struct {
Enable bool `yaml:"enable" config:"enable" default:"false"`
Provider string `yaml:"provider" config:"provider" default:"openai"` // openai, claude
APIKey string `yaml:"api_key" config:"api_key" default:""`
Endpoint string `yaml:"endpoint" config:"endpoint" default:"https://api.openai.com/v1"`
Model string `yaml:"model" config:"model" default:"gpt-4"`
MaxTokens int `yaml:"max_tokens" config:"max_tokens" default:"1024"`
Timeout int `yaml:"timeout" config:"timeout" default:"30"`
HistorySize int `yaml:"history_size" config:"history_size" default:"20"`
OpsecCheck bool `yaml:"opsec_check" config:"opsec_check" default:"false"` // Enable AI OPSEC risk assessment
}

type GithubSetting struct {
Repo string `yaml:"repo" config:"repo" default:""`
Owner string `yaml:"owner" config:"owner" default:""`
Expand All @@ -44,17 +55,24 @@ func (github *GithubSetting) ToProtobuf() *clientpb.GithubActionBuildConfig {
}

func LoadSettings() (*Settings, error) {
rootDir, _ := filepath.Abs(GetRootAppDir())
//data, err := os.ReadFile(filepath.Join(rootDir, settingsFileName))
//if err != nil {
// return defaultSettings(), err
//}
settings := defaultSettings()
err := configutil.LoadConfig(filepath.Join(rootDir, maliceProfile), settings)
setting, err := GetSetting()
if err == nil && setting != nil {
return setting, nil
}

_, loadErr := LoadProfile()
if loadErr != nil {
return defaultSettings(), loadErr
}

setting, err = GetSetting()
if err != nil {
return defaultSettings(), err
}
return settings, nil
if setting == nil {
return defaultSettings(), nil
}
return setting, nil
}

func defaultSettings() *Settings {
Expand All @@ -68,16 +86,67 @@ func defaultSettings() *Settings {
}
}

// setConfigs sets multiple config key-value pairs, returning the first error encountered.
func setConfigs(kvs [][2]interface{}) error {
for _, kv := range kvs {
if err := config.Set(kv[0].(string), kv[1]); err != nil {
return err
}
}
return nil
}

// SaveSettings - Save the current settings to disk
func SaveSettings(settings *Settings) error {
rootDir, _ := filepath.Abs(GetRootAppDir())
if settings == nil {
settings = defaultSettings()
}
data, err := json.MarshalIndent(settings, "", " ")
if err != nil {

// Ensure profile is loaded so we don't overwrite unrelated config sections.
if _, err := LoadProfile(); err != nil {
return err
}

// Top-level settings
if err := setConfigs([][2]interface{}{
{"settings.max_server_log_size", settings.MaxServerLogSize},
{"settings.opsec_threshold", settings.OpsecThreshold},
{"settings.mcp_enable", settings.McpEnable},
{"settings.mcp_addr", settings.McpAddr},
{"settings.localrpc_enable", settings.LocalRPCEnable},
{"settings.localrpc_addr", settings.LocalRPCAddr},
}); err != nil {
return err
}
err = ioutil.WriteFile(filepath.Join(rootDir, maliceProfile), data, 0600)
return err

// Github settings
if settings.Github != nil {
if err := setConfigs([][2]interface{}{
{"settings.github.repo", settings.Github.Repo},
{"settings.github.owner", settings.Github.Owner},
{"settings.github.token", settings.Github.Token},
{"settings.github.workflow", settings.Github.Workflow},
}); err != nil {
return err
}
}

// AI settings
if settings.AI != nil {
if err := setConfigs([][2]interface{}{
{"settings.ai.enable", settings.AI.Enable},
{"settings.ai.provider", settings.AI.Provider},
{"settings.ai.api_key", settings.AI.APIKey},
{"settings.ai.endpoint", settings.AI.Endpoint},
{"settings.ai.model", settings.AI.Model},
{"settings.ai.max_tokens", settings.AI.MaxTokens},
{"settings.ai.timeout", settings.AI.Timeout},
{"settings.ai.history_size", settings.AI.HistorySize},
{"settings.ai.opsec_check", settings.AI.OpsecCheck},
}); err != nil {
return err
}
}

return nil
}
10 changes: 7 additions & 3 deletions client/cmd/cli/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,14 +20,18 @@ func rootCmd(con *core.Console) (*cobra.Command, error) {
}
cmd.TraverseChildren = true

// 添加 --mcp flag
// Add --mcp flag
cmd.PersistentFlags().String("mcp", "", "enable MCP server with address (e.g., 127.0.0.1:5005)")
// 添加 --rpc flag
// Add --rpc flag
cmd.PersistentFlags().String("rpc", "", "enable local gRPC server with address (e.g., 127.0.0.1:15004)")
// Add global --wizard flag
command.RegisterWizardFlag(cmd)

bind := command.MakeBind(cmd, con, "golang")
command.BindCommonCommands(bind)
cmd.PersistentPreRunE, cmd.PersistentPostRunE = command.ConsoleRunnerCmd(con, cmd)
// Wrap PersistentPreRunE to support wizard mode
originalPre, originalPost := command.ConsoleRunnerCmd(con, cmd)
cmd.PersistentPreRunE, cmd.PersistentPostRunE = command.WrapWithWizardSupport(con, originalPre, originalPost)
cmd.AddCommand(command.ImplantCmd(con))
carapace.Gen(cmd)

Expand Down
8 changes: 6 additions & 2 deletions client/cmd/genhelp/gen_help.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,13 @@
package main

import (
"bytes"
"fmt"
"io"
"os"
"sort"
"strings"

"github.com/chainreactors/IoM-go/consts"
"github.com/chainreactors/IoM-go/proto/services/clientrpc"
"github.com/chainreactors/malice-network/client/assets"
Expand Down Expand Up @@ -42,8 +48,6 @@ import (
"github.com/gookit/config/v2"
"github.com/gookit/config/v2/yaml"
"github.com/spf13/cobra"
"io"
"os"
)

func init() {
Expand Down
135 changes: 135 additions & 0 deletions client/command/ai/analyze.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
package ai

import (
"context"
"fmt"
"strings"
"time"

"github.com/chainreactors/malice-network/client/assets"
"github.com/chainreactors/malice-network/client/core"
"github.com/spf13/cobra"
)

// AnalyzeCmd handles the analyze command - analyzes errors and provides suggestions
func AnalyzeCmd(cmd *cobra.Command, con *core.Console, args []string) error {
settings, err := assets.LoadSettings()
if err != nil {
return fmt.Errorf("failed to load settings: %w", err)
}

if settings.AI == nil || !settings.AI.Enable {
return fmt.Errorf("AI is not enabled. Use 'ai-config --enable' to enable it")
}

if settings.AI.APIKey == "" {
return fmt.Errorf("AI API key is not configured. Use 'ai-config --api-key <key>' to set it")
}

// Get the error to analyze
var errorText string
if len(args) > 0 {
errorText = strings.Join(args, " ")
}

if errorText == "" {
return fmt.Errorf("please provide an error message to analyze. Usage: analyze <error message>")
}

// Get context
historySize := settings.AI.HistorySize
if historySize <= 0 {
historySize = 20
}
history := con.GetRecentHistory(historySize)

// Build session context if available
sessionContext := buildSessionContext(con)

// Build the analysis prompt
prompt := buildAnalysisPrompt(errorText, history, sessionContext)

aiClient := core.NewAIClient(settings.AI)

timeout := settings.AI.Timeout
if timeout <= 0 {
timeout = 30
}
ctx, cancel := context.WithTimeout(context.Background(), time.Duration(timeout)*time.Second)
defer cancel()

fmt.Println("\nAnalyzing error...")
fmt.Println()

// Use streaming for real-time output
response, err := aiClient.AskStream(ctx, prompt, nil, func(chunk string) {
fmt.Print(chunk)
})
if err != nil {
return fmt.Errorf("AI analysis failed: %w", err)
}

fmt.Println()

// Parse command suggestions
commands := core.ParseCommandSuggestions(response)
if len(commands) > 0 {
fmt.Println("\nSuggested commands:")
for i, cmd := range commands {
fmt.Printf(" [%d] %s\n", i+1, cmd.Command)
}
}

fmt.Println()
return nil
}

func buildSessionContext(con *core.Console) string {
var sb strings.Builder

session := con.GetInteractive()
if session != nil {
sb.WriteString(fmt.Sprintf("Current session: %s\n", session.SessionId))
if session.Os != nil {
sb.WriteString(fmt.Sprintf("OS: %s %s\n", session.Os.Name, session.Os.Arch))
}
if session.Process != nil {
sb.WriteString(fmt.Sprintf("Process: %s (PID: %d)\n", session.Process.Name, session.Process.Pid))
sb.WriteString(fmt.Sprintf("User: %s\n", session.Process.Owner))
}
} else {
sb.WriteString("No active session\n")
}

return sb.String()
}

func buildAnalysisPrompt(errorText string, history []string, sessionContext string) string {
var sb strings.Builder

sb.WriteString("Analyze the following error and provide:\n")
sb.WriteString("1. Possible causes of the error\n")
sb.WriteString("2. Suggested solutions or workarounds\n")
sb.WriteString("3. Alternative commands that might work\n\n")

sb.WriteString("Error message:\n")
sb.WriteString(errorText)
sb.WriteString("\n\n")

if sessionContext != "" {
sb.WriteString("Session context:\n")
sb.WriteString(sessionContext)
sb.WriteString("\n")
}

if len(history) > 0 {
sb.WriteString("Recent command history:\n")
for _, cmd := range history {
sb.WriteString(fmt.Sprintf("- %s\n", cmd))
}
}

sb.WriteString("\nProvide a concise analysis. Wrap any command suggestions in backticks like `command`.")

return sb.String()
}
Loading