Skip to content
This repository was archived by the owner on Jan 23, 2026. It is now read-only.
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
7 changes: 7 additions & 0 deletions cmd/cloud.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import (
"github.com/agentuity/cli/internal/errsystem"
"github.com/agentuity/cli/internal/ignore"
"github.com/agentuity/cli/internal/project"
"github.com/agentuity/cli/internal/prompts"
"github.com/agentuity/cli/internal/util"
"github.com/agentuity/go-common/crypto"
"github.com/agentuity/go-common/env"
Expand Down Expand Up @@ -450,6 +451,12 @@ Examples:
orgSecret = *startResponse.Data.OrgSecret
}

// Process prompts.yaml if present
if err := prompts.ProcessPrompts(ctx, logger, client, dir, startResponse.Data.DeploymentId); err != nil {
errsystem.New(errsystem.ErrDeployProject, err,
errsystem.WithContextMessage("Error processing prompts")).ShowErrorAndExit()
}

var saveProject bool

// remove any agents that were deleted from the project
Expand Down
48 changes: 47 additions & 1 deletion internal/bundler/bundler.go
Original file line number Diff line number Diff line change
Expand Up @@ -376,6 +376,10 @@ func bundleJavascript(ctx BundleContext, dir string, outdir string, theproject *
return nil // This line will never be reached due to os.Exit
}

if err := copyPromptsFromSrc(ctx.Logger, dir, outdir); err != nil {
return fmt.Errorf("copy prompts.yaml: %w", err)
}

if err := validateDiskRequest(ctx, outdir); err != nil {
return err
}
Expand Down Expand Up @@ -469,7 +473,49 @@ func bundlePython(ctx BundleContext, dir string, outdir string, theproject *proj
}
config["app"] = app
}
return os.WriteFile(filepath.Join(outdir, "config.json"), []byte(cstr.JSONStringify(config)), 0644)
if err := os.WriteFile(filepath.Join(outdir, "config.json"), []byte(cstr.JSONStringify(config)), 0644); err != nil {
return err
}

if err := copyPromptsFromSrc(ctx.Logger, dir, outdir); err != nil {
return fmt.Errorf("copy prompts.yaml: %w", err)
}

return nil
}

func copyPromptsFromSrc(logger logger.Logger, projectDir, outdir string) error {
srcRoot := filepath.Join(projectDir, "src")
if !util.Exists(srcRoot) {
return nil // nothing to do
}

return filepath.Walk(srcRoot, func(path string, info os.FileInfo, err error) error {
if err != nil {
return err
}
if info.IsDir() {
return nil
}
name := strings.ToLower(info.Name())
if name != "prompts.yaml" && name != "prompts.yml" {
return nil
}

rel, err := filepath.Rel(projectDir, path)
if err != nil {
return err
}
destPath := filepath.Join(outdir, rel)

if err := os.MkdirAll(filepath.Dir(destPath), 0o755); err != nil {
return err
}

logger.Debug("copying prompts file: %s -> %s", path, destPath)
_, err = util.CopyFile(path, destPath)
return err
})
}

func getAgents(theproject *project.Project, filename string) []AgentConfig {
Expand Down
124 changes: 124 additions & 0 deletions internal/prompts/prompts.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
package prompts

import (
"context"
"encoding/json"
"fmt"
"io"
"os"
"path/filepath"

"github.com/agentuity/cli/internal/util"
"github.com/agentuity/go-common/logger"
"gopkg.in/yaml.v3"
)

type Prompt struct {
ID string `yaml:"id" json:"id"`
Name string `yaml:"name" json:"name"`
Description string `yaml:"description,omitempty" json:"description,omitempty"`
System string `yaml:"system,omitempty" json:"system,omitempty"`
Prompt string `yaml:"prompt,omitempty" json:"prompt,omitempty"`
}

type PromptsFile struct {
Prompts []Prompt `yaml:"prompts" json:"prompts"`
}

type PromptRequest struct {
Slug string `json:"slug"`
Content map[string]interface{} `json:"content"`
}

type PromptsAPIRequest struct {
Prompts []PromptRequest `json:"prompts"`
}

// ProcessPrompts reads the single prompts.yaml file from .agentuity bundle and sends it to the API
func ProcessPrompts(ctx context.Context, logger logger.Logger, client *util.APIClient, projectDir string, deploymentId string) error {
// Look for the single prompts.yaml file that was copied by bundler
promptsFile := filepath.Join(projectDir, ".agentuity", "src", "prompts.yaml")
if !util.Exists(promptsFile) {
logger.Debug("no prompts.yaml found in bundle, skipping prompts processing")
return nil
}

prompts, err := parsePromptsFile(promptsFile)
if err != nil {
return fmt.Errorf("failed to parse prompts.yaml: %w", err)
}

if len(prompts) == 0 {
logger.Debug("no prompts found in prompts.yaml, skipping prompts processing")
return nil
}

// Validate prompts have required fields
for _, prompt := range prompts {
if prompt.ID == "" {
return fmt.Errorf("prompt missing required 'id' field")
}
if prompt.Name == "" {
return fmt.Errorf("prompt '%s' missing required 'name' field", prompt.ID)
}
// Either / or
if prompt.System == "" {
return fmt.Errorf("prompt '%s' missing required 'system' field", prompt.ID)
}
if prompt.Prompt == "" {
return fmt.Errorf("prompt '%s' missing required 'prompt' field", prompt.ID)
}
}

// Convert to API request format
var apiRequest PromptsAPIRequest
for _, prompt := range prompts {
// Convert prompt to map for JSON serialization
contentBytes, err := json.Marshal(prompt)
if err != nil {
return fmt.Errorf("failed to marshal prompt %s: %w", prompt.ID, err)
}

var content map[string]interface{}
if err := json.Unmarshal(contentBytes, &content); err != nil {
return fmt.Errorf("failed to unmarshal prompt %s content: %w", prompt.ID, err)
}

apiRequest.Prompts = append(apiRequest.Prompts, PromptRequest{
Slug: prompt.ID,
Content: content,
})

logger.Debug("processing prompt: %s", prompt.ID)
}

// Send to API
endpoint := fmt.Sprintf("/cli/deploy/%s/prompts", deploymentId)
if err := client.Do("PUT", endpoint, apiRequest, nil); err != nil {
return fmt.Errorf("failed to process prompts via API: %w", err)
}

logger.Info("processed %d prompts successfully", len(prompts))
return nil
}

// parsePromptsFile parses a single prompts.yaml file
func parsePromptsFile(filename string) ([]Prompt, error) {
file, err := os.Open(filename)
if err != nil {
return nil, err
}
defer file.Close()

content, err := io.ReadAll(file)
if err != nil {
return nil, err
}

var promptsFile PromptsFile
if err := yaml.Unmarshal(content, &promptsFile); err != nil {
return nil, fmt.Errorf("failed to parse YAML: %w", err)
}

return promptsFile.Prompts, nil
}
Loading