← back to kidandcat__ccc

Function bodies 92 total

All specs Real LLM only Function bodies
getSystemStats function · go · L28-L109 (82 LOC)
commands.go
func getSystemStats() string {
	var sb strings.Builder
	hostname, _ := os.Hostname()
	sb.WriteString(fmt.Sprintf("🖥 %s\n\n", hostname))

	// Uptime
	if out, err := exec.Command("uptime").Output(); err == nil {
		sb.WriteString(fmt.Sprintf("⏱ %s\n", strings.TrimSpace(string(out))))
	}

	// CPU info
	if out, err := exec.Command("uname", "-m").Output(); err == nil {
		arch := strings.TrimSpace(string(out))
		// Count cores: nproc on Linux, sysctl on macOS
		var cores string
		if c, err := exec.Command("nproc").Output(); err == nil {
			cores = strings.TrimSpace(string(c))
		} else if c, err := exec.Command("sysctl", "-n", "hw.ncpu").Output(); err == nil {
			cores = strings.TrimSpace(string(c))
		}
		sb.WriteString(fmt.Sprintf("🧠 CPU: %s cores (%s)\n", cores, arch))
	}

	// Memory: Linux uses free, macOS uses vm_stat + sysctl
	if out, err := exec.Command("free", "-h").Output(); err == nil {
		// Linux
		lines := strings.Split(string(out), "\n")
		for _, l := range lines {
			if strings.Ha
executeCommand function · go · L112-L146 (35 LOC)
commands.go
func executeCommand(cmdStr string) (string, error) {
	ctx, cancel := context.WithTimeout(context.Background(), 2*time.Minute)
	defer cancel()

	shell := "bash"
	if _, err := exec.LookPath("zsh"); err == nil {
		shell = "zsh"
	}
	cmd := exec.CommandContext(ctx, shell, "-l", "-c", cmdStr)
	cmd.Dir, _ = os.UserHomeDir()

	var stdout, stderr bytes.Buffer
	cmd.Stdout = &stdout
	cmd.Stderr = &stderr

	err := cmd.Run()

	output := stdout.String()
	if stderr.Len() > 0 {
		if output != "" {
			output += "\n"
		}
		output += stderr.String()
	}

	if output == "" {
		if err != nil {
			output = fmt.Sprintf("Error: %v", err)
		} else {
			output = "(no output)"
		}
	}

	return strings.TrimSpace(output), err
}
runClaude function · go · L149-L198 (50 LOC)
commands.go
func runClaude(prompt string) (string, error) {
	ctx, cancel := context.WithTimeout(context.Background(), 10*time.Minute)
	defer cancel()

	home, _ := os.UserHomeDir()
	workDir := home

	words := strings.Fields(prompt)
	if len(words) > 0 {
		firstWord := words[0]
		potentialDir := filepath.Join(home, firstWord)
		if info, err := os.Stat(potentialDir); err == nil && info.IsDir() {
			workDir = potentialDir
			prompt = strings.TrimSpace(strings.TrimPrefix(prompt, firstWord))
			if prompt == "" {
				return "Error: no prompt provided after directory name", nil
			}
		}
	}

	if claudePath == "" {
		return "Error: claude binary not found", fmt.Errorf("claude not found")
	}
	cmd := exec.CommandContext(ctx, claudePath, "--dangerously-skip-permissions", "-p", prompt)
	cmd.Dir = workDir

	var stdout, stderr bytes.Buffer
	cmd.Stdout = &stdout
	cmd.Stderr = &stderr

	err := cmd.Run()

	output := stdout.String()
	if stderr.Len() > 0 {
		if output != "" {
			output += "\n"
		}
		output += stderr.St
stopListenerService function · go · L200-L219 (20 LOC)
commands.go
func stopListenerService() {
	home, _ := os.UserHomeDir()
	if _, err := os.Stat("/Library"); err == nil {
		// macOS - launchd
		plistPath := filepath.Join(home, "Library", "LaunchAgents", "com.ccc.plist")
		exec.Command("launchctl", "unload", plistPath).Run()
	} else {
		// Linux - systemd
		exec.Command("systemctl", "--user", "stop", "ccc").Run()
	}
	// Also kill any manual listener via lock file PID
	lockPath := filepath.Join(home, ".ccc.lock")
	if data, err := os.ReadFile(lockPath); err == nil {
		pidStr := strings.TrimSpace(string(data))
		if pidStr != "" {
			exec.Command("kill", pidStr).Run()
		}
	}
	time.Sleep(500 * time.Millisecond)
}
startListenerService function · go · L221-L229 (9 LOC)
commands.go
func startListenerService() {
	home, _ := os.UserHomeDir()
	if _, err := os.Stat("/Library"); err == nil {
		plistPath := filepath.Join(home, "Library", "LaunchAgents", "com.ccc.plist")
		exec.Command("launchctl", "load", plistPath).Run()
	} else {
		exec.Command("systemctl", "--user", "start", "ccc").Run()
	}
}
setup function · go · L231-L420 (190 LOC)
commands.go
func setup(botToken string) error {
	fmt.Println("🚀 Claude Code Companion Setup")
	fmt.Println("==============================")
	fmt.Println()

	// Load existing config if present (preserve sessions, group, etc.)
	config, _ := loadConfig()
	if config == nil {
		config = &Config{Sessions: make(map[string]*SessionInfo)}
	}
	config.BotToken = botToken

	// Stop listener to avoid getUpdates conflict (409 Conflict)
	fmt.Println("Stopping listener...")
	stopListenerService()

	// Step 1: Permission mode
	fmt.Println("Step 1/6: Permission mode")
	var permMode string
	err := huh.NewSelect[string]().
		Title("How should remote sessions handle permissions?").
		Description("This controls what happens when Claude Code needs\npermission to run tools in Telegram-controlled sessions.").
		Options(
			huh.NewOption[string](
				"Auto-approve\n"+
					"  All permissions granted automatically. Claude works without\n"+
					"  interruptions. Best for trusted environments where you control\n"+
					"  p
doctor function · go · L460-L641 (182 LOC)
commands.go
func doctor() {
	fmt.Println("🩺 ccc doctor")
	fmt.Println("=============")
	fmt.Println()

	allGood := true

	// Check tmux
	fmt.Print("tmux.............. ")
	if tmuxPath != "" {
		fmt.Printf("✅ %s\n", tmuxPath)
	} else {
		fmt.Println("❌ not found")
		fmt.Println("   Install: brew install tmux (macOS) or apt install tmux (Linux)")
		allGood = false
	}

	// Check claude
	fmt.Print("claude............ ")
	if claudePath != "" {
		fmt.Printf("✅ %s\n", claudePath)
	} else {
		fmt.Println("❌ not found")
		fmt.Println("   Install: npm install -g @anthropic-ai/claude-code")
		allGood = false
	}

	// Check ccc is in PATH (for hooks)
	fmt.Print("ccc in PATH....... ")
	home, _ := os.UserHomeDir()
	cccPaths := []string{
		filepath.Join(home, "bin", "ccc"),
		filepath.Join(home, "go", "bin", "ccc"),
	}
	foundCccPath := ""
	for _, p := range cccPaths {
		if _, err := os.Stat(p); err == nil {
			foundCccPath = p
			break
		}
	}
	if foundCccPath != "" {
		fmt.Printf("✅ %s\n", foundCccPath)
	} else {
All rows above produced by Repobility · https://repobility.com
send function · go · L644-L671 (28 LOC)
commands.go
func send(message string) error {
	config, err := loadConfig()
	if err != nil {
		return fmt.Errorf("not configured. Run: ccc setup <bot_token>")
	}

	if !config.Away {
		fmt.Println("Away mode off, skipping notification.")
		return nil
	}

	// Try to send to session topic if we're in a session directory
	if config.GroupID != 0 {
		cwd, _ := os.Getwd()
		for name, info := range config.Sessions {
			if info == nil {
				continue
			}
			// Match against saved path, subdirectories of saved path, or suffix
			if cwd == info.Path || strings.HasPrefix(cwd, info.Path+"/") || strings.HasSuffix(cwd, "/"+name) {
				return sendMessage(config, config.GroupID, info.TopicID, message)
			}
		}
	}

	// Fallback to private chat
	return sendMessage(config, config.ChatID, 0, message)
}
printHelp function · go · L1237-L1279 (43 LOC)
commands.go
func printHelp() {
	fmt.Printf(`ccc - Claude Code Companion v%s

Your companion for Claude Code - control sessions remotely via Telegram and tmux.

USAGE:
    ccc                     Start/attach tmux session in current directory
    ccc -c                  Continue previous session
    ccc <message>           Send notification (if away mode is on)

COMMANDS:
    setup <token>           Complete setup (bot, hook, service - all in one!)
    doctor                  Check all dependencies and configuration
    config                  Show/set configuration values
    config projects-dir <path>  Set base directory for projects
    config oauth-token <token>  Set OAuth token
    setgroup                Configure Telegram group for topics (if skipped during setup)
    listen                  Start the Telegram bot listener manually
    install                 Install Claude hook manually
    send <file>             Send file to current session's Telegram topic
    relay [port]            Sta
handleAuthCode function · go · L1349-L1394 (46 LOC)
commands.go
func handleAuthCode(config *Config, chatID, threadID int64, code string) {
	authWaitingCode = false
	code = strings.TrimSpace(code)

	sendMessage(config, chatID, threadID, "🔄 Sending code to Claude...")

	exec.Command(tmuxPath, "send-keys", "-t", authTmuxSession, "-l", code).Run()
	time.Sleep(200 * time.Millisecond)
	exec.Command(tmuxPath, "send-keys", "-t", authTmuxSession, "C-m").Run()

	for i := 0; i < 10; i++ {
		time.Sleep(2 * time.Second)
		out, _ := exec.Command(tmuxPath, "capture-pane", "-t", authTmuxSession, "-p").Output()
		pane := string(out)

		if strings.Contains(pane, "Yes, I accept") {
			exec.Command(tmuxPath, "send-keys", "-t", authTmuxSession, "Down").Run()
			time.Sleep(200 * time.Millisecond)
			exec.Command(tmuxPath, "send-keys", "-t", authTmuxSession, "C-m").Run()
			continue
		}

		if strings.Contains(pane, "Press Enter") || strings.Contains(pane, "Enter to confirm") {
			exec.Command(tmuxPath, "send-keys", "-t", authTmuxSession, "C-m").Run()
			continue
		}

		i
getConfigPath function · go · L10-L13 (4 LOC)
config.go
func getConfigPath() string {
	home, _ := os.UserHomeDir()
	return filepath.Join(home, ".ccc.json")
}
loadConfig function · go · L15-L103 (89 LOC)
config.go
func loadConfig() (*Config, error) {
	data, err := os.ReadFile(getConfigPath())
	if err != nil {
		return nil, err
	}

	// First check if this is old format (sessions as map[string]int64)
	var rawConfig map[string]json.RawMessage
	if err := json.Unmarshal(data, &rawConfig); err != nil {
		return nil, err
	}

	// Try to detect old sessions format
	var needsMigration bool
	var oldSessions map[string]int64
	if sessionsRaw, ok := rawConfig["sessions"]; ok {
		// Try to parse as old format (map of topic IDs)
		if json.Unmarshal(sessionsRaw, &oldSessions) == nil && len(oldSessions) > 0 {
			// Check if values are positive numbers (old format)
			for _, v := range oldSessions {
				if v > 0 {
					needsMigration = true
					break
				}
			}
		}
	}

	var config Config
	if needsMigration {
		// Parse everything except sessions first
		type ConfigWithoutSessions struct {
			BotToken    string `json:"bot_token"`
			ChatID      int64  `json:"chat_id"`
			GroupID     int64  `json:"group_id"`
			Pro
saveConfig function · go · L105-L111 (7 LOC)
config.go
func saveConfig(config *Config) error {
	data, err := json.MarshalIndent(config, "", "  ")
	if err != nil {
		return err
	}
	return os.WriteFile(getConfigPath(), data, 0600)
}
getProjectsDir function · go · L114-L125 (12 LOC)
config.go
func getProjectsDir(config *Config) string {
	if config.ProjectsDir != "" {
		// Expand ~ to home directory
		if strings.HasPrefix(config.ProjectsDir, "~/") {
			home, _ := os.UserHomeDir()
			return filepath.Join(home, config.ProjectsDir[2:])
		}
		return config.ProjectsDir
	}
	home, _ := os.UserHomeDir()
	return home
}
resolveProjectPath function · go · L130-L145 (16 LOC)
config.go
func resolveProjectPath(config *Config, name string) string {
	// Absolute path
	if strings.HasPrefix(name, "/") {
		return name
	}
	// Home-relative path (~/something or just ~)
	if strings.HasPrefix(name, "~/") || name == "~" {
		home, _ := os.UserHomeDir()
		if name == "~" {
			return home
		}
		return filepath.Join(home, name[2:])
	}
	// Relative to projects_dir
	return filepath.Join(getProjectsDir(config), name)
}
Repobility — same analyzer, your code, free for public repos · /scan/
expandPath function · go · L148-L154 (7 LOC)
config.go
func expandPath(path string) string {
	if strings.HasPrefix(path, "~/") {
		home, _ := os.UserHomeDir()
		return filepath.Join(home, path[2:])
	}
	return path
}
telegramActiveFlag function · go · L16-L18 (3 LOC)
hooks.go
func telegramActiveFlag(tmuxName string) string {
	return "/tmp/ccc-telegram-active-" + tmuxName
}
readHookStdin function · go · L21-L35 (15 LOC)
hooks.go
func readHookStdin() ([]byte, error) {
	stdinData := make(chan []byte, 1)
	go func() {
		defer func() { recover() }()
		data, _ := io.ReadAll(os.Stdin)
		stdinData <- data
	}()

	select {
	case rawData := <-stdinData:
		return rawData, nil
	case <-time.After(2 * time.Second):
		return nil, nil
	}
}
findSession function · go · L38-L48 (11 LOC)
hooks.go
func findSession(config *Config, cwd string) (string, int64) {
	for name, info := range config.Sessions {
		if name == "" || info == nil {
			continue
		}
		if cwd == info.Path || strings.HasPrefix(cwd, info.Path+"/") || strings.HasSuffix(cwd, "/"+name) {
			return name, info.TopicID
		}
	}
	return "", 0
}
handleStopHook function · go · L50-L95 (46 LOC)
hooks.go
func handleStopHook() error {
	defer func() { recover() }()

	rawData, _ := readHookStdin()
	if len(rawData) == 0 {
		return nil
	}

	hookData, err := parseHookData(rawData)
	if err != nil {
		return nil
	}

	config, err := loadConfig()
	if err != nil || config == nil {
		return nil
	}

	sessName, topicID := findSession(config, hookData.Cwd)
	if sessName == "" || config.GroupID == 0 || topicID == 0 {
		return nil
	}

	hookLog("stop-hook: session=%s transcript=%s", sessName, hookData.TranscriptPath)

	// Clear Telegram active flag when Claude stops
	tmuxName := "claude-" + strings.ReplaceAll(sessName, ".", "_")
	os.Remove(telegramActiveFlag(tmuxName))

	blocks := extractLastTurn(hookData.TranscriptPath)
	if len(blocks) == 0 {
		// No text blocks found, just send completion marker
		sendMessage(config, config.GroupID, topicID, fmt.Sprintf("✅ %s", sessName))
		return nil
	}

	for i, block := range blocks {
		text := block
		if i == len(blocks)-1 {
			text = fmt.Sprintf("✅ %s\n\n%s", sessN
extractLastTurn function · go · L99-L241 (143 LOC)
hooks.go
func extractLastTurn(transcriptPath string) []string {
	if transcriptPath == "" {
		return nil
	}

	f, err := os.Open(transcriptPath)
	if err != nil {
		return nil
	}
	defer f.Close()

	type contentBlock struct {
		Type    string `json:"type"`
		Text    string `json:"text"`
		Name    string `json:"name,omitempty"`
		Content string `json:"content,omitempty"`
	}

	// transcriptLine handles both nested (message.content) and flat (root-level
	// content) JSONL formats. Claude Code v2.1.45+ may emit either.
	type transcriptLine struct {
		Type      string          `json:"type"`
		RequestID string          `json:"requestId,omitempty"`
		Role      string          `json:"role,omitempty"`
		Content   json.RawMessage `json:"content,omitempty"`
		Message   struct {
			Role    string          `json:"role"`
			Content json.RawMessage `json:"content"`
		} `json:"message"`
	}

	// Parse all lines
	type parsedEntry struct {
		ttype     string
		requestID string
		role      string
		content   json.RawM
isToolResult function · go · L244-L260 (17 LOC)
hooks.go
func isToolResult(content json.RawMessage) bool {
	if len(content) == 0 {
		return false
	}
	// Try as array of objects
	var blocks []struct {
		Type string `json:"type"`
	}
	if json.Unmarshal(content, &blocks) == nil {
		for _, b := range blocks {
			if b.Type == "tool_result" {
				return true
			}
		}
	}
	return false
}
handlePermissionHook function · go · L262-L408 (147 LOC)
hooks.go
func handlePermissionHook() error {
	defer func() { recover() }()

	rawData, _ := readHookStdin()
	if len(rawData) == 0 {
		return nil
	}

	hookData, err := parseHookData(rawData)
	if err != nil {
		return nil
	}

	config, err := loadConfig()
	if err != nil || config == nil {
		return nil
	}

	sessName, topicID := findSession(config, hookData.Cwd)
	if sessName == "" || config.GroupID == 0 {
		return nil
	}

	// Handle AskUserQuestion - forward to Telegram with buttons
	if hookData.ToolName == "AskUserQuestion" && len(hookData.ToolInput.Questions) > 0 {
		for qIdx, q := range hookData.ToolInput.Questions {
			if q.Question == "" {
				continue
			}
			msg := fmt.Sprintf("❓ %s\n\n%s", q.Header, q.Question)

			var buttons [][]InlineKeyboardButton
			for i, opt := range q.Options {
				if opt.Label == "" {
					continue
				}
				totalQuestions := len(hookData.ToolInput.Questions)
				callbackData := fmt.Sprintf("%s:%d:%d:%d", sessName, qIdx, totalQuestions, i)
				if len(callbackData) > 
Citation: Repobility (2026). State of AI-Generated Code. https://repobility.com/research/
outputPermissionDecision function · go · L411-L421 (11 LOC)
hooks.go
func outputPermissionDecision(decision, reason string) {
	response := map[string]interface{}{
		"hookSpecificOutput": map[string]interface{}{
			"hookEventName":            "PreToolUse",
			"permissionDecision":       decision,
			"permissionDecisionReason": reason,
		},
	}
	data, _ := json.Marshal(response)
	fmt.Println(string(data))
}
handleNotificationHook function · go · L423-L425 (3 LOC)
hooks.go
func handleNotificationHook() error {
	return nil
}
isCccHook function · go · L428-L446 (19 LOC)
hooks.go
func isCccHook(entry interface{}) bool {
	if m, ok := entry.(map[string]interface{}); ok {
		if cmd, ok := m["command"].(string); ok {
			return strings.Contains(cmd, "ccc hook")
		}
		if hooks, ok := m["hooks"].([]interface{}); ok {
			for _, h := range hooks {
				if hm, ok := h.(map[string]interface{}); ok {
					if cmd, ok := hm["command"].(string); ok {
						if strings.Contains(cmd, "ccc hook") {
							return true
						}
					}
				}
			}
		}
	}
	return false
}
installHook function · go · L458-L537 (80 LOC)
hooks.go
func installHook() error {
	home, _ := os.UserHomeDir()
	settingsPath := filepath.Join(home, ".claude", "settings.json")

	data, err := os.ReadFile(settingsPath)
	if err != nil {
		return fmt.Errorf("failed to read settings.json: %w", err)
	}

	var settings map[string]interface{}
	if err := json.Unmarshal(data, &settings); err != nil {
		return fmt.Errorf("failed to parse settings.json: %w", err)
	}

	hooks, ok := settings["hooks"].(map[string]interface{})
	if !ok {
		hooks = make(map[string]interface{})
	}

	cccHooks := map[string][]interface{}{
		"PreToolUse": {
			map[string]interface{}{
				"hooks": []interface{}{
					map[string]interface{}{
						"command": cccPath + " hook-permission",
						"type":    "command",
						"timeout": 300000,
					},
				},
				"matcher": "",
			},
		},
		"Stop": {
			map[string]interface{}{
				"hooks": []interface{}{
					map[string]interface{}{
						"command": cccPath + " hook-stop",
						"type":    "command",
					},
				},
			},
		},
	}

	// R
uninstallHook function · go · L539-L584 (46 LOC)
hooks.go
func uninstallHook() error {
	home, _ := os.UserHomeDir()
	settingsPath := filepath.Join(home, ".claude", "settings.json")

	data, err := os.ReadFile(settingsPath)
	if err != nil {
		return fmt.Errorf("failed to read settings.json: %w", err)
	}

	var settings map[string]interface{}
	if err := json.Unmarshal(data, &settings); err != nil {
		return fmt.Errorf("failed to parse settings.json: %w", err)
	}

	hooks, ok := settings["hooks"].(map[string]interface{})
	if !ok {
		fmt.Println("No hooks found")
		return nil
	}

	hookTypes := []string{"Stop", "Notification", "PermissionRequest", "PostToolUse", "PreToolUse", "UserPromptSubmit"}
	for _, hookType := range hookTypes {
		if existing, ok := hooks[hookType].([]interface{}); ok {
			filtered := removeCccHooks(existing)
			if len(filtered) == 0 {
				delete(hooks, hookType)
			} else {
				hooks[hookType] = filtered
			}
		}
	}

	settings["hooks"] = hooks

	newData, err := json.MarshalIndent(settings, "", "  ")
	if err != nil {
		return fmt
installSkill function · go · L586-L641 (56 LOC)
hooks.go
func installSkill() error {
	home, _ := os.UserHomeDir()
	skillDir := filepath.Join(home, ".claude", "skills")
	skillPath := filepath.Join(skillDir, "ccc-send.md")

	if err := os.MkdirAll(skillDir, 0755); err != nil {
		return fmt.Errorf("failed to create skills directory: %w", err)
	}

	skillContent := `# CCC Send - File Transfer Skill

## Description
Send files to the user via Telegram using the ccc send command.

## Usage
When the user asks you to send them a file, or when you have generated/built a file that the user needs (like an APK, binary, or any other file), use this command:

` + "```bash" + `
ccc send <file_path>
` + "```" + `

## How it works
- **Small files (< 50MB)**: Sent directly via Telegram
- **Large files (≥ 50MB)**: Streamed via relay server with a one-time download link

## Examples

### Send a built APK
` + "```bash" + `
ccc send ./build/app.apk
` + "```" + `

### Send a generated file
` + "```bash" + `
ccc send ./output/report.pdf
` + "```" + `

### Send from su
uninstallSkill function · go · L643-L648 (6 LOC)
hooks.go
func uninstallSkill() error {
	home, _ := os.UserHomeDir()
	skillPath := filepath.Join(home, ".claude", "skills", "ccc-send.md")
	os.Remove(skillPath)
	return nil
}
truncate function · go · L651-L656 (6 LOC)
hooks.go
func truncate(s string, n int) string {
	if len(s) <= n {
		return s
	}
	return s[:n] + "..."
}
Repobility's GitHub App fixes findings like these · https://github.com/apps/repobility-bot
hookLog function · go · L659-L666 (8 LOC)
hooks.go
func hookLog(format string, args ...interface{}) {
	f, err := os.OpenFile("/tmp/ccc-hook-debug.log", os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
	if err != nil {
		return
	}
	defer f.Close()
	fmt.Fprintf(f, "[%s] %s\n", time.Now().Format("15:04:05"), fmt.Sprintf(format, args...))
}
parseHookData function · go · L138-L147 (10 LOC)
main.go
func parseHookData(data []byte) (HookData, error) {
	var hd HookData
	if err := json.Unmarshal(data, &hd); err != nil {
		return hd, err
	}
	if len(hd.ToolInputRaw) > 0 {
		json.Unmarshal(hd.ToolInputRaw, &hd.ToolInput)
	}
	return hd, nil
}
init function · go · L155-L157 (3 LOC)
main.go
func init() {
	initPaths()
}
main function · go · L159-L411 (253 LOC)
main.go
func main() {
	// Handle flags
	if len(os.Args) > 1 {
		switch os.Args[1] {
		case "-h", "--help", "help":
			printHelp()
			return
		case "-v", "--version", "version":
			fmt.Printf("ccc version %s\n", version)
			return
		}
	}

	if len(os.Args) < 2 {
		// No args: start/attach tmux session with topic
		if err := startSession(false); err != nil {
			os.Exit(1)
		}
		return
	}

	// Check for -c flag (continue) as first arg
	if os.Args[1] == "-c" {
		if err := startSession(true); err != nil {
			os.Exit(1)
		}
		return
	}

	switch os.Args[1] {
	case "run":
		// Run claude directly (used inside tmux sessions)
		continueSession := len(os.Args) > 2 && os.Args[2] == "-c"
		if err := runClaudeRaw(continueSession); err != nil {
			os.Exit(1)
		}
		return
	case "setup":
		if len(os.Args) < 3 {
			fmt.Println("Usage: ccc setup <bot_token>")
			os.Exit(1)
		}
		if err := setup(os.Args[2]); err != nil {
			fmt.Fprintf(os.Stderr, "Error: %v\n", err)
			os.Exit(1)
		}

	case "doctor":
		doctor()

	
generateOTPSecret function · go · L38-L50 (13 LOC)
otp.go
func generateOTPSecret() (secret string, provisioningURI string, err error) {
	key, err := totp.Generate(totp.GenerateOpts{
		Issuer:      "CCC",
		AccountName: "claude-code-companion",
		Algorithm:   otp.AlgorithmSHA1,
		Digits:      otp.DigitsSix,
		Period:      30,
	})
	if err != nil {
		return "", "", fmt.Errorf("failed to generate TOTP key: %w", err)
	}
	return key.Secret(), key.URL(), nil
}
validateOTP function · go · L53-L56 (4 LOC)
otp.go
func validateOTP(secret, code string) bool {
	code = strings.TrimSpace(code)
	return totp.Validate(code, secret)
}
isOTPEnabled function · go · L59-L61 (3 LOC)
otp.go
func isOTPEnabled(config *Config) bool {
	return config.OTPSecret != ""
}
setupOTP function · go · L64-L82 (19 LOC)
otp.go
func setupOTP(config *Config) (string, error) {
	secret, uri, err := generateOTPSecret()
	if err != nil {
		return "", err
	}

	config.OTPSecret = secret
	if err := saveConfig(config); err != nil {
		return "", fmt.Errorf("failed to save config: %w", err)
	}

	// Print QR code to terminal
	fmt.Println("\nScan this QR code with your authenticator app:")
	fmt.Println()
	qrterminal.GenerateHalfBlock(uri, qrterminal.L, os.Stdout)

	msg := fmt.Sprintf("Or enter the secret manually: %s", secret)
	return msg, nil
}
All rows above produced by Repobility · https://repobility.com
writeOTPRequest function · go · L85-L91 (7 LOC)
otp.go
func writeOTPRequest(sessionID string, req *OTPPermissionRequest) error {
	data, err := json.Marshal(req)
	if err != nil {
		return err
	}
	return os.WriteFile(otpRequestPrefix+sessionID, data, 0600)
}
writeOTPResponse function · go · L94-L104 (11 LOC)
otp.go
func writeOTPResponse(sessionID string, approved bool) error {
	resp := OTPPermissionResponse{
		Approved:  approved,
		Timestamp: time.Now().Unix(),
	}
	data, err := json.Marshal(resp)
	if err != nil {
		return err
	}
	return os.WriteFile(otpResponsePrefix+sessionID, data, 0600)
}
waitForOTPResponse function · go · L108-L137 (30 LOC)
otp.go
func waitForOTPResponse(sessionID, tmuxName string, timeout time.Duration) (bool, error) {
	responsePath := otpResponsePrefix + sessionID
	deadline := time.Now().Add(timeout)

	for time.Now().Before(deadline) {
		// Check if another parallel hook already got approved and wrote a grant
		if hasValidOTPGrant(tmuxName) {
			os.Remove(otpRequestPrefix + sessionID)
			return true, nil
		}

		data, err := os.ReadFile(responsePath)
		if err == nil {
			// Clean up files
			os.Remove(responsePath)
			os.Remove(otpRequestPrefix + sessionID)

			var resp OTPPermissionResponse
			if err := json.Unmarshal(data, &resp); err != nil {
				return false, err
			}
			return resp.Approved, nil
		}
		time.Sleep(500 * time.Millisecond)
	}

	// Clean up on timeout
	os.Remove(otpRequestPrefix + sessionID)
	return false, fmt.Errorf("OTP timeout")
}
getPendingOTPRequest function · go · L140-L150 (11 LOC)
otp.go
func getPendingOTPRequest(sessionID string) (*OTPPermissionRequest, error) {
	data, err := os.ReadFile(otpRequestPrefix + sessionID)
	if err != nil {
		return nil, err
	}
	var req OTPPermissionRequest
	if err := json.Unmarshal(data, &req); err != nil {
		return nil, err
	}
	return &req, nil
}
findPendingOTPSession function · go · L153-L163 (11 LOC)
otp.go
func findPendingOTPSession() string {
	matches, err := filepath.Glob(otpRequestPrefix + "*")
	if err != nil || len(matches) == 0 {
		return ""
	}
	for _, match := range matches {
		sessionID := strings.TrimPrefix(match, otpRequestPrefix)
		return sessionID
	}
	return ""
}
hasValidOTPGrant function · go · L166-L172 (7 LOC)
otp.go
func hasValidOTPGrant(tmuxName string) bool {
	info, err := os.Stat(otpGrantPrefix + tmuxName)
	if err != nil {
		return false
	}
	return time.Since(info.ModTime()) < otpGrantDuration
}
writeOTPGrant function · go · L175-L177 (3 LOC)
otp.go
func writeOTPGrant(tmuxName string) {
	os.WriteFile(otpGrantPrefix+tmuxName, []byte("1"), 0600)
}
handleSendFile function · go · L21-L105 (85 LOC)
relay.go
func handleSendFile(filePath string) error {
	config, err := loadConfig()
	if err != nil {
		return fmt.Errorf("no config found: %w", err)
	}

	// Get absolute path
	if !filepath.IsAbs(filePath) {
		cwd, _ := os.Getwd()
		filePath = filepath.Join(cwd, filePath)
	}

	// Check file exists
	fileInfo, err := os.Stat(filePath)
	if err != nil {
		return fmt.Errorf("file not found: %w", err)
	}

	// Find session from current directory
	cwd, _ := os.Getwd()
	var sessionName string
	var topicID int64
	for name, info := range config.Sessions {
		if info == nil {
			continue
		}
		if cwd == info.Path || strings.HasPrefix(cwd, info.Path+"/") {
			sessionName = name
			topicID = info.TopicID
			break
		}
	}

	if topicID == 0 || config.GroupID == 0 {
		return fmt.Errorf("no session found for current directory")
	}

	fileName := filepath.Base(filePath)
	fileSize := fileInfo.Size()

	// Small file: send directly via Telegram
	if fileSize < maxTelegramFileSize {
		fmt.Printf("📤 Sending %s (%d MB) via T
Repobility — same analyzer, your code, free for public repos · /scan/
streamFileToRelay function · go · L107-L170 (64 LOC)
relay.go
func streamFileToRelay(relayURL, token, filePath, fileName string, fileSize int64) error {
	// Poll for download requests - loop to allow multiple downloads
	timeout := time.After(10 * time.Minute)
	ticker := time.NewTicker(1 * time.Second)
	defer ticker.Stop()

	downloadCount := 0

	for {
		select {
		case <-timeout:
			http.Get(relayURL + "/cancel/" + token)
			if downloadCount > 0 {
				fmt.Printf("⏰ Session expired after %d download(s)\n", downloadCount)
				return nil
			}
			return fmt.Errorf("download timed out (10 min)")
		case <-ticker.C:
			resp, err := http.Get(relayURL + "/status/" + token)
			if err != nil {
				continue
			}
			body, _ := io.ReadAll(io.LimitReader(resp.Body, maxResponseSize))
			resp.Body.Close()

			status := string(body)
			if status == "waiting" {
				continue
			} else if status == "ready" {
				// Someone requested download, start streaming
				downloadCount++
				fmt.Printf("📤 Streaming %s (download #%d)...\n", fileName, downloadCount)

				file, er
runRelayServer function · go · L188-L429 (242 LOC)
relay.go
func runRelayServer(port string) {
	// Clean up old transfers periodically
	go func() {
		for {
			time.Sleep(1 * time.Minute)
			relayTransfers.Lock()
			for token, t := range relayTransfers.transfers {
				if time.Since(t.Created) > 15*time.Minute {
					t.Status = "cancelled"
					select {
					case <-t.DoneChan:
					default:
						close(t.DoneChan)
					}
					delete(relayTransfers.transfers, token)
				}
			}
			relayTransfers.Unlock()
		}
	}()

	// Register a new transfer
	http.HandleFunc("/register", func(w http.ResponseWriter, r *http.Request) {
		if r.Method != http.MethodPost {
			http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
			return
		}

		var data struct {
			Token    string `json:"token"`
			Filename string `json:"filename"`
			Size     int64  `json:"size"`
		}
		if err := json.NewDecoder(r.Body).Decode(&data); err != nil {
			http.Error(w, "Invalid JSON", http.StatusBadRequest)
			return
		}

		relayTransfers.Lock()
		relayTransfers.transfers[data.To
installService function · go · L10-L20 (11 LOC)
service.go
func installService() error {
	home, _ := os.UserHomeDir()

	// Detect OS and install appropriate service
	if _, err := os.Stat("/Library"); err == nil {
		// macOS - use launchd
		return installLaunchdService(home)
	}
	// Linux - use systemd
	return installSystemdService(home)
}
page 1 / 2next ›