Skip to content

Commit 7b4f728

Browse files
authored
Merge pull request #140 from shelltime/feat/ccotel-otel-v2-passthrough
feat(daemon): add Claude Code OTEL v2 passthrough collector
2 parents 2337ac8 + a27600c commit 7b4f728

14 files changed

Lines changed: 1175 additions & 137 deletions

File tree

cmd/cli/main.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,7 @@ func main() {
105105
commands.DotfilesCommand,
106106
commands.DoctorCommand,
107107
commands.QueryCommand,
108+
commands.CCCommand,
108109
}
109110
err = app.Run(os.Args)
110111
if err != nil {

cmd/daemon/main.go

Lines changed: 21 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,10 @@ package main
22

33
import (
44
"context"
5+
"fmt"
56
"log/slog"
67
"os"
78
"os/signal"
8-
"path/filepath"
99
"syscall"
1010

1111
"github.com/ThreeDotsLabs/watermill"
@@ -25,36 +25,22 @@ var (
2525
ppToken = ""
2626
)
2727

28-
func getConfigPath() string {
29-
homeDir, err := os.UserHomeDir()
30-
if err != nil {
31-
slog.Error("Failed to get user home directory", slog.Any("err", err))
32-
return ""
33-
}
34-
return filepath.Join(homeDir, ".shelltime", "config.toml")
35-
}
36-
3728
func main() {
3829
l := slog.New(slog.NewTextHandler(os.Stdout, &slog.HandlerOptions{
3930
AddSource: true,
4031
Level: slog.LevelDebug,
4132
}))
4233
slog.SetDefault(l)
4334

44-
daemonConfigService := daemon.NewConfigService(daemon.DefaultConfigPath)
45-
daemonConfig, err := daemonConfigService.GetConfig()
35+
ctx := context.Background()
36+
configFile := os.ExpandEnv(fmt.Sprintf("%s/%s/%s", "$HOME", model.COMMAND_BASE_STORAGE_FOLDER, "config.toml"))
37+
daemonConfigService := model.NewConfigService(configFile)
38+
cfg, err := daemonConfigService.ReadConfigFile(ctx)
4639
if err != nil {
4740
slog.Error("Failed to get daemon config", slog.Any("err", err))
4841
return
4942
}
5043

51-
cs, err := daemonConfigService.GetUserConfig()
52-
if err != nil {
53-
slog.Error("Failed to get user config", slog.Any("err", err))
54-
return
55-
}
56-
57-
ctx := context.Background()
5844
uptraceOptions := []uptrace.Option{
5945
uptrace.WithDSN(uptraceDsn),
6046
uptrace.WithServiceName("cli-daemon"),
@@ -66,7 +52,6 @@ func main() {
6652
uptraceOptions = append(uptraceOptions, uptrace.WithResourceAttributes(attribute.String("hostname", hs)))
6753
}
6854

69-
cfg, err := cs.ReadConfigFile(ctx)
7055
if err != nil ||
7156
cfg.EnableMetrics == nil ||
7257
*cfg.EnableMetrics == false ||
@@ -82,7 +67,7 @@ func main() {
8267
defer uptrace.Shutdown(ctx)
8368
defer uptrace.ForceFlush(ctx)
8469

85-
daemon.Init(cs, version)
70+
daemon.Init(daemonConfigService, version)
8671
model.InjectVar(version)
8772
cmdService := model.NewCommandService()
8873

@@ -96,7 +81,7 @@ func main() {
9681

9782
go daemon.SocketTopicProccessor(msg)
9883

99-
// Start CCUsage service if enabled
84+
// Start CCUsage service if enabled (v1 - ccusage CLI based)
10085
if cfg.CCUsage != nil && cfg.CCUsage.Enabled != nil && *cfg.CCUsage.Enabled {
10186
ccUsageService := model.NewCCUsageService(cfg, cmdService)
10287
if err := ccUsageService.Start(ctx); err != nil {
@@ -107,8 +92,21 @@ func main() {
10792
}
10893
}
10994

95+
// Start CCOtel service if enabled (v2 - OTEL gRPC passthrough)
96+
var ccOtelServer *daemon.CCOtelServer
97+
if cfg.CCOtel != nil && cfg.CCOtel.Enabled != nil && *cfg.CCOtel.Enabled {
98+
ccOtelProcessor := daemon.NewCCOtelProcessor(cfg)
99+
ccOtelServer = daemon.NewCCOtelServer(cfg.CCOtel.GRPCPort, ccOtelProcessor)
100+
if err := ccOtelServer.Start(); err != nil {
101+
slog.Error("Failed to start CCOtel gRPC server", slog.Any("err", err))
102+
} else {
103+
slog.Info("CCOtel gRPC server started", slog.Int("port", cfg.CCOtel.GRPCPort))
104+
defer ccOtelServer.Stop()
105+
}
106+
}
107+
110108
// Create processor instance
111-
processor := daemon.NewSocketHandler(daemonConfig, pubsub)
109+
processor := daemon.NewSocketHandler(&cfg, pubsub)
112110

113111
// Start processor
114112
if err := processor.Start(); err != nil {

commands/cc.go

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
package commands
2+
3+
import (
4+
"github.com/gookit/color"
5+
"github.com/malamtime/cli/model"
6+
"github.com/urfave/cli/v2"
7+
)
8+
9+
var CCCommand = &cli.Command{
10+
Name: "cc",
11+
Usage: "Claude Code integration commands",
12+
Subcommands: []*cli.Command{
13+
CCInstallCommand,
14+
CCUninstallCommand,
15+
},
16+
}
17+
18+
var CCInstallCommand = &cli.Command{
19+
Name: "install",
20+
Aliases: []string{"i"},
21+
Usage: "Install Claude Code OTEL environment configuration to shell config files",
22+
Action: commandCCInstall,
23+
}
24+
25+
var CCUninstallCommand = &cli.Command{
26+
Name: "uninstall",
27+
Aliases: []string{"u"},
28+
Usage: "Remove Claude Code OTEL environment configuration from shell config files",
29+
Action: commandCCUninstall,
30+
}
31+
32+
func commandCCInstall(c *cli.Context) error {
33+
color.Yellow.Println("Installing Claude Code OTEL configuration...")
34+
35+
// Create shell services
36+
zshService := model.NewZshCCOtelEnvService()
37+
fishService := model.NewFishCCOtelEnvService()
38+
bashService := model.NewBashCCOtelEnvService()
39+
40+
// Install for all shells (non-blocking failures)
41+
if err := zshService.Install(); err != nil {
42+
color.Red.Printf("Failed to install for zsh: %v\n", err)
43+
}
44+
45+
if err := fishService.Install(); err != nil {
46+
color.Red.Printf("Failed to install for fish: %v\n", err)
47+
}
48+
49+
if err := bashService.Install(); err != nil {
50+
color.Red.Printf("Failed to install for bash: %v\n", err)
51+
}
52+
53+
color.Green.Println("Claude Code OTEL configuration has been installed!")
54+
color.Yellow.Println("Please restart your shell or source your config file to apply changes.")
55+
56+
return nil
57+
}
58+
59+
func commandCCUninstall(c *cli.Context) error {
60+
color.Yellow.Println("Removing Claude Code OTEL configuration...")
61+
62+
// Create shell services
63+
zshService := model.NewZshCCOtelEnvService()
64+
fishService := model.NewFishCCOtelEnvService()
65+
bashService := model.NewBashCCOtelEnvService()
66+
67+
// Uninstall from all shells
68+
if err := zshService.Uninstall(); err != nil {
69+
color.Red.Printf("Failed to uninstall from zsh: %v\n", err)
70+
}
71+
72+
if err := fishService.Uninstall(); err != nil {
73+
color.Red.Printf("Failed to uninstall from fish: %v\n", err)
74+
}
75+
76+
if err := bashService.Uninstall(); err != nil {
77+
color.Red.Printf("Failed to uninstall from bash: %v\n", err)
78+
}
79+
80+
color.Green.Println("Claude Code OTEL configuration has been removed!")
81+
82+
return nil
83+
}

commands/track.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -264,7 +264,7 @@ func DoSyncData(
264264
trackingData []model.TrackingData,
265265
meta model.TrackingMetaData,
266266
) error {
267-
socketPath := daemon.DefaultSocketPath
267+
socketPath := config.SocketPath
268268
isSocketReady := daemon.IsSocketReady(ctx, socketPath)
269269

270270
logrus.Traceln("is socket ready: ", isSocketReady)

0 commit comments

Comments
 (0)