on main help sign in
feat(config): add trailer_style option
7b47c6cAmolith
README.md+5−2
@@ -345,14 +345,17 @@ it creates. You can customize this behavior with the `attribution` option:
345 "$schema": "https://charm.land/crush.json",
345 "$schema": "https://charm.land/crush.json",
346 "options": {
346 "options": {
347 "attribution": {
347 "attribution": {
348− "co_authored_by": true,
348+ "trailer_style": "co-authored-by",
349 "generated_with": true
349 "generated_with": true
350 }
350 }
351 }
351 }
352}
352}
353```
353```
354
354
355+- `trailer_style`: Controls the attribution trailer added to commit messages (default: `co-authored-by`)
355−- `co_authored_by`: When true (default), adds `Co-Authored-By: Crush <crush@charm.land>` to commit messages
356+ - `co-authored-by`: Adds `Co-Authored-By: Crush <crush@charm.land>`
357+ - `assisted-by`: Adds `Assisted-by: [Model Name] via Crush` (includes the model name)
358+ - `none`: No attribution trailer
356- `generated_with`: When true (default), adds `💘 Generated with Crush` line to commit messages and PR descriptions
359- `generated_with`: When true (default), adds `💘 Generated with Crush` line to commit messages and PR descriptions
357
360
358### Custom Providers
361### Custom Providers
internal/agent/common_test.go+1−1
@@ -176,7 +176,7 @@ func coderAgent(r *recorder.Recorder, env fakeEnv, large, small fantasy.Language
176 return nil, err
176 return nil, err
177 }
177 }
178 allTools := []fantasy.AgentTool{
178 allTools := []fantasy.AgentTool{
179− tools.NewBashTool(env.permissions, env.workingDir, cfg.Options.Attribution),
179+ tools.NewBashTool(env.permissions, env.workingDir, cfg.Options.Attribution, large.Model()),
180 tools.NewDownloadTool(env.permissions, env.workingDir, r.GetDefaultClient()),
180 tools.NewDownloadTool(env.permissions, env.workingDir, r.GetDefaultClient()),
181 tools.NewEditTool(env.lspClients, env.permissions, env.history, env.workingDir),
181 tools.NewEditTool(env.lspClients, env.permissions, env.history, env.workingDir),
182 tools.NewMultiEditTool(env.lspClients, env.permissions, env.history, env.workingDir),
182 tools.NewMultiEditTool(env.lspClients, env.permissions, env.history, env.workingDir),
internal/agent/coordinator.go+7−1
@@ -327,8 +327,14 @@ func (c *coordinator) buildTools(ctx context.Context, agent config.Agent) ([]fan
327 allTools = append(allTools, agenticFetchTool)
327 allTools = append(allTools, agenticFetchTool)
328 }
328 }
329
329
330+ // Get the model name for the agent
331+ modelName := ""
332+ if modelCfg, ok := c.cfg.Models[agent.Model]; ok {
333+ modelName = modelCfg.Model
334+ }
335+
330 allTools = append(allTools,
336 allTools = append(allTools,
331− tools.NewBashTool(c.permissions, c.cfg.WorkingDir(), c.cfg.Options.Attribution),
337+ tools.NewBashTool(c.permissions, c.cfg.WorkingDir(), c.cfg.Options.Attribution, modelName),
332 tools.NewJobOutputTool(),
338 tools.NewJobOutputTool(),
333 tools.NewJobKillTool(),
339 tools.NewJobKillTool(),
334 tools.NewDownloadTool(c.permissions, c.cfg.WorkingDir(), nil),
340 tools.NewDownloadTool(c.permissions, c.cfg.WorkingDir(), nil),
internal/agent/tools/bash.go+5−3
@@ -63,6 +63,7 @@ type bashDescriptionData struct {
63 BannedCommands string
63 BannedCommands string
64 MaxOutputLength int
64 MaxOutputLength int
65 Attribution config.Attribution
65 Attribution config.Attribution
66+ ModelName string
66}
67}
67
68
68var bannedCommands = []string{
69var bannedCommands = []string{
@@ -138,13 +139,14 @@ var bannedCommands = []string{
138 "ufw",
139 "ufw",
139}
140}
140
141
141−func bashDescription(attribution *config.Attribution) string {
142+func bashDescription(attribution *config.Attribution, modelName string) string {
142 bannedCommandsStr := strings.Join(bannedCommands, ", ")
143 bannedCommandsStr := strings.Join(bannedCommands, ", ")
143 var out bytes.Buffer
144 var out bytes.Buffer
144 if err := bashDescriptionTpl.Execute(&out, bashDescriptionData{
145 if err := bashDescriptionTpl.Execute(&out, bashDescriptionData{
145 BannedCommands: bannedCommandsStr,
146 BannedCommands: bannedCommandsStr,
146 MaxOutputLength: MaxOutputLength,
147 MaxOutputLength: MaxOutputLength,
147 Attribution: *attribution,
148 Attribution: *attribution,
149+ ModelName: modelName,
148 }); err != nil {
150 }); err != nil {
149 // this should never happen.
151 // this should never happen.
150 panic("failed to execute bash description template: " + err.Error())
152 panic("failed to execute bash description template: " + err.Error())
@@ -184,10 +186,10 @@ func blockFuncs() []shell.BlockFunc {
184 }
186 }
185}
187}
186
188
187−func NewBashTool(permissions permission.Service, workingDir string, attribution *config.Attribution) fantasy.AgentTool {
189+func NewBashTool(permissions permission.Service, workingDir string, attribution *config.Attribution, modelName string) fantasy.AgentTool {
188 return fantasy.NewAgentTool(
190 return fantasy.NewAgentTool(
189 BashToolName,
191 BashToolName,
190− string(bashDescription(attribution)),
192+ string(bashDescription(attribution, modelName)),
191 func(ctx context.Context, params BashParams, call fantasy.ToolCall) (fantasy.ToolResponse, error) {
193 func(ctx context.Context, params BashParams, call fantasy.ToolCall) (fantasy.ToolResponse, error) {
192 if params.Command == "" {
194 if params.Command == "" {
193 return fantasy.NewTextErrorResponse("missing command"), nil
195 return fantasy.NewTextErrorResponse("missing command"), nil
internal/agent/tools/bash.tpl+3−1
@@ -66,7 +66,9 @@ When user asks to create git commit:
66{{ if .Attribution.GeneratedWith}}
66{{ if .Attribution.GeneratedWith}}
67 💘 Generated with Crush
67 💘 Generated with Crush
68{{ end }}
68{{ end }}
69−{{ if .Attribution.CoAuthoredBy}}
69+{{ if eq .Attribution.TrailerStyle "assisted-by"}}
70+ Assisted-by: {{ .ModelName }} via Crush
71+{{ else if eq .Attribution.TrailerStyle "co-authored-by"}}
70 Co-Authored-By: Crush <crush@charm.land>
72 Co-Authored-By: Crush <crush@charm.land>
71{{ end }}
73{{ end }}
72 EOF
74 EOF
internal/config/config.go+10−2
@@ -166,9 +166,17 @@ type Permissions struct {
166 SkipRequests bool `json:"-"` // Automatically accept all permissions (YOLO mode)
166 SkipRequests bool `json:"-"` // Automatically accept all permissions (YOLO mode)
167}
167}
168
168
169+type TrailerStyle string
170+
171+const (
172+ TrailerStyleNone TrailerStyle = "none"
173+ TrailerStyleCoAuthoredBy TrailerStyle = "co-authored-by"
174+ TrailerStyleAssistedBy TrailerStyle = "assisted-by"
175+)
176+
169type Attribution struct {
177type Attribution struct {
170− CoAuthoredBy bool `json:"co_authored_by,omitempty" jsonschema:"description=Add Co-Authored-By trailer to commit messages,default=true"`
178+ TrailerStyle TrailerStyle `json:"trailer_style,omitempty" jsonschema:"description=Style of attribution trailer to add to commits,enum=none,enum=co-authored-by,enum=assisted-by,default=co-authored-by"`
171− GeneratedWith bool `json:"generated_with,omitempty" jsonschema:"description=Add Generated with Crush line to commit messages and issues and PRs,default=true"`
179+ GeneratedWith bool `json:"generated_with,omitempty" jsonschema:"description=Add Generated with Crush line to commit messages and issues and PRs,default=true"`
172}
180}
173
181
174type Options struct {
182type Options struct {
internal/config/load.go+1−1
@@ -353,7 +353,7 @@ func (c *Config) setDefaults(workingDir, dataDir string) {
353
353
354 if c.Options.Attribution == nil {
354 if c.Options.Attribution == nil {
355 c.Options.Attribution = &Attribution{
355 c.Options.Attribution = &Attribution{
356− CoAuthoredBy: true,
356+ TrailerStyle: TrailerStyleCoAuthoredBy,
357 GeneratedWith: true,
357 GeneratedWith: true,
358 }
358 }
359 }
359 }
schema.json+9−4
@@ -5,10 +5,15 @@
5 "$defs": {
5 "$defs": {
6 "Attribution": {
6 "Attribution": {
7 "properties": {
7 "properties": {
8− "co_authored_by": {
8+ "trailer_style": {
9− "type": "boolean",
9+ "type": "string",
10+ "enum": [
11+ "none",
12+ "co-authored-by",
13+ "assisted-by"
14+ ],
10− "description": "Add Co-Authored-By trailer to commit messages",
15+ "description": "Style of attribution trailer to add to commits",
11− "default": true
16+ "default": "co-authored-by"
12 },
17 },
13 "generated_with": {
18 "generated_with": {
14 "type": "boolean",
19 "type": "boolean",