103f2cd33e
LaunchOpts gains LaunchDir. session.Run resolves it via workspace.ResolveLaunch, prints any fallback warning, and passes the absolute path as the child process's working directory. Security violations (absolute paths, .. escape) abort the session. The banner gains a 'project dir: <name>/' line when launch_dir is set. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
135 lines
3.6 KiB
Go
135 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), 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,
|
|
})
|
|
}
|