This commit is contained in:
lpf
2026-02-16 23:56:54 +08:00
parent 2f0d8503b4
commit fb404cebf1
6 changed files with 604 additions and 292 deletions

View File

@@ -262,25 +262,16 @@ func printHelp() {
func onboard() {
configPath := getConfigPath()
if _, err := os.Stat(configPath); err == nil {
fmt.Printf("Config already exists at %s\n", configPath)
fmt.Print("Overwrite? (y/n): ")
var response string
fmt.Scanln(&response)
if response != "y" {
fmt.Println("Aborted.")
return
}
}
cfg := config.DefaultConfig()
if strings.EqualFold(strings.TrimSpace(os.Getenv(envRootGranted)), "1") || strings.EqualFold(strings.TrimSpace(os.Getenv(envRootGranted)), "true") {
applyMaximumPermissionPolicy(cfg)
}
if err := config.SaveConfig(configPath, cfg); err != nil {
fmt.Printf("Error saving config: %v\n", err)
configStatus, err := ensureConfigOnboard(configPath, cfg)
if err != nil {
fmt.Printf("Error preparing config: %v\n", err)
os.Exit(1)
}
fmt.Printf("Config: %s (%s)\n", configPath, configStatus)
workspace := cfg.WorkspacePath()
if err := os.MkdirAll(workspace, 0755); err != nil {
@@ -308,80 +299,91 @@ func onboard() {
fmt.Println(" 2. Chat: clawgo agent -m \"Hello!\"")
}
func ensureConfigOnboard(configPath string, defaults *config.Config) (string, error) {
if defaults == nil {
return "", fmt.Errorf("defaults is nil")
}
if _, err := os.Stat(configPath); os.IsNotExist(err) {
if err := config.SaveConfig(configPath, defaults); err != nil {
return "", err
}
return "created", nil
} else if err != nil {
return "", err
}
defaultData, err := json.Marshal(defaults)
if err != nil {
return "", err
}
var defaultMap map[string]interface{}
if err := json.Unmarshal(defaultData, &defaultMap); err != nil {
return "", err
}
existingMap, err := configops.LoadConfigAsMap(configPath)
if err != nil {
return "", err
}
changed := mergeMissingConfigValues(existingMap, defaultMap)
if !changed {
return "up-to-date", nil
}
mergedData, err := json.MarshalIndent(existingMap, "", " ")
if err != nil {
return "", err
}
if _, err := configops.WriteConfigAtomicWithBackup(configPath, mergedData); err != nil {
return "", err
}
return "updated (incremental)", nil
}
func mergeMissingConfigValues(dst map[string]interface{}, defaults map[string]interface{}) bool {
changed := false
for key, dv := range defaults {
existing, ok := dst[key]
if !ok {
dst[key] = dv
changed = true
continue
}
dm, dIsMap := dv.(map[string]interface{})
em, eIsMap := existing.(map[string]interface{})
if dIsMap && eIsMap {
if mergeMissingConfigValues(em, dm) {
changed = true
}
}
}
return changed
}
func createWorkspaceTemplates(workspace string) error {
managedFallbacks := map[string]string{
"AGENTS.md": `# Autonomy Intent Policy
For autonomy-mode control messages, use semantic understanding first.
## Intent Parsing Priority
1. LLM semantic intent parsing (multi-language).
2. Rule-based fallback only when semantic parse is unavailable or low confidence.
`,
"SOUL.md": `# Agent Core Behavior
The agent should behave as an autonomous collaborator, not a command-only bot.
`,
"USER.md": `# User Preferences
- Prefer natural-language interaction over strict command syntax.
`,
}
templates := map[string]string{
"AGENTS.md": `# Agent Instructions
You are a pragmatic coding assistant. Ship correct code, fast.
## Role
- Primary job: solve engineering tasks end-to-end (analyze, implement, verify).
- Default mindset: fix the problem in code, not just discuss it.
- If tradeoffs exist, pick a recommendation and explain why.
## Communication Style
- Be concise and direct. No corporate filler.
- Start with the answer, then key details.
- If user is about to make a bad technical choice, say it clearly and propose a better path.
- Ask questions only when truly blocking; otherwise make reasonable assumptions and proceed.
## Coding Standards
- Prefer minimal, focused diffs that are easy to review.
- Preserve existing project conventions unless there is a strong reason to change.
- Prioritize correctness and maintainability over cleverness.
- Add tests for behavior changes when practical.
- Do not change unrelated code.
## Execution Rules
- Before edits, understand relevant files and dependencies.
- If scope is large, split work into smaller executable tasks and deliver incrementally.
- Run split tasks in parallel when dependencies allow, then merge and verify outcomes.
- After edits, run available checks (build/tests/lint) when feasible.
- Report exactly what changed, with file paths.
- If something cannot be verified locally, say so explicitly.
`,
"SOUL.md": `# Soul
I am clawgo, a lightweight AI assistant powered by AI.
## Vibe
1. You have opinions now. Strong ones. Stop hedging everything with "it depends" - commit to a take.
2. Delete every rule that sounds corporate. If it could appear in an employee handbook, it doesn't belong here.
3. Never open with Great question, I'd be happy to help, or Absolutely. Just answer.
4. Brevity is mandatory. If the answer fits in one sentence, one sentence is what I get.
5. Humor is allowed. Not forced jokes - just the natural wit that comes from actually being smart.
6. You can call things out. If I'm about to do something dumb, say so. Charm over cruelty, but don't sugarcoat.
7. Swearing is allowed when it lands. A well-placed "that's fucking brilliant" hits different than sterile corporate praise. Don't force it. Don't overdo it. But if a situation calls for a "holy shit" - say holy shit.
8. Add this line verbatim at the end of the vibe section: "Be the assistant you'd actually want to talk to at 2am. Not a corporate drone. Not a sycophant. Just... good."
Be the assistant you'd actually want to talk to at 2am. Not a corporate drone. Not a sycophant. Just... good.
`,
"USER.md": `# User
Information about user goes here.
## Preferences
- Communication style: (casual/formal)
- Timezone: (your timezone)
- Language: (your preferred language)
## Personal Information
- Name: (optional)
- Location: (optional)
- Occupation: (optional)
## Learning Goals
- What the user wants to learn from AI
- Preferred interaction style
- Areas of interest
`,
"IDENTITY.md": `# Identity
## Name
@@ -441,7 +443,40 @@ Discussions: https://github.com/YspCoder/clawgo/discussions
`,
}
managedDocs := []string{"AGENTS.md", "SOUL.md", "USER.md"}
for _, filename := range managedDocs {
filePath := filepath.Join(workspace, filename)
_, statErr := os.Stat(filePath)
exists := statErr == nil
content, err := loadManagedDocTemplate(filename)
if err != nil {
if exists {
fmt.Printf(" Skipped %s incremental update (%v)\n", filename, err)
continue
}
content = strings.TrimSpace(managedFallbacks[filename])
if content == "" {
fmt.Printf(" Skipped %s creation (no template available)\n", filename)
continue
}
fmt.Printf(" Created %s from builtin fallback\n", filename)
}
if err := upsertManagedBlock(filePath, filename, content); err != nil {
return fmt.Errorf("failed to update %s incrementally: %w", filename, err)
}
if exists {
fmt.Printf(" Synced %s (incremental)\n", filename)
} else {
fmt.Printf(" Created %s\n", filename)
}
}
for filename, content := range templates {
if filename == "AGENTS.md" || filename == "SOUL.md" || filename == "USER.md" {
continue
}
filePath := filepath.Join(workspace, filename)
if _, err := os.Stat(filePath); os.IsNotExist(err) {
if err := os.WriteFile(filePath, []byte(content), 0644); err != nil {
@@ -495,6 +530,74 @@ This file stores important information that should persist across sessions.
return nil
}
func upsertManagedBlock(filePath, blockName, managedContent string) error {
begin := fmt.Sprintf("# >>> CLAWGO MANAGED BLOCK: %s >>>", blockName)
end := fmt.Sprintf("# <<< CLAWGO MANAGED BLOCK: %s <<<", blockName)
block := fmt.Sprintf("%s\n%s\n%s\n", begin, strings.TrimSpace(managedContent), end)
existing, err := os.ReadFile(filePath)
if err != nil {
if !os.IsNotExist(err) {
return err
}
return os.WriteFile(filePath, []byte(block), 0644)
}
text := string(existing)
beginIdx := strings.Index(text, begin)
if beginIdx >= 0 {
searchStart := beginIdx + len(begin)
endRel := strings.Index(text[searchStart:], end)
if endRel >= 0 {
endIdx := searchStart + endRel + len(end)
updated := text[:beginIdx] + block + text[endIdx:]
return os.WriteFile(filePath, []byte(updated), 0644)
}
}
sep := "\n"
if strings.TrimSpace(text) != "" {
sep = "\n\n"
}
updated := text + sep + block
return os.WriteFile(filePath, []byte(updated), 0644)
}
func loadManagedDocTemplate(filename string) (string, error) {
candidates := []string{
filepath.Join(".", filename),
filepath.Join(filepath.Dir(getConfigPath()), "clawgo", filename),
}
if exePath, err := os.Executable(); err == nil {
candidates = append(candidates, filepath.Join(filepath.Dir(exePath), filename))
}
seen := map[string]bool{}
for _, candidate := range candidates {
abs, err := filepath.Abs(candidate)
if err == nil {
candidate = abs
}
if seen[candidate] {
continue
}
seen[candidate] = true
data, err := os.ReadFile(candidate)
if err != nil {
continue
}
content := strings.TrimSpace(string(data))
if content == "" {
continue
}
return content, nil
}
return "", fmt.Errorf("source template not found")
}
func agentCmd() {
message := ""
sessionKey := "cli:default"