Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
55 changes: 55 additions & 0 deletions internal/cli/cli.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ var Commands = []struct {
{"conversation", "List conversation turns from the Claude session (interactive on TTY)"},
{"info", "Show the matched Claude session metadata"},
{"sessions", "List session IDs with metadata (use --pick for TUI JSON picker)"},
{"move", "Move a session's project path to a new location (--from, --session, --help)"},
{"config", "View/edit ccx config and get/set dot-path values"},
{"help", "Show available commands and usage"},
}
Expand Down Expand Up @@ -149,6 +150,9 @@ func printHelp() {
fmt.Fprintf(os.Stderr, " ccx images Interactive image picker\n")
fmt.Fprintf(os.Stderr, " ccx conversation Interactive conversation picker\n")
fmt.Fprintf(os.Stderr, " ccx info Show current matched session metadata\n")
fmt.Fprintf(os.Stderr, " ccx move <new-path> Move current session's project path\n")
fmt.Fprintf(os.Stderr, " ccx move --session <id> <new> Move a session by ID\n")
fmt.Fprintf(os.Stderr, " ccx move --from <dir> <new> Move a project dir by path (no session ID needed)\n")
fmt.Fprintf(os.Stderr, " ccx config view Print ~/.config/ccx/config.yaml\n")
fmt.Fprintf(os.Stderr, " ccx config edit Open config in $EDITOR\n")
fmt.Fprintf(os.Stderr, " ccx config set remote.pod_name ccx-worker\n\n")
Expand Down Expand Up @@ -322,6 +326,57 @@ func RunSessions(claudeDir string, all bool) error {
return nil
}

// RunMove moves a project's session directory to newPath, taking every
// session under it along. oldDir, if set, is used directly. Otherwise
// sessionID resolves to its project dir; if that's empty too, the session
// is resolved the same way other subcommands do (tmux window / live
// registry match).
func RunMove(claudeDir, sessionID, oldDir, newPath string) error {
newPath = strings.TrimSpace(newPath)
if newPath == "" {
return fmt.Errorf("usage: ccx move [--from <dir> | --session <id>] <new-path>")
}
abs, err := filepath.Abs(newPath)
if err != nil {
return fmt.Errorf("resolve new path: %w", err)
}
newPath = abs

oldPath := strings.TrimSpace(oldDir)
if oldPath != "" {
abs, err := filepath.Abs(oldPath)
if err != nil {
return fmt.Errorf("resolve old path: %w", err)
}
oldPath = abs
} else if sessionID != "" {
sess, ok := session.FindSessionByID(claudeDir, sessionID)
if !ok {
return fmt.Errorf("session %s not found", sessionID)
}
oldPath = sess.ProjectPath
} else {
_, sessID, err := findSessionFile(claudeDir)
if err != nil {
return err
}
sess, ok := session.FindSessionByID(claudeDir, sessID)
if !ok {
return fmt.Errorf("session %s not found", sessID)
}
oldPath = sess.ProjectPath
}

if oldPath == newPath {
return fmt.Errorf("new path is the same as the current path: %s", oldPath)
}
if err := session.MoveProject(oldPath, newPath); err != nil {
return err
}
fmt.Fprintf(os.Stdout, "%s -> %s\n", oldPath, newPath)
return nil
}

func RunInfo(claudeDir string) error {
_, sessID, err := findSessionFile(claudeDir)
if err != nil {
Expand Down
25 changes: 25 additions & 0 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -200,6 +200,31 @@ func main() {
os.Exit(1)
}
os.Exit(0)
case "move":
fs := flag.NewFlagSet("move", flag.ExitOnError)
sess := fs.String("session", "", "session ID to move (prefix match); default: current tmux window's session")
from := fs.String("from", "", "project directory to move by path instead of session ID (moves every session under it)")
dirFlag := fs.String("dir", "", "path to Claude data directory (default: ~/.claude)")
fs.Usage = func() {
fmt.Fprintf(os.Stderr, "Move a Claude session's project path to a new location.\n\n")
fmt.Fprintf(os.Stderr, "Usage:\n")
fmt.Fprintf(os.Stderr, " ccx move <new-path> Move current tmux session's project path\n")
fmt.Fprintf(os.Stderr, " ccx move --session <id> <new-path> Move a specific session by ID\n")
fmt.Fprintf(os.Stderr, " ccx move --from <dir> <new-path> Move a project dir by path (no session lookup)\n\n")
fmt.Fprintf(os.Stderr, "Flags:\n")
fs.PrintDefaults()
}
fs.Parse(os.Args[2:])
if fs.NArg() != 1 {
fs.Usage()
os.Exit(1)
}
dir := resolveClaudeDir(*dirFlag)
if err := cli.RunMove(dir, *sess, *from, fs.Arg(0)); err != nil {
fmt.Fprintf(os.Stderr, "Error: %v\n", err)
os.Exit(1)
}
os.Exit(0)
case "urls", "files", "changes", "images", "conversation", "info", "help":
subcmd := os.Args[1]
fs := flag.NewFlagSet(subcmd, flag.ExitOnError)
Expand Down
Loading