02dcdcc215
When `ctask new` launched the agent or shell and the child exited without touching any files (e.g. the user canceled at Claude Code's trust prompt), the workspace was left behind as empty clutter. Now, if the invocation just created the workspace and the manifest diff is empty (zero added, modified, deleted), the workspace directory is removed and finalize is skipped. resume/open/last are unaffected (NewlyCreated defaults to false), and --no-launch is unaffected (session.Run is never called). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
134 lines
3.6 KiB
Go
134 lines
3.6 KiB
Go
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))
|
|
|
|
return session.Run(session.LaunchOpts{
|
|
WsDir: ws.Path,
|
|
EnvVars: envVars,
|
|
Agent: agent,
|
|
Mode: ws.Meta.Mode,
|
|
Slug: ws.Meta.Slug,
|
|
Shell: newShell,
|
|
NewlyCreated: true,
|
|
})
|
|
}
|