Add witness activity events to gt feed (gt-nfdyl)
Implement activity event emission for witness patrol operations: - patrol_started: When witness begins patrol cycle - polecat_checked: When witness checks a polecat - polecat_nudged: When witness nudges a stuck polecat - escalation_sent: When witness escalates to Mayor/Deacon - patrol_complete: When patrol cycle finishes Also adds refinery merge queue events for future use: - merge_started, merge_complete, merge_failed, queue_processed New command: `gt activity emit <event-type>` allows agents to emit events from CLI. Events write to ~/gt/.events.jsonl for gt feed. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
198
internal/cmd/activity.go
Normal file
198
internal/cmd/activity.go
Normal file
@@ -0,0 +1,198 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/steveyegge/gastown/internal/events"
|
||||
"github.com/steveyegge/gastown/internal/style"
|
||||
"github.com/steveyegge/gastown/internal/workspace"
|
||||
)
|
||||
|
||||
// Activity emit command flags
|
||||
var (
|
||||
activityEventType string
|
||||
activityActor string
|
||||
activityRig string
|
||||
activityPolecat string
|
||||
activityTarget string
|
||||
activityReason string
|
||||
activityMessage string
|
||||
activityStatus string
|
||||
activityIssue string
|
||||
activityTo string
|
||||
activityCount int
|
||||
)
|
||||
|
||||
var activityCmd = &cobra.Command{
|
||||
Use: "activity",
|
||||
GroupID: GroupDiag,
|
||||
Short: "Emit and view activity events",
|
||||
Long: `Emit and view activity events for the Gas Town activity feed.
|
||||
|
||||
Events are written to ~/gt/.events.jsonl and can be viewed with 'gt feed'.
|
||||
|
||||
Subcommands:
|
||||
emit Emit an activity event`,
|
||||
}
|
||||
|
||||
var activityEmitCmd = &cobra.Command{
|
||||
Use: "emit <event-type>",
|
||||
Short: "Emit an activity event",
|
||||
Long: `Emit an activity event to the Gas Town activity feed.
|
||||
|
||||
Supported event types for witness patrol:
|
||||
patrol_started - When witness begins patrol cycle
|
||||
polecat_checked - When witness checks a polecat
|
||||
polecat_nudged - When witness nudges a stuck polecat
|
||||
escalation_sent - When witness escalates to Mayor/Deacon
|
||||
patrol_complete - When patrol cycle finishes
|
||||
|
||||
Supported event types for refinery:
|
||||
merge_started - When refinery starts a merge
|
||||
merge_complete - When merge succeeds
|
||||
merge_failed - When merge fails
|
||||
queue_processed - When refinery finishes processing queue
|
||||
|
||||
Common options:
|
||||
--actor Who is emitting the event (e.g., gastown/witness)
|
||||
--rig Which rig the event is about
|
||||
--message Human-readable message
|
||||
|
||||
Examples:
|
||||
gt activity emit patrol_started --rig gastown --count 3
|
||||
gt activity emit polecat_checked --rig gastown --polecat Toast --status working --issue gt-xyz
|
||||
gt activity emit polecat_nudged --rig gastown --polecat Toast --reason "idle for 10 minutes"
|
||||
gt activity emit escalation_sent --rig gastown --target Toast --to mayor --reason "unresponsive"
|
||||
gt activity emit patrol_complete --rig gastown --count 3 --message "All polecats healthy"`,
|
||||
Args: cobra.ExactArgs(1),
|
||||
RunE: runActivityEmit,
|
||||
}
|
||||
|
||||
func init() {
|
||||
// Emit command flags
|
||||
activityEmitCmd.Flags().StringVar(&activityActor, "actor", "", "Actor emitting the event (auto-detected if not set)")
|
||||
activityEmitCmd.Flags().StringVar(&activityRig, "rig", "", "Rig the event is about")
|
||||
activityEmitCmd.Flags().StringVar(&activityPolecat, "polecat", "", "Polecat involved (for polecat_checked, polecat_nudged)")
|
||||
activityEmitCmd.Flags().StringVar(&activityTarget, "target", "", "Target of the action (for escalation)")
|
||||
activityEmitCmd.Flags().StringVar(&activityReason, "reason", "", "Reason for the action")
|
||||
activityEmitCmd.Flags().StringVar(&activityMessage, "message", "", "Human-readable message")
|
||||
activityEmitCmd.Flags().StringVar(&activityStatus, "status", "", "Status (for polecat_checked: working, idle, stuck)")
|
||||
activityEmitCmd.Flags().StringVar(&activityIssue, "issue", "", "Issue ID (for polecat_checked)")
|
||||
activityEmitCmd.Flags().StringVar(&activityTo, "to", "", "Escalation target (for escalation_sent: mayor, deacon)")
|
||||
activityEmitCmd.Flags().IntVar(&activityCount, "count", 0, "Polecat count (for patrol events)")
|
||||
|
||||
activityCmd.AddCommand(activityEmitCmd)
|
||||
rootCmd.AddCommand(activityCmd)
|
||||
}
|
||||
|
||||
func runActivityEmit(cmd *cobra.Command, args []string) error {
|
||||
eventType := args[0]
|
||||
|
||||
// Validate we're in a Gas Town workspace
|
||||
_, err := workspace.FindFromCwdOrError()
|
||||
if err != nil {
|
||||
return fmt.Errorf("not in a Gas Town workspace: %w", err)
|
||||
}
|
||||
|
||||
// Auto-detect actor if not provided
|
||||
actor := activityActor
|
||||
if actor == "" {
|
||||
actor = detectActor()
|
||||
}
|
||||
|
||||
// Build payload based on event type
|
||||
var payload map[string]interface{}
|
||||
|
||||
switch eventType {
|
||||
case events.TypePatrolStarted, events.TypePatrolComplete:
|
||||
if activityRig == "" {
|
||||
return fmt.Errorf("--rig is required for %s events", eventType)
|
||||
}
|
||||
payload = events.PatrolPayload(activityRig, activityCount, activityMessage)
|
||||
|
||||
case events.TypePolecatChecked:
|
||||
if activityRig == "" || activityPolecat == "" {
|
||||
return fmt.Errorf("--rig and --polecat are required for polecat_checked events")
|
||||
}
|
||||
if activityStatus == "" {
|
||||
activityStatus = "checked"
|
||||
}
|
||||
payload = events.PolecatCheckPayload(activityRig, activityPolecat, activityStatus, activityIssue)
|
||||
|
||||
case events.TypePolecatNudged:
|
||||
if activityRig == "" || activityPolecat == "" {
|
||||
return fmt.Errorf("--rig and --polecat are required for polecat_nudged events")
|
||||
}
|
||||
payload = events.NudgePayload(activityRig, activityPolecat, activityReason)
|
||||
|
||||
case events.TypeEscalationSent:
|
||||
if activityRig == "" || activityTarget == "" || activityTo == "" {
|
||||
return fmt.Errorf("--rig, --target, and --to are required for escalation_sent events")
|
||||
}
|
||||
payload = events.EscalationPayload(activityRig, activityTarget, activityTo, activityReason)
|
||||
|
||||
case events.TypeMergeStarted, events.TypeMerged, events.TypeMergeFailed, events.TypeMergeSkipped:
|
||||
// Refinery events - flexible payload
|
||||
payload = make(map[string]interface{})
|
||||
if activityRig != "" {
|
||||
payload["rig"] = activityRig
|
||||
}
|
||||
if activityMessage != "" {
|
||||
payload["message"] = activityMessage
|
||||
}
|
||||
if activityTarget != "" {
|
||||
payload["branch"] = activityTarget
|
||||
}
|
||||
if activityReason != "" {
|
||||
payload["reason"] = activityReason
|
||||
}
|
||||
|
||||
default:
|
||||
// Generic event - use whatever flags are provided
|
||||
payload = make(map[string]interface{})
|
||||
if activityRig != "" {
|
||||
payload["rig"] = activityRig
|
||||
}
|
||||
if activityPolecat != "" {
|
||||
payload["polecat"] = activityPolecat
|
||||
}
|
||||
if activityTarget != "" {
|
||||
payload["target"] = activityTarget
|
||||
}
|
||||
if activityReason != "" {
|
||||
payload["reason"] = activityReason
|
||||
}
|
||||
if activityMessage != "" {
|
||||
payload["message"] = activityMessage
|
||||
}
|
||||
if activityStatus != "" {
|
||||
payload["status"] = activityStatus
|
||||
}
|
||||
if activityIssue != "" {
|
||||
payload["issue"] = activityIssue
|
||||
}
|
||||
if activityTo != "" {
|
||||
payload["to"] = activityTo
|
||||
}
|
||||
if activityCount > 0 {
|
||||
payload["count"] = activityCount
|
||||
}
|
||||
}
|
||||
|
||||
// Emit the event
|
||||
if err := events.LogFeed(eventType, actor, payload); err != nil {
|
||||
return fmt.Errorf("emitting event: %w", err)
|
||||
}
|
||||
|
||||
// Print confirmation
|
||||
payloadJSON, _ := json.Marshal(payload)
|
||||
fmt.Printf("%s Emitted %s event\n", style.Success.Render("✓"), style.Bold.Render(eventType))
|
||||
fmt.Printf(" Actor: %s\n", actor)
|
||||
fmt.Printf(" Payload: %s\n", string(payloadJSON))
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Note: detectActor is defined in sling.go and reused here
|
||||
Reference in New Issue
Block a user