// SPDX-License-Identifier: EUPL-1.2 // CLI commands registered by the agentic service during OnStartup. package agentic import ( "context" "os" "dappco.re/go/agent/pkg/lib" core "dappco.re/go/core" ) // registerCommands adds agentic CLI commands to Core's command tree. func (s *PrepSubsystem) registerCommands(ctx context.Context) { c := s.core c.Command("run/task", core.Command{Description: "Run a single task end-to-end", Action: s.cmdRunTaskFactory(ctx)}) c.Command("run/orchestrator", core.Command{Description: "Run the queue orchestrator (standalone, no MCP)", Action: s.cmdOrchestratorFactory(ctx)}) c.Command("prep", core.Command{Description: "Prepare a workspace: clone repo, build prompt", Action: s.cmdPrep}) c.Command("status", core.Command{Description: "List agent workspace statuses", Action: s.cmdStatus}) c.Command("prompt", core.Command{Description: "Build and display an agent prompt for a repo", Action: s.cmdPrompt}) c.Command("extract", core.Command{Description: "Extract a workspace template to a directory", Action: s.cmdExtract}) } // cmdRunTaskFactory returns the run/task action closure (needs ctx for DispatchSync). func (s *PrepSubsystem) cmdRunTaskFactory(ctx context.Context) func(core.Options) core.Result { return func(opts core.Options) core.Result { return s.cmdRunTask(ctx, opts) } } func (s *PrepSubsystem) cmdRunTask(ctx context.Context, opts core.Options) core.Result { repo := opts.String("repo") agent := opts.String("agent") task := opts.String("task") issueStr := opts.String("issue") org := opts.String("org") if repo == "" || task == "" { core.Print(nil, "usage: core-agent run task --repo= --task=\"...\" --agent=codex [--issue=N] [--org=core]") return core.Result{OK: false} } if agent == "" { agent = "codex" } if org == "" { org = "core" } issue := parseIntStr(issueStr) core.Print(os.Stderr, "core-agent run task") core.Print(os.Stderr, " repo: %s/%s", org, repo) core.Print(os.Stderr, " agent: %s", agent) if issue > 0 { core.Print(os.Stderr, " issue: #%d", issue) } core.Print(os.Stderr, " task: %s", task) core.Print(os.Stderr, "") result := s.DispatchSync(ctx, DispatchSyncInput{ Org: org, Repo: repo, Agent: agent, Task: task, Issue: issue, }) if !result.OK { core.Print(os.Stderr, "FAILED: %v", result.Error) return core.Result{Value: result.Error, OK: false} } core.Print(os.Stderr, "DONE: %s", result.Status) if result.PRURL != "" { core.Print(os.Stderr, " PR: %s", result.PRURL) } return core.Result{OK: true} } // cmdOrchestratorFactory returns the orchestrator action closure (needs ctx for blocking). func (s *PrepSubsystem) cmdOrchestratorFactory(ctx context.Context) func(core.Options) core.Result { return func(opts core.Options) core.Result { return s.cmdOrchestrator(ctx, opts) } } func (s *PrepSubsystem) cmdOrchestrator(ctx context.Context, _ core.Options) core.Result { core.Print(os.Stderr, "core-agent orchestrator running (pid %s)", core.Env("PID")) core.Print(os.Stderr, " workspace: %s", WorkspaceRoot()) core.Print(os.Stderr, " watching queue, draining on 30s tick + completion poke") <-ctx.Done() core.Print(os.Stderr, "orchestrator shutting down") return core.Result{OK: true} } func (s *PrepSubsystem) cmdPrep(opts core.Options) core.Result { repo := opts.String("_arg") if repo == "" { core.Print(nil, "usage: core-agent prep --issue=N|--pr=N|--branch=X --task=\"...\"") return core.Result{OK: false} } input := PrepInput{ Repo: repo, Org: opts.String("org"), Task: opts.String("task"), Template: opts.String("template"), Persona: opts.String("persona"), DryRun: opts.Bool("dry-run"), } if v := opts.String("issue"); v != "" { input.Issue = parseIntStr(v) } if v := opts.String("pr"); v != "" { input.PR = parseIntStr(v) } if v := opts.String("branch"); v != "" { input.Branch = v } if v := opts.String("tag"); v != "" { input.Tag = v } if input.Issue == 0 && input.PR == 0 && input.Branch == "" && input.Tag == "" { input.Branch = "dev" } _, out, err := s.TestPrepWorkspace(context.Background(), input) if err != nil { core.Print(nil, "error: %v", err) return core.Result{Value: err, OK: false} } core.Print(nil, "workspace: %s", out.WorkspaceDir) core.Print(nil, "repo: %s", out.RepoDir) core.Print(nil, "branch: %s", out.Branch) core.Print(nil, "resumed: %v", out.Resumed) core.Print(nil, "memories: %d", out.Memories) core.Print(nil, "consumers: %d", out.Consumers) if out.Prompt != "" { core.Print(nil, "") core.Print(nil, "--- prompt (%d chars) ---", len(out.Prompt)) core.Print(nil, "%s", out.Prompt) } return core.Result{OK: true} } func (s *PrepSubsystem) cmdStatus(opts core.Options) core.Result { wsRoot := WorkspaceRoot() fsys := s.core.Fs() r := fsys.List(wsRoot) if !r.OK { core.Print(nil, "no workspaces found at %s", wsRoot) return core.Result{OK: true} } entries := r.Value.([]os.DirEntry) if len(entries) == 0 { core.Print(nil, "no workspaces") return core.Result{OK: true} } for _, e := range entries { if !e.IsDir() { continue } statusFile := core.JoinPath(wsRoot, e.Name(), "status.json") if sr := fsys.Read(statusFile); sr.OK { core.Print(nil, " %s", e.Name()) } } return core.Result{OK: true} } func (s *PrepSubsystem) cmdPrompt(opts core.Options) core.Result { repo := opts.String("_arg") if repo == "" { core.Print(nil, "usage: core-agent prompt --task=\"...\"") return core.Result{OK: false} } org := opts.String("org") if org == "" { org = "core" } task := opts.String("task") if task == "" { task = "Review and report findings" } repoPath := core.JoinPath(core.Env("DIR_HOME"), "Code", org, repo) input := PrepInput{ Repo: repo, Org: org, Task: task, Template: opts.String("template"), Persona: opts.String("persona"), } prompt, memories, consumers := s.TestBuildPrompt(context.Background(), input, "dev", repoPath) core.Print(nil, "memories: %d", memories) core.Print(nil, "consumers: %d", consumers) core.Print(nil, "") core.Print(nil, "%s", prompt) return core.Result{OK: true} } func (s *PrepSubsystem) cmdExtract(opts core.Options) core.Result { tmpl := opts.String("_arg") if tmpl == "" { tmpl = "default" } target := opts.String("target") if target == "" { target = core.Path("Code", ".core", "workspace", "test-extract") } data := &lib.WorkspaceData{ Repo: "test-repo", Branch: "dev", Task: "test extraction", Agent: "codex", } core.Print(nil, "extracting template %q to %s", tmpl, target) if err := lib.ExtractWorkspace(tmpl, target, data); err != nil { return core.Result{Value: err, OK: false} } fsys := s.core.Fs() r := fsys.List(target) if r.OK { for _, e := range r.Value.([]os.DirEntry) { marker := " " if e.IsDir() { marker = "/" } core.Print(nil, " %s%s", e.Name(), marker) } } core.Print(nil, "done") return core.Result{OK: true} } // parseIntStr extracts digits from a string and returns the integer value. func parseIntStr(s string) int { n := 0 for _, ch := range s { if ch >= '0' && ch <= '9' { n = n*10 + int(ch-'0') } } return n }