package cmd import ( "fmt" "os" "github.com/spf13/cobra" "github.com/warrenronsiek/ctask/internal/config" "github.com/warrenronsiek/ctask/internal/session" "github.com/warrenronsiek/ctask/internal/shell" "github.com/warrenronsiek/ctask/internal/workspace" ) var newCmd = &cobra.Command{ Use: "new [title]", Short: "Create a new task or project workspace and launch the agent", Long: "Create a new task or project workspace and launch the agent. If title is omitted, generates task-HHMMSS.", Args: cobra.MaximumNArgs(1), SilenceUsage: true, RunE: runNew, } var ( newCategory string newContainer bool newShell bool newAgent string newNoLaunch bool newProject bool ) func init() { newCmd.Flags().StringVarP(&newCategory, "category", "c", "", "Workspace category subdirectory (default: \"general\" for tasks, \"projects\" for projects)") newCmd.Flags().BoolVar(&newProject, "project", false, "Create a project workspace (longer-lived; uses CTASK_PROJECT_ROOT if set, runs git init)") newCmd.Flags().BoolVar(&newContainer, "container", false, "Launch in container sandbox (deferred)") newCmd.Flags().BoolVar(&newShell, "shell", false, "Open interactive shell instead of agent") newCmd.Flags().StringVarP(&newAgent, "agent", "a", "", "Command to exec as the agent") newCmd.Flags().BoolVar(&newNoLaunch, "no-launch", false, "Create workspace only, do not launch") rootCmd.AddCommand(newCmd) } func runNew(cmd *cobra.Command, args []string) error { if newContainer { fmt.Println(shell.ContainerNotice()) return nil } agent := newAgent if agent == "" { agent = config.ResolveAgent() } title := "" if len(args) > 0 { title = args[0] } // Determine category default and explicit-ness. categoryExplicit := cmd.Flags().Changed("category") category := newCategory if !categoryExplicit { if newProject { category = "projects" } else { category = "general" } } // Determine workspace root + skip-category behavior. taskRoot := config.ResolveRoot() root := taskRoot skipCategoryDir := false if newProject { if override := config.ResolveProjectRoot(); override != "" { root = override if !categoryExplicit { skipCategoryDir = true } } } opts := workspace.CreateOpts{ Root: root, Title: title, Category: category, Mode: "local", Agent: agent, IsProject: newProject, SeedDir: config.ResolveSeedDir(), SkipCategoryDir: skipCategoryDir, } if newProject { opts.ProjectSeedDir = config.ResolveProjectSeedDir() } ws, err := workspace.Create(opts) if err != nil { return err } if newProject { if !workspace.GitAvailable() { fmt.Println("[ctask] git not found; skipped repository initialization") } else { if err := workspace.RunGitInit(ws.Path); err != nil { fmt.Fprintf(os.Stderr, "[ctask] warning: %v\n", err) } } // Seed-wins: EnsureGitignore is a no-op when a seed provided .gitignore. if err := workspace.EnsureGitignore(ws.Path); err != nil { fmt.Fprintf(os.Stderr, "[ctask] warning: failed to create .gitignore: %v\n", err) } } relPath := workspace.RelativePath(root, ws.Path) fmt.Printf("[ctask] created %s\n", relPath) if newNoLaunch { return nil } envVars := config.EnvVars(ws.Meta.Slug, ws.Meta.Mode, root, ws.Path, ws.Meta.Category, workspace.EffectiveType(ws.Meta), ws.Meta.LaunchDir) return session.Run(session.LaunchOpts{ WsDir: ws.Path, EnvVars: envVars, Agent: agent, Mode: ws.Meta.Mode, Slug: ws.Meta.Slug, Shell: newShell, LaunchDir: ws.Meta.LaunchDir, NewlyCreated: true, }) }