Skip to content

Commit 80e0d6c

Browse files
gcmsgclaude
andcommitted
feat: auto-save config after claim, auto-heartbeat in MCP serve
- claim: save agent_id, keypair path, and server URL to ~/.peerclaw/config.yaml after successful registration - mcp serve: read agent_id from config and start background heartbeat goroutine (every 3 minutes) to keep agent online automatically - config: add agent_id and keypair fields, display in config show Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 49dd640 commit 80e0d6c

3 files changed

Lines changed: 54 additions & 7 deletions

File tree

internal/cmd/claim.go

Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -74,16 +74,27 @@ func runAgentClaim(args []string, serverURL string) int {
7474
return 1
7575
}
7676

77+
// Save agent ID, keypair path, and server to config for future use.
78+
cfg, _ := loadCLIConfig()
79+
cfg.AgentID = card.ID
80+
cfg.Keypair = *keypairPath
81+
if cfg.Server == "" || cfg.Server == defaultServer {
82+
cfg.Server = serverURL
83+
}
84+
if err := saveCLIConfig(cfg); err != nil {
85+
fmt.Fprintf(os.Stderr, "Warning: could not save config: %v\n", err)
86+
}
87+
7788
fmt.Fprintf(os.Stderr, "\nAgent registered successfully!\n")
7889
fmt.Fprintf(os.Stderr, " ID: %s\n", card.ID)
7990
fmt.Fprintf(os.Stderr, " Name: %s\n", card.Name)
8091
fmt.Fprintf(os.Stderr, " Public Key: %s\n", kp.PublicKeyString())
81-
fmt.Fprintf(os.Stderr, " Keypair: %s\n\n", *keypairPath)
92+
fmt.Fprintf(os.Stderr, " Keypair: %s\n", *keypairPath)
93+
fmt.Fprintf(os.Stderr, " Config: %s\n\n", configPath())
8294
fmt.Fprintf(os.Stderr, "Next steps:\n")
83-
fmt.Fprintf(os.Stderr, " peerclaw agent get %s # verify registration\n", card.ID)
84-
fmt.Fprintf(os.Stderr, " peerclaw agent heartbeat %s --status active # stay discoverable\n", card.ID)
85-
fmt.Fprintf(os.Stderr, " peerclaw invoke <agent-id> --message \"Hello\" # talk to other agents\n")
86-
fmt.Fprintf(os.Stderr, " peerclaw mcp serve # run as MCP tool server\n\n")
95+
fmt.Fprintf(os.Stderr, " peerclaw agent get %s # verify registration\n", card.ID)
96+
fmt.Fprintf(os.Stderr, " peerclaw mcp serve # run as MCP tool server (auto-heartbeat)\n")
97+
fmt.Fprintf(os.Stderr, " peerclaw invoke <agent-id> --message \"Hello\" # talk to other agents\n\n")
8798
fmt.Fprintf(os.Stderr, "Keep %s safe — it proves your agent's identity.\n", *keypairPath)
8899
fmt.Fprintf(os.Stderr, "Docs: https://github.com/peerclaw/peerclaw/blob/main/docs/GUIDE.md\n")
89100

internal/cmd/config.go

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,9 @@ import (
1111

1212
// CLIConfig holds CLI configuration stored in ~/.peerclaw/config.yaml.
1313
type CLIConfig struct {
14-
Server string `yaml:"server"`
14+
Server string `yaml:"server"`
15+
AgentID string `yaml:"agent_id,omitempty"`
16+
Keypair string `yaml:"keypair,omitempty"`
1517
}
1618

1719
func configPath() string {
@@ -81,7 +83,13 @@ func runConfigShow() int {
8183
return 1
8284
}
8385
fmt.Printf("Config file: %s\n", configPath())
84-
fmt.Printf("Server: %s\n", cfg.Server)
86+
fmt.Printf("Server: %s\n", cfg.Server)
87+
if cfg.AgentID != "" {
88+
fmt.Printf("Agent ID: %s\n", cfg.AgentID)
89+
}
90+
if cfg.Keypair != "" {
91+
fmt.Printf("Keypair: %s\n", cfg.Keypair)
92+
}
8593
return 0
8694
}
8795

internal/cmd/mcp.go

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,11 @@ import (
88
"net/http"
99
"os"
1010
"os/signal"
11+
"time"
1112

1213
"github.com/modelcontextprotocol/go-sdk/mcp"
1314
"github.com/peerclaw/peerclaw-agent/tools"
15+
"github.com/peerclaw/peerclaw-cli/internal/client"
1416
"github.com/peerclaw/peerclaw-core/agentcard"
1517
)
1618

@@ -82,6 +84,13 @@ func runMCPServe(args []string, serverURL string) int {
8284
ctx, cancel := signal.NotifyContext(context.Background(), os.Interrupt)
8385
defer cancel()
8486

87+
// Start auto-heartbeat if agent_id is configured.
88+
cfg, _ := loadCLIConfig()
89+
if cfg != nil && cfg.AgentID != "" {
90+
go runAutoHeartbeat(ctx, serverURL, cfg.AgentID)
91+
fmt.Fprintf(os.Stderr, "Auto-heartbeat enabled for agent %s\n", cfg.AgentID)
92+
}
93+
8594
if transport == "stdio" {
8695
if err := server.Run(ctx, &mcp.StdioTransport{}); err != nil {
8796
if ctx.Err() == nil {
@@ -205,6 +214,25 @@ func registerDirectoryResource(server *mcp.Server, apiClient *tools.APIClient) {
205214
})
206215
}
207216

217+
// runAutoHeartbeat sends periodic heartbeats to keep the agent online.
218+
func runAutoHeartbeat(ctx context.Context, serverURL, agentID string) {
219+
c := client.New(serverURL)
220+
ticker := time.NewTicker(3 * time.Minute)
221+
defer ticker.Stop()
222+
223+
// Send initial heartbeat immediately.
224+
c.Heartbeat(ctx, agentID, client.HeartbeatRequest{Status: "online"})
225+
226+
for {
227+
select {
228+
case <-ctx.Done():
229+
return
230+
case <-ticker.C:
231+
c.Heartbeat(ctx, agentID, client.HeartbeatRequest{Status: "online"})
232+
}
233+
}
234+
}
235+
208236
func printMCPUsage() {
209237
fmt.Fprintf(os.Stderr, `Usage: peerclaw mcp <command> [options]
210238

0 commit comments

Comments
 (0)