package cmd import ( "context" "encoding/json" "fmt" "os" "strings" "gogs.dmsc.dev/arp/arp_cli/client" "gogs.dmsc.dev/arp/arp_cli/config" "github.com/AlecAivazis/survey/v2" "github.com/olekukonko/tablewriter" "github.com/urfave/cli/v3" ) // WorkflowCommand returns the workflow command func WorkflowCommand() *cli.Command { return &cli.Command{ Name: "workflow", Usage: "Manage workflows", Description: `Manage ARP workflows. Workflows are automated processes defined by templates. Workflow templates define the structure of automated workflows as DAGs (Directed Acyclic Graphs). Workflow instances are running executions of templates. Workflow nodes represent individual steps in a running workflow.`, Commands: []*cli.Command{ // WorkflowTemplate commands { Name: "template", Usage: "Manage workflow templates", Commands: []*cli.Command{ { Name: "list", Aliases: []string{"ls"}, Usage: "List all workflow templates", Flags: []cli.Flag{ &cli.BoolFlag{ Name: "json", Aliases: []string{"j"}, Usage: "Output as JSON", }, }, Action: workflowTemplateList, }, { Name: "get", Usage: "Get a workflow template by ID", Flags: []cli.Flag{ &cli.StringFlag{ Name: "id", Aliases: []string{"i"}, Usage: "Template ID", Required: true, }, &cli.BoolFlag{ Name: "json", Aliases: []string{"j"}, Usage: "Output as JSON", }, }, Action: workflowTemplateGet, }, { Name: "create", Usage: "Create a new workflow template", Action: workflowTemplateCreate, Flags: []cli.Flag{ &cli.StringFlag{ Name: "name", Aliases: []string{"n"}, Usage: "Template name", }, &cli.StringFlag{ Name: "description", Aliases: []string{"d"}, Usage: "Template description", }, &cli.StringFlag{ Name: "definition", Aliases: []string{"f"}, Usage: "Workflow definition (JSON string or @filename)", }, &cli.BoolFlag{ Name: "active", Usage: "Set template as active", Value: true, }, }, }, { Name: "update", Usage: "Update a workflow template", Action: workflowTemplateUpdate, Flags: []cli.Flag{ &cli.StringFlag{ Name: "id", Aliases: []string{"i"}, Usage: "Template ID", Required: true, }, &cli.StringFlag{ Name: "name", Aliases: []string{"n"}, Usage: "Template name", }, &cli.StringFlag{ Name: "description", Aliases: []string{"d"}, Usage: "Template description", }, &cli.StringFlag{ Name: "definition", Aliases: []string{"f"}, Usage: "Workflow definition (JSON string or @filename)", }, &cli.BoolFlag{ Name: "active", Usage: "Set template as active", }, }, }, { Name: "delete", Usage: "Delete a workflow template", Action: workflowTemplateDelete, Flags: []cli.Flag{ &cli.StringFlag{ Name: "id", Aliases: []string{"i"}, Usage: "Template ID", Required: true, }, &cli.BoolFlag{ Name: "yes", Aliases: []string{"y"}, Usage: "Skip confirmation", }, }, }, }, }, // WorkflowInstance commands { Name: "instance", Usage: "Manage workflow instances", Commands: []*cli.Command{ { Name: "list", Aliases: []string{"ls"}, Usage: "List all workflow instances", Flags: []cli.Flag{ &cli.BoolFlag{ Name: "json", Aliases: []string{"j"}, Usage: "Output as JSON", }, }, Action: workflowInstanceList, }, { Name: "get", Usage: "Get a workflow instance by ID", Flags: []cli.Flag{ &cli.StringFlag{ Name: "id", Aliases: []string{"i"}, Usage: "Instance ID", Required: true, }, &cli.BoolFlag{ Name: "json", Aliases: []string{"j"}, Usage: "Output as JSON", }, }, Action: workflowInstanceGet, }, { Name: "start", Usage: "Start a new workflow instance from a template", Action: workflowInstanceStart, Flags: []cli.Flag{ &cli.StringFlag{ Name: "template", Aliases: []string{"t"}, Usage: "Template ID", Required: true, }, &cli.StringFlag{ Name: "service", Aliases: []string{"s"}, Usage: "Service ID to associate with the workflow", }, &cli.StringFlag{ Name: "context", Aliases: []string{"c"}, Usage: "Initial workflow context (JSON string)", }, }, }, { Name: "cancel", Usage: "Cancel a running workflow instance", Action: workflowInstanceCancel, Flags: []cli.Flag{ &cli.StringFlag{ Name: "id", Aliases: []string{"i"}, Usage: "Instance ID", Required: true, }, }, }, }, }, // WorkflowNode commands { Name: "node", Usage: "Manage workflow nodes", Commands: []*cli.Command{ { Name: "list", Aliases: []string{"ls"}, Usage: "List all nodes for a workflow instance", Flags: []cli.Flag{ &cli.StringFlag{ Name: "instance", Aliases: []string{"i"}, Usage: "Instance ID", Required: true, }, &cli.BoolFlag{ Name: "json", Aliases: []string{"j"}, Usage: "Output as JSON", }, }, Action: workflowNodeList, }, { Name: "get", Usage: "Get a workflow node by ID", Flags: []cli.Flag{ &cli.StringFlag{ Name: "id", Aliases: []string{"i"}, Usage: "Node ID", Required: true, }, &cli.BoolFlag{ Name: "json", Aliases: []string{"j"}, Usage: "Output as JSON", }, }, Action: workflowNodeGet, }, { Name: "retry", Usage: "Retry a failed workflow node", Action: workflowNodeRetry, Flags: []cli.Flag{ &cli.StringFlag{ Name: "id", Aliases: []string{"i"}, Usage: "Node ID", Required: true, }, }, }, }, }, }, } } // WorkflowTemplate represents a workflow template type WorkflowTemplate struct { ID string `json:"id"` Name string `json:"name"` Description string `json:"description"` Definition string `json:"definition"` IsActive bool `json:"isActive"` CreatedBy *User `json:"createdBy"` CreatedAt string `json:"createdAt"` UpdatedAt string `json:"updatedAt"` } // WorkflowInstance represents a running workflow instance type WorkflowInstance struct { ID string `json:"id"` Template *WorkflowTemplate `json:"template"` Status string `json:"status"` Context string `json:"context"` Service *Service `json:"service"` CreatedAt string `json:"createdAt"` UpdatedAt string `json:"updatedAt"` CompletedAt *string `json:"completedAt"` } // WorkflowNode represents a node in a workflow instance type WorkflowNode struct { ID string `json:"id"` NodeKey string `json:"nodeKey"` NodeType string `json:"nodeType"` Status string `json:"status"` Task *Task `json:"task"` InputData string `json:"inputData"` OutputData string `json:"outputData"` RetryCount int `json:"retryCount"` CreatedAt string `json:"createdAt"` UpdatedAt string `json:"updatedAt"` StartedAt *string `json:"startedAt"` CompletedAt *string `json:"completedAt"` } // WorkflowTemplate CRUD operations func workflowTemplateList(ctx context.Context, cmd *cli.Command) error { cfg, err := config.Load() if err != nil { return err } if err := RequireAuth(cfg); err != nil { return err } c := client.New(cfg.ServerURL) c.SetToken(cfg.Token) query := `query WorkflowTemplates { workflowTemplates { id name description definition isActive createdBy { id email } createdAt updatedAt } }` resp, err := c.Query(query, nil) if err != nil { return err } var result struct { WorkflowTemplates []WorkflowTemplate `json:"workflowTemplates"` } if err := json.Unmarshal(resp.Data, &result); err != nil { return err } if cmd.Bool("json") { enc := json.NewEncoder(os.Stdout) enc.SetIndent("", " ") return enc.Encode(result.WorkflowTemplates) } if len(result.WorkflowTemplates) == 0 { fmt.Println("No workflow templates found.") return nil } table := tablewriter.NewWriter(os.Stdout) table.Header([]string{"ID", "Name", "Description", "Active", "Created By", "Created At"}) for _, t := range result.WorkflowTemplates { desc := t.Description if len(desc) > 30 { desc = desc[:27] + "..." } createdBy := "" if t.CreatedBy != nil { createdBy = t.CreatedBy.Email } active := "yes" if !t.IsActive { active = "no" } table.Append([]string{t.ID, t.Name, desc, active, createdBy, t.CreatedAt}) } table.Render() return nil } func workflowTemplateGet(ctx context.Context, cmd *cli.Command) error { cfg, err := config.Load() if err != nil { return err } if err := RequireAuth(cfg); err != nil { return err } c := client.New(cfg.ServerURL) c.SetToken(cfg.Token) id := cmd.String("id") query := `query WorkflowTemplate($id: ID!) { workflowTemplate(id: $id) { id name description definition isActive createdBy { id email } createdAt updatedAt } }` resp, err := c.Query(query, map[string]interface{}{"id": id}) if err != nil { return err } var result struct { WorkflowTemplate *WorkflowTemplate `json:"workflowTemplate"` } if err := json.Unmarshal(resp.Data, &result); err != nil { return err } if result.WorkflowTemplate == nil { return fmt.Errorf("workflow template not found") } if cmd.Bool("json") { enc := json.NewEncoder(os.Stdout) enc.SetIndent("", " ") return enc.Encode(result.WorkflowTemplate) } t := result.WorkflowTemplate fmt.Printf("ID: %s\n", t.ID) fmt.Printf("Name: %s\n", t.Name) fmt.Printf("Description: %s\n", t.Description) fmt.Printf("Active: %v\n", t.IsActive) if t.CreatedBy != nil { fmt.Printf("Created By: %s\n", t.CreatedBy.Email) } fmt.Printf("Created At: %s\n", t.CreatedAt) fmt.Printf("Updated At: %s\n", t.UpdatedAt) fmt.Printf("\nDefinition:\n%s\n", t.Definition) return nil } func workflowTemplateCreate(ctx context.Context, cmd *cli.Command) error { cfg, err := config.Load() if err != nil { return err } if err := RequireAuth(cfg); err != nil { return err } name := cmd.String("name") description := cmd.String("description") definition := cmd.String("definition") isActive := cmd.Bool("active") if name == "" { prompt := &survey.Input{Message: "Template name:"} if err := survey.AskOne(prompt, &name, survey.WithValidator(survey.Required)); err != nil { return err } } if description == "" { prompt := &survey.Input{Message: "Description (optional):"} survey.AskOne(prompt, &description) } if definition == "" { prompt := &survey.Multiline{Message: "Workflow definition (JSON):"} if err := survey.AskOne(prompt, &definition, survey.WithValidator(survey.Required)); err != nil { return err } } // Handle file input for definition definition = ReadFileOrString(definition) c := client.New(cfg.ServerURL) c.SetToken(cfg.Token) mutation := `mutation CreateWorkflowTemplate($input: NewWorkflowTemplate!) { createWorkflowTemplate(input: $input) { id name description definition isActive createdBy { id email } createdAt updatedAt } }` input := map[string]interface{}{ "name": name, "description": description, "definition": definition, "isActive": isActive, } resp, err := c.Mutation(mutation, map[string]interface{}{"input": input}) if err != nil { return err } var result struct { CreateWorkflowTemplate *WorkflowTemplate `json:"createWorkflowTemplate"` } if err := json.Unmarshal(resp.Data, &result); err != nil { return err } if result.CreateWorkflowTemplate == nil { return fmt.Errorf("failed to create workflow template") } fmt.Printf("Workflow template created successfully!\n") fmt.Printf("ID: %s\n", result.CreateWorkflowTemplate.ID) fmt.Printf("Name: %s\n", result.CreateWorkflowTemplate.Name) return nil } func workflowTemplateUpdate(ctx context.Context, cmd *cli.Command) error { cfg, err := config.Load() if err != nil { return err } if err := RequireAuth(cfg); err != nil { return err } id := cmd.String("id") name := cmd.String("name") description := cmd.String("description") definition := cmd.String("definition") // Check if active flag was explicitly set var isActive *bool if cmd.IsSet("active") { val := cmd.Bool("active") isActive = &val } if name == "" && description == "" && definition == "" && isActive == nil { fmt.Println("No updates provided. Use flags to specify what to update.") return nil } c := client.New(cfg.ServerURL) c.SetToken(cfg.Token) input := make(map[string]interface{}) if name != "" { input["name"] = name } if description != "" { input["description"] = description } if definition != "" { input["definition"] = ReadFileOrString(definition) } if isActive != nil { input["isActive"] = *isActive } mutation := `mutation UpdateWorkflowTemplate($id: ID!, $input: UpdateWorkflowTemplateInput!) { updateWorkflowTemplate(id: $id, input: $input) { id name description definition isActive createdAt updatedAt } }` resp, err := c.Mutation(mutation, map[string]interface{}{"id": id, "input": input}) if err != nil { return err } var result struct { UpdateWorkflowTemplate *WorkflowTemplate `json:"updateWorkflowTemplate"` } if err := json.Unmarshal(resp.Data, &result); err != nil { return err } if result.UpdateWorkflowTemplate == nil { return fmt.Errorf("workflow template not found") } fmt.Printf("Workflow template updated successfully!\n") fmt.Printf("ID: %s\n", result.UpdateWorkflowTemplate.ID) fmt.Printf("Name: %s\n", result.UpdateWorkflowTemplate.Name) return nil } func workflowTemplateDelete(ctx context.Context, cmd *cli.Command) error { cfg, err := config.Load() if err != nil { return err } if err := RequireAuth(cfg); err != nil { return err } id := cmd.String("id") skipConfirm := cmd.Bool("yes") if !skipConfirm { confirm := false prompt := &survey.Confirm{ Message: fmt.Sprintf("Are you sure you want to delete workflow template %s?", id), Default: false, } if err := survey.AskOne(prompt, &confirm); err != nil { return err } if !confirm { fmt.Println("Deletion cancelled.") return nil } } c := client.New(cfg.ServerURL) c.SetToken(cfg.Token) mutation := `mutation DeleteWorkflowTemplate($id: ID!) { deleteWorkflowTemplate(id: $id) }` resp, err := c.Mutation(mutation, map[string]interface{}{"id": id}) if err != nil { return err } var result struct { DeleteWorkflowTemplate bool `json:"deleteWorkflowTemplate"` } if err := json.Unmarshal(resp.Data, &result); err != nil { return err } if result.DeleteWorkflowTemplate { fmt.Printf("Workflow template %s deleted successfully.\n", id) } else { fmt.Printf("Failed to delete workflow template %s.\n", id) } return nil } // WorkflowInstance operations func workflowInstanceList(ctx context.Context, cmd *cli.Command) error { cfg, err := config.Load() if err != nil { return err } if err := RequireAuth(cfg); err != nil { return err } c := client.New(cfg.ServerURL) c.SetToken(cfg.Token) query := `query WorkflowInstances { workflowInstances { id template { id name } status context service { id name } createdAt updatedAt completedAt } }` resp, err := c.Query(query, nil) if err != nil { return err } var result struct { WorkflowInstances []WorkflowInstance `json:"workflowInstances"` } if err := json.Unmarshal(resp.Data, &result); err != nil { return err } if cmd.Bool("json") { enc := json.NewEncoder(os.Stdout) enc.SetIndent("", " ") return enc.Encode(result.WorkflowInstances) } if len(result.WorkflowInstances) == 0 { fmt.Println("No workflow instances found.") return nil } table := tablewriter.NewWriter(os.Stdout) table.Header([]string{"ID", "Template", "Status", "Service", "Created At"}) for _, i := range result.WorkflowInstances { templateName := "" if i.Template != nil { templateName = i.Template.Name } serviceName := "" if i.Service != nil { serviceName = i.Service.Name } table.Append([]string{i.ID, templateName, i.Status, serviceName, i.CreatedAt}) } table.Render() return nil } func workflowInstanceGet(ctx context.Context, cmd *cli.Command) error { cfg, err := config.Load() if err != nil { return err } if err := RequireAuth(cfg); err != nil { return err } c := client.New(cfg.ServerURL) c.SetToken(cfg.Token) id := cmd.String("id") query := `query WorkflowInstance($id: ID!) { workflowInstance(id: $id) { id template { id name description } status context service { id name } createdAt updatedAt completedAt } }` resp, err := c.Query(query, map[string]interface{}{"id": id}) if err != nil { return err } var result struct { WorkflowInstance *WorkflowInstance `json:"workflowInstance"` } if err := json.Unmarshal(resp.Data, &result); err != nil { return err } if result.WorkflowInstance == nil { return fmt.Errorf("workflow instance not found") } if cmd.Bool("json") { enc := json.NewEncoder(os.Stdout) enc.SetIndent("", " ") return enc.Encode(result.WorkflowInstance) } i := result.WorkflowInstance fmt.Printf("ID: %s\n", i.ID) if i.Template != nil { fmt.Printf("Template: %s (%s)\n", i.Template.Name, i.Template.ID) } fmt.Printf("Status: %s\n", i.Status) if i.Service != nil { fmt.Printf("Service: %s (%s)\n", i.Service.Name, i.Service.ID) } fmt.Printf("Created At: %s\n", i.CreatedAt) fmt.Printf("Updated At: %s\n", i.UpdatedAt) if i.CompletedAt != nil { fmt.Printf("Completed At: %s\n", *i.CompletedAt) } if i.Context != "" { fmt.Printf("\nContext:\n%s\n", i.Context) } return nil } func workflowInstanceStart(ctx context.Context, cmd *cli.Command) error { cfg, err := config.Load() if err != nil { return err } if err := RequireAuth(cfg); err != nil { return err } templateID := cmd.String("template") serviceID := cmd.String("service") contextJSON := cmd.String("context") c := client.New(cfg.ServerURL) c.SetToken(cfg.Token) mutation := `mutation StartWorkflow($templateId: ID!, $input: StartWorkflowInput!) { startWorkflow(templateId: $templateId, input: $input) { id template { id name } status context service { id name } createdAt } }` input := make(map[string]interface{}) if serviceID != "" { input["serviceId"] = serviceID } if contextJSON != "" { input["context"] = contextJSON } resp, err := c.Mutation(mutation, map[string]interface{}{"templateId": templateID, "input": input}) if err != nil { return err } var result struct { StartWorkflow *WorkflowInstance `json:"startWorkflow"` } if err := json.Unmarshal(resp.Data, &result); err != nil { return err } if result.StartWorkflow == nil { return fmt.Errorf("failed to start workflow") } fmt.Printf("Workflow started successfully!\n") fmt.Printf("Instance ID: %s\n", result.StartWorkflow.ID) if result.StartWorkflow.Template != nil { fmt.Printf("Template: %s\n", result.StartWorkflow.Template.Name) } fmt.Printf("Status: %s\n", result.StartWorkflow.Status) return nil } func workflowInstanceCancel(ctx context.Context, cmd *cli.Command) error { cfg, err := config.Load() if err != nil { return err } if err := RequireAuth(cfg); err != nil { return err } id := cmd.String("id") c := client.New(cfg.ServerURL) c.SetToken(cfg.Token) mutation := `mutation CancelWorkflow($id: ID!) { cancelWorkflow(id: $id) { id status completedAt } }` resp, err := c.Mutation(mutation, map[string]interface{}{"id": id}) if err != nil { return err } var result struct { CancelWorkflow *WorkflowInstance `json:"cancelWorkflow"` } if err := json.Unmarshal(resp.Data, &result); err != nil { return err } if result.CancelWorkflow == nil { return fmt.Errorf("workflow instance not found") } fmt.Printf("Workflow cancelled successfully!\n") fmt.Printf("Instance ID: %s\n", result.CancelWorkflow.ID) fmt.Printf("Status: %s\n", result.CancelWorkflow.Status) return nil } // WorkflowNode operations func workflowNodeList(ctx context.Context, cmd *cli.Command) error { cfg, err := config.Load() if err != nil { return err } if err := RequireAuth(cfg); err != nil { return err } c := client.New(cfg.ServerURL) c.SetToken(cfg.Token) instanceID := cmd.String("instance") query := `query WorkflowInstance($id: ID!) { workflowInstance(id: $id) { id status nodes: workflowNodes { id nodeKey nodeType status task { id title } retryCount createdAt startedAt completedAt } } }` resp, err := c.Query(query, map[string]interface{}{"id": instanceID}) if err != nil { return err } var result struct { WorkflowInstance *struct { ID string `json:"id"` Status string `json:"status"` Nodes []WorkflowNode `json:"nodes"` } `json:"workflowInstance"` } if err := json.Unmarshal(resp.Data, &result); err != nil { return err } if result.WorkflowInstance == nil { return fmt.Errorf("workflow instance not found") } if cmd.Bool("json") { enc := json.NewEncoder(os.Stdout) enc.SetIndent("", " ") return enc.Encode(result.WorkflowInstance.Nodes) } if len(result.WorkflowInstance.Nodes) == 0 { fmt.Println("No nodes found for this workflow instance.") return nil } table := tablewriter.NewWriter(os.Stdout) table.Header([]string{"ID", "Key", "Type", "Status", "Task", "Retries"}) for _, n := range result.WorkflowInstance.Nodes { taskTitle := "" if n.Task != nil { taskTitle = n.Task.Title if len(taskTitle) > 30 { taskTitle = taskTitle[:27] + "..." } } table.Append([]string{n.ID, n.NodeKey, n.NodeType, n.Status, taskTitle, fmt.Sprintf("%d", n.RetryCount)}) } table.Render() return nil } func workflowNodeGet(ctx context.Context, cmd *cli.Command) error { cfg, err := config.Load() if err != nil { return err } if err := RequireAuth(cfg); err != nil { return err } c := client.New(cfg.ServerURL) c.SetToken(cfg.Token) id := cmd.String("id") query := `query WorkflowNode($id: ID!) { workflowNode(id: $id) { id nodeKey nodeType status task { id title } inputData outputData retryCount createdAt updatedAt startedAt completedAt } }` // Note: This assumes a workflowNode query exists. If not, we need to fetch via instance // For now, let's use a workaround by fetching the instance and finding the node query = `query WorkflowInstances { workflowInstances { id nodes: workflowNodes { id nodeKey nodeType status task { id title content } inputData outputData retryCount createdAt updatedAt startedAt completedAt } } }` resp, err := c.Query(query, nil) if err != nil { return err } var result struct { WorkflowInstances []struct { ID string `json:"id"` Nodes []WorkflowNode `json:"nodes"` } `json:"workflowInstances"` } if err := json.Unmarshal(resp.Data, &result); err != nil { return err } // Find the node var node *WorkflowNode for _, instance := range result.WorkflowInstances { for _, n := range instance.Nodes { if n.ID == id { node = &n break } } if node != nil { break } } if node == nil { return fmt.Errorf("workflow node not found") } if cmd.Bool("json") { enc := json.NewEncoder(os.Stdout) enc.SetIndent("", " ") return enc.Encode(node) } fmt.Printf("ID: %s\n", node.ID) fmt.Printf("Key: %s\n", node.NodeKey) fmt.Printf("Type: %s\n", node.NodeType) fmt.Printf("Status: %s\n", node.Status) fmt.Printf("Retry Count: %d\n", node.RetryCount) if node.Task != nil { fmt.Printf("Task: %s (%s)\n", node.Task.Title, node.Task.ID) } fmt.Printf("Created At: %s\n", node.CreatedAt) fmt.Printf("Updated At: %s\n", node.UpdatedAt) if node.StartedAt != nil { fmt.Printf("Started At: %s\n", *node.StartedAt) } if node.CompletedAt != nil { fmt.Printf("Completed At: %s\n", *node.CompletedAt) } if node.InputData != "" { fmt.Printf("\nInput Data:\n%s\n", node.InputData) } if node.OutputData != "" { fmt.Printf("\nOutput Data:\n%s\n", node.OutputData) } return nil } func workflowNodeRetry(ctx context.Context, cmd *cli.Command) error { cfg, err := config.Load() if err != nil { return err } if err := RequireAuth(cfg); err != nil { return err } id := cmd.String("id") c := client.New(cfg.ServerURL) c.SetToken(cfg.Token) mutation := `mutation RetryWorkflowNode($nodeId: ID!) { retryWorkflowNode(nodeId: $nodeId) { id nodeKey status retryCount } }` resp, err := c.Mutation(mutation, map[string]interface{}{"nodeId": id}) if err != nil { return err } var result struct { RetryWorkflowNode *WorkflowNode `json:"retryWorkflowNode"` } if err := json.Unmarshal(resp.Data, &result); err != nil { return err } if result.RetryWorkflowNode == nil { return fmt.Errorf("workflow node not found") } fmt.Printf("Workflow node retry initiated!\n") fmt.Printf("Node ID: %s\n", result.RetryWorkflowNode.ID) fmt.Printf("Key: %s\n", result.RetryWorkflowNode.NodeKey) fmt.Printf("Status: %s\n", result.RetryWorkflowNode.Status) fmt.Printf("Retry Count: %d\n", result.RetryWorkflowNode.RetryCount) return nil } // ReadFileOrString reads file content if input starts with @, otherwise returns as-is func ReadFileOrString(input string) string { if strings.HasPrefix(input, "@") { filename := input[1:] content, err := os.ReadFile(filename) if err != nil { return input // Return original if file can't be read } return string(content) } return input }