Files
agent-radar 2ed3415fb7
Publish Claude Code Binaries / Stage and publish binaries (push) Canceled after 0s
Publish Tutti App Runtime / Build darwin-arm64 (push) Canceled after 0s
Publish Tutti App Runtime / Build darwin-amd64 (push) Canceled after 0s
Publish Tutti App Runtime / Build linux-amd64 (push) Canceled after 0s
Publish Tutti App Runtime / Build windows-amd64 (push) Canceled after 0s
Sync Tutti Agent Skills / sync (push) Canceled after 0s
Windows Agent Adapters / Test Windows agent adapters (push) Canceled after 0s
Windows Daemon Adapters / Test Windows daemon adapters (push) Canceled after 0s
Publish Tutti App Runtime / Publish runtime catalog (push) Canceled after 0s
源码快照 @ 2026-09-01 (source-only, 剔除非源码素材)
2026-09-01 20:17:27 +08:00

838 lines
24 KiB
Go

package app
import (
"context"
"encoding/json"
"fmt"
"io"
"os"
"path/filepath"
"sort"
"strings"
"github.com/tutti-os/tutti/apps/cli/internal/daemon"
"github.com/tutti-os/tutti/apps/cli/internal/defaults"
)
const prefixHelpGroupPreviewLimit = 5
const dynamicStdinInputLimitBytes = 1024 * 1024
var dynamicInputReader = func() io.Reader { return os.Stdin }
type options struct {
json bool
}
func RunWithProgram(ctx context.Context, program string, args []string, stdout io.Writer, stderr io.Writer) int {
commandName := displayCommandName(program)
opts, rest, err := parseOptions(args)
if err != nil {
return writeCLIError(stdout, stderr, jsonRequested(args), "", reasonInvalidInput, err, 2)
}
if len(rest) == 0 || rest[0] == "help" || rest[0] == "--help" || rest[0] == "-h" {
return runHelp(ctx, commandName, stdout)
}
switch rest[0] {
case "status":
if len(rest) != 1 {
return writeCLIError(
stdout, stderr, opts.json, "", reasonInvalidInput,
fmt.Errorf("usage: %s status [--json]", commandName), 2,
)
}
return runStatus(ctx, commandName, opts, stdout, stderr)
case "managed-model":
return runManagedModel(ctx, commandName, opts, rest[1:], stdout, stderr)
default:
return runDynamic(ctx, commandName, opts, rest, stdout, stderr)
}
}
func displayCommandName(program string) string {
name := filepath.Base(strings.TrimSpace(program))
if name == "." || name == "" {
name = "tutti"
}
if strings.EqualFold(name, "tutti") && isDevelopmentCLI(program) {
return "tutti-dev"
}
return name
}
func isDevelopmentCLI(program string) bool {
cleanProgram := filepath.Clean(program)
devBuildSegment := "build" + string(filepath.Separator) + "dev" + string(filepath.Separator)
if strings.HasPrefix(cleanProgram, devBuildSegment) ||
strings.Contains(cleanProgram, string(filepath.Separator)+devBuildSegment) {
return true
}
return defaults.ResolveDefaultsFromEnv().Runtime.Env == "development"
}
func parseOptions(args []string) (options, []string, error) {
var opts options
rest := make([]string, 0, len(args))
for _, arg := range args {
switch arg {
case "--json":
opts.json = true
default:
rest = append(rest, arg)
}
}
return opts, rest, nil
}
func runStatus(ctx context.Context, commandName string, opts options, stdout io.Writer, stderr io.Writer) int {
client, err := discoverClient()
if err != nil {
return writeCLIError(stdout, stderr, opts.json, commandName+" status", reasonDaemonUnavailable, err, 1)
}
health, err := client.GetHealth(ctx)
if err != nil {
return writeCLIError(stdout, stderr, opts.json, commandName+" status", reasonDaemonRequestFailed, err, daemonErrorExitCode(err))
}
if opts.json {
return writeJSON(stdout, stderr, health)
}
fmt.Fprintf(stdout, "service: %s\nstatus: %s\n", health.Service, health.Status)
return 0
}
func runDynamic(ctx context.Context, commandName string, opts options, args []string, stdout io.Writer, stderr io.Writer) int {
invocationPrefix := strings.TrimSpace(commandName + " " + strings.Join(args, " "))
client, err := discoverClient()
if err != nil {
return writeCLIError(stdout, stderr, opts.json, invocationPrefix, reasonDaemonUnavailable, err, 1)
}
invokeContext := cliInvokeContextFromEnv()
capabilities, err := client.ListCapabilitiesForWorkspaceWithOptions(ctx, invokeContext.WorkspaceID, daemon.CapabilityListOptions{
IncludeIntegration: includeIntegrationCapabilitiesFromEnv(),
AgentSessionID: invokeContext.AgentSessionID,
})
if err != nil {
return writeCLIError(stdout, stderr, opts.json, invocationPrefix, reasonDaemonRequestFailed, err, daemonErrorExitCode(err))
}
command, commandArgs, ok := matchCapability(capabilities.Commands, args)
if !ok && legacyAgentCompatibilityInvocation(args) && !includeIntegrationCapabilitiesFromEnv() {
compatibilities, listErr := client.ListCapabilitiesForWorkspaceWithOptions(ctx, invokeContext.WorkspaceID, daemon.CapabilityListOptions{
IncludeIntegration: true,
AgentSessionID: invokeContext.AgentSessionID,
})
if listErr == nil {
command, commandArgs, ok = matchCapability(compatibilities.Commands, args)
}
}
if !ok {
if prefix, help := commandHelpPrefix(args); help {
if printCommandPrefixHelp(stdout, commandName, prefix, capabilities.Commands) {
return 0
}
}
if !opts.json && printCommandPrefixHelp(stdout, commandName, args, capabilities.Commands) {
return 2
}
return writeCLIError(
stdout, stderr, opts.json, "", reasonCommandNotFound,
fmt.Errorf("unknown command: %s", strings.Join(args, " ")), 2,
)
}
if isCommandHelpRequest(commandArgs) {
printDynamicCommandHelp(stdout, commandName, command)
return 0
}
waitOptions, commandArgs, err := parseWaitOptions(command, commandArgs)
if err != nil {
prefix := strings.TrimSpace(commandName + " " + strings.Join(command.Path, " "))
return writeCLIError(stdout, stderr, opts.json, prefix, reasonInvalidInput, err, 2)
}
input, err := parseCommandInput(command, commandArgs)
if err != nil {
prefix := strings.TrimSpace(commandName + " " + strings.Join(command.Path, " "))
return writeCLIError(stdout, stderr, opts.json, prefix, reasonInvalidInput, err, 2)
}
input, err = hydrateDynamicStdinInput(command, input)
if err != nil {
prefix := strings.TrimSpace(commandName + " " + strings.Join(command.Path, " "))
return writeCLIError(stdout, stderr, opts.json, prefix, reasonInvalidInput, err, 2)
}
outputMode := command.Output.DefaultMode
if opts.json {
outputMode = "json"
}
response, err := invokeDynamicCommand(ctx, client, command, daemon.InvokeRequest{
Input: input,
OutputMode: outputMode,
Context: invokeContext,
}, waitOptions)
if err != nil {
prefix := strings.TrimSpace(commandName + " " + strings.Join(command.Path, " "))
return writeCLIError(stdout, stderr, opts.json, prefix, reasonDaemonRequestFailed, err, daemonErrorExitCode(err))
}
if response.Output == nil {
return 0
}
if opts.json {
return writeDynamicJSON(stdout, stderr, *response.Output)
}
return writeCommandOutput(stdout, stderr, *response.Output)
}
func hydrateDynamicStdinInput(command daemon.Capability, input map[string]any) (map[string]any, error) {
value, _ := input["arguments-json"].(string)
if strings.TrimSpace(value) != "-" {
return input, nil
}
properties, _ := command.InputSchema["properties"].(map[string]any)
if properties["arguments-json"] == nil {
return nil, fmt.Errorf("--arguments-json - is not supported by this command")
}
content, err := io.ReadAll(io.LimitReader(dynamicInputReader(), dynamicStdinInputLimitBytes+1))
if err != nil {
return nil, fmt.Errorf("read standard input: %w", err)
}
if len(content) > dynamicStdinInputLimitBytes {
return nil, fmt.Errorf("standard input exceeds %d bytes", dynamicStdinInputLimitBytes)
}
if strings.TrimSpace(string(content)) == "" {
return nil, fmt.Errorf("standard input must contain one JSON object")
}
next := make(map[string]any, len(input))
for key, value := range input {
next[key] = value
}
next["arguments-json"] = string(content)
return next, nil
}
func legacyAgentCompatibilityInvocation(args []string) bool {
if len(args) < 2 {
return false
}
path := strings.Join(args[:2], " ")
return path == "agent providers" || path == "agent cancel" || path == "agent session-summary" ||
path == "codex start" || path == "claude start"
}
func runHelp(ctx context.Context, commandName string, stdout io.Writer) int {
var commands []daemon.Capability
client, err := discoverClient()
if err == nil {
invokeContext := cliInvokeContextFromEnv()
capabilities, listErr := client.ListCapabilitiesForWorkspaceWithOptions(ctx, invokeContext.WorkspaceID, daemon.CapabilityListOptions{
IncludeIntegration: includeIntegrationCapabilitiesFromEnv(),
AgentSessionID: invokeContext.AgentSessionID,
})
if listErr == nil {
commands = capabilities.Commands
}
}
printHelp(stdout, commandName, commands)
return 0
}
func includeIntegrationCapabilitiesFromEnv() bool {
return strings.TrimSpace(os.Getenv("TUTTI_APP_ID")) != "" &&
strings.TrimSpace(os.Getenv("TUTTI_CLI")) != ""
}
func cliInvokeContextFromEnv() daemon.InvokeContext {
return daemon.InvokeContext{
AppID: strings.TrimSpace(os.Getenv("TUTTI_APP_ID")),
Source: "cli",
WorkspaceID: strings.TrimSpace(os.Getenv("TUTTI_WORKSPACE_ID")),
ParentCommandID: strings.TrimSpace(os.Getenv("TUTTI_APP_CLI_PARENT_COMMAND_ID")),
AgentSessionID: strings.TrimSpace(os.Getenv("TUTTI_AGENT_SESSION_ID")),
AgentCWD: strings.TrimSpace(os.Getenv("TUTTI_AGENT_CWD")),
AgentRailPlacementJSON: strings.TrimSpace(
os.Getenv("TUTTI_AGENT_RAIL_PLACEMENT"),
),
}
}
func discoverClient() (*daemon.Client, error) {
endpoint, err := daemon.DiscoverEndpoint()
if err != nil {
return nil, err
}
client, err := daemon.NewClient(endpoint)
if err != nil {
return nil, err
}
return client, nil
}
func matchCapability(commands []daemon.Capability, args []string) (daemon.Capability, []string, bool) {
var matchedCommand daemon.Capability
var matchedArgs []string
matchedLength := -1
for _, command := range commands {
if len(args) < len(command.Path) {
continue
}
matched := true
for index, segment := range command.Path {
if args[index] != segment {
matched = false
break
}
}
if matched && len(command.Path) > matchedLength {
matchedCommand = command
matchedArgs = args[len(command.Path):]
matchedLength = len(command.Path)
}
}
if matchedLength >= 0 {
return matchedCommand, matchedArgs, true
}
return daemon.Capability{}, nil, false
}
func isCommandHelpRequest(args []string) bool {
return len(args) == 1 && (args[0] == "--help" || args[0] == "-h")
}
func commandHelpPrefix(args []string) ([]string, bool) {
if len(args) == 0 {
return nil, false
}
last := args[len(args)-1]
if last != "--help" && last != "-h" {
return nil, false
}
prefix := args[:len(args)-1]
if len(prefix) == 0 {
return nil, false
}
return prefix, true
}
func writeCommandOutput(stdout io.Writer, stderr io.Writer, output daemon.CommandOutput) int {
switch output.Kind {
case "plain", "markdown":
writeCommandWarnings(stderr, output.Warnings)
fmt.Fprintln(stdout, output.Text)
return 0
case "table":
writeCommandWarnings(stderr, output.Warnings)
writeTable(stdout, output.Columns, output.Rows)
return 0
case "json":
return writeDynamicJSON(stdout, stderr, output)
default:
return writeJSON(stdout, stderr, output)
}
}
func writeDynamicJSON(stdout io.Writer, stderr io.Writer, output daemon.CommandOutput) int {
if len(output.Warnings) > 0 {
envelope := map[string]any{
"warnings": output.Warnings,
}
if len(output.Value) > 0 {
envelope["value"] = output.Value
} else if output.Rows != nil {
envelope["rows"] = output.Rows
} else {
envelope["output"] = output
}
return writeJSON(stdout, stderr, envelope)
}
if len(output.Value) > 0 {
return writeJSON(stdout, stderr, output.Value)
}
if output.Rows != nil {
return writeJSON(stdout, stderr, output.Rows)
}
return writeJSON(stdout, stderr, output)
}
func writeCommandWarnings(stderr io.Writer, warnings []daemon.CommandWarning) {
for _, warning := range warnings {
message := strings.TrimSpace(warning.Message)
if message == "" {
message = strings.TrimSpace(warning.Code)
}
if message != "" {
fmt.Fprintf(stderr, "warning: %s\n", message)
}
}
}
func writeJSON(stdout io.Writer, stderr io.Writer, value any) int {
encoded, err := json.MarshalIndent(value, "", " ")
if err != nil {
fmt.Fprintf(stderr, "encode output: %v\n", err)
return 1
}
fmt.Fprintln(stdout, string(encoded))
return 0
}
func writeTable(stdout io.Writer, columns []daemon.TableColumn, rows []map[string]any) {
if len(columns) == 0 {
for _, row := range rows {
fmt.Fprintln(stdout, row)
}
return
}
widths := make([]int, len(columns))
for index, column := range columns {
widths[index] = len(column.Label)
for _, row := range rows {
width := len(fmt.Sprint(row[column.Key]))
if width > widths[index] {
widths[index] = width
}
}
}
for index, column := range columns {
if index > 0 {
fmt.Fprint(stdout, " ")
}
fmt.Fprintf(stdout, "%-*s", widths[index], column.Label)
}
fmt.Fprintln(stdout)
for index := range columns {
if index > 0 {
fmt.Fprint(stdout, " ")
}
fmt.Fprint(stdout, strings.Repeat("-", widths[index]))
}
fmt.Fprintln(stdout)
for _, row := range rows {
for index, column := range columns {
if index > 0 {
fmt.Fprint(stdout, " ")
}
fmt.Fprintf(stdout, "%-*s", widths[index], fmt.Sprint(row[column.Key]))
}
fmt.Fprintln(stdout)
}
}
func printHelp(stdout io.Writer, commandName string, commands []daemon.Capability) {
fmt.Fprintf(stdout, "Usage: %s [--json] <command>\n", commandName)
fmt.Fprintln(stdout)
fmt.Fprintln(stdout, "Commands:")
rows := []helpCommandRow{{Name: "status", Summary: "Show local tuttid status"}}
rows = append(rows, rootHelpRows(commands)...)
printHelpRows(stdout, rows)
printIntegrationOnlyNotice(stdout, commands)
}
func printCommandPrefixHelp(stdout io.Writer, commandName string, prefix []string, commands []daemon.Capability) bool {
matches := matchingPrefixCommands(commands, prefix)
if len(matches) == 0 {
return false
}
fmt.Fprintf(stdout, "Usage: %s %s <command>\n", commandName, strings.Join(prefix, " "))
fmt.Fprintln(stdout)
fmt.Fprintln(stdout, "Commands:")
printHelpRows(stdout, prefixHelpRows(prefix, matches))
printIntegrationOnlyNotice(stdout, matches)
fmt.Fprintln(stdout)
fmt.Fprintf(stdout, "Use \"%s %s <command> --help\" for command details.\n", commandName, strings.Join(prefix, " "))
printDocumentationHints(stdout, matches)
return true
}
func printDynamicCommandHelp(stdout io.Writer, commandName string, command daemon.Capability) {
flags := waitCommandFlags(command, commandFlags(command.InputSchema))
fmt.Fprintf(stdout, "Usage: %s %s", commandName, strings.Join(command.Path, " "))
if command.Output.JSON || command.Output.DefaultMode == "json" {
fmt.Fprint(stdout, " [--json]")
}
for _, flag := range flags {
if flag.Required {
fmt.Fprintf(stdout, " --%s <value>", flag.Name)
continue
}
fmt.Fprintf(stdout, " [--%s <value>]", flag.Name)
}
fmt.Fprintln(stdout)
fmt.Fprintln(stdout)
if strings.TrimSpace(command.Summary) != "" {
fmt.Fprintln(stdout, command.Summary)
}
if strings.TrimSpace(command.Description) != "" && command.Description != command.Summary {
if strings.TrimSpace(command.Summary) != "" {
fmt.Fprintln(stdout)
}
fmt.Fprintln(stdout, command.Description)
}
printIntegrationOnlyNotice(stdout, []daemon.Capability{command})
if len(flags) == 0 {
printDocumentationHints(stdout, []daemon.Capability{command})
return
}
fmt.Fprintln(stdout)
fmt.Fprintln(stdout, "Flags:")
width := 0
for _, flag := range flags {
if len(flag.Name) > width {
width = len(flag.Name)
}
}
for _, flag := range flags {
required := ""
if flag.Required {
required = " required"
}
description := ""
if flag.Description != "" {
description = " " + flag.Description
}
fmt.Fprintf(stdout, " --%-*s %s%s%s\n", width, flag.Name, flag.Type, required, description)
if len(flag.Values) > 0 {
fmt.Fprintf(stdout, " Values: %s\n", strings.Join(flag.Values, ", "))
}
if flag.HasDefault {
fmt.Fprintf(stdout, " Default: %s\n", flag.Default)
}
}
printDocumentationHints(stdout, []daemon.Capability{command})
}
type helpCommandRow struct {
Name string
Summary string
IntegrationOnly bool
IncludesIntegrationOnly bool
Children []helpCommandRow
RemainingChildren int
}
func rootHelpRows(commands []daemon.Capability) []helpCommandRow {
type group struct {
count int
integrationCount int
summary string
description string
}
groups := map[string]group{}
for _, command := range commands {
if len(command.Path) == 0 {
continue
}
name := command.Path[0]
if name == "status" {
continue
}
current := groups[name]
current.count++
if isIntegrationCapability(command) {
current.integrationCount++
}
if len(command.Path) == 1 && current.summary == "" {
current.summary = command.Summary
}
if current.description == "" {
current.description = commandScopeDescription(command)
}
groups[name] = current
}
names := make([]string, 0, len(groups))
for name := range groups {
names = append(names, name)
}
sort.Strings(names)
rows := make([]helpCommandRow, 0, len(names))
for _, name := range names {
item := groups[name]
summary := item.summary
if summary == "" {
summary = fmt.Sprintf("%d commands", item.count)
}
if item.description != "" && item.count > 0 {
summary = fmt.Sprintf("%s %d commands", item.description, item.count)
}
rows = append(rows, helpCommandRow{
Name: name,
Summary: summary,
IntegrationOnly: item.count > 0 && item.integrationCount == item.count,
IncludesIntegrationOnly: item.integrationCount > 0 && item.integrationCount < item.count,
})
}
return rows
}
func commandScopeDescription(command daemon.Capability) string {
description := strings.TrimSpace(command.Source.CLIDescription)
if description != "" {
return description
}
return strings.TrimSpace(command.Source.AppDescription)
}
func matchingPrefixCommands(commands []daemon.Capability, prefix []string) []daemon.Capability {
result := make([]daemon.Capability, 0)
for _, command := range commands {
if len(command.Path) <= len(prefix) {
continue
}
matched := true
for index, segment := range prefix {
if command.Path[index] != segment {
matched = false
break
}
}
if matched {
result = append(result, command)
}
}
sort.SliceStable(result, func(left, right int) bool {
return strings.Join(result[left].Path, " ") < strings.Join(result[right].Path, " ")
})
return result
}
func prefixHelpRows(prefix []string, commands []daemon.Capability) []helpCommandRow {
type group struct {
count int
integrationCount int
summary string
}
groups := map[string]group{}
for _, command := range commands {
if len(command.Path) <= len(prefix) {
continue
}
remaining := command.Path[len(prefix):]
name := remaining[0]
current := groups[name]
current.count++
if isIntegrationCapability(command) {
current.integrationCount++
}
if len(remaining) == 1 && current.summary == "" {
current.summary = command.Summary
}
groups[name] = current
}
names := make([]string, 0, len(groups))
for name := range groups {
names = append(names, name)
}
sort.Strings(names)
rows := make([]helpCommandRow, 0, len(names))
for _, name := range names {
item := groups[name]
summary := item.summary
if summary == "" {
summary = fmt.Sprintf("%d commands", item.count)
}
if item.count == 1 {
if required := requiredFlagSummaryForPrefixCommand(prefix, name, commands); required != "" {
summary = summary + " required: " + required
}
}
rows = append(rows, helpCommandRow{
Name: name,
Summary: summary,
IntegrationOnly: item.count > 0 && item.integrationCount == item.count,
IncludesIntegrationOnly: item.integrationCount > 0 && item.integrationCount < item.count,
Children: prefixHelpChildRows(prefix, name, commands, prefixHelpGroupPreviewLimit),
RemainingChildren: prefixHelpRemainingChildCount(prefix, name, commands, prefixHelpGroupPreviewLimit),
})
}
return rows
}
func prefixHelpChildRows(prefix []string, name string, commands []daemon.Capability, limit int) []helpCommandRow {
if limit <= 0 {
return nil
}
childPrefix := append(append([]string(nil), prefix...), name)
leaves := make([]daemon.Capability, 0)
for _, command := range commands {
if len(command.Path) != len(childPrefix)+1 {
continue
}
if !pathHasPrefix(command.Path, childPrefix) {
continue
}
leaves = append(leaves, command)
}
sort.SliceStable(leaves, func(left, right int) bool {
return strings.Join(leaves[left].Path, " ") < strings.Join(leaves[right].Path, " ")
})
if len(leaves) > limit {
leaves = leaves[:limit]
}
rows := make([]helpCommandRow, 0, len(leaves))
for _, command := range leaves {
childName := command.Path[len(childPrefix)]
summary := strings.TrimSpace(command.Summary)
if required := requiredFlagSummary(command); required != "" {
summary = summary + " required: " + required
}
rows = append(rows, helpCommandRow{
Name: childName,
Summary: summary,
IntegrationOnly: isIntegrationCapability(command),
})
}
return rows
}
func prefixHelpRemainingChildCount(prefix []string, name string, commands []daemon.Capability, limit int) int {
if limit <= 0 {
return 0
}
childPrefix := append(append([]string(nil), prefix...), name)
count := 0
for _, command := range commands {
if len(command.Path) == len(childPrefix)+1 && pathHasPrefix(command.Path, childPrefix) {
count++
}
}
if count <= limit {
return 0
}
return count - limit
}
func pathHasPrefix(path []string, prefix []string) bool {
if len(path) < len(prefix) {
return false
}
for index, segment := range prefix {
if path[index] != segment {
return false
}
}
return true
}
func requiredFlagSummaryForPrefixCommand(prefix []string, name string, commands []daemon.Capability) string {
for _, command := range commands {
if len(command.Path) != len(prefix)+1 {
continue
}
if command.Path[len(prefix)] != name {
continue
}
return requiredFlagSummary(command)
}
return ""
}
func requiredFlagSummary(command daemon.Capability) string {
flags := commandFlags(command.InputSchema)
required := make([]string, 0)
for _, flag := range flags {
if flag.Required {
required = append(required, "--"+flag.Name+" <value>")
}
}
return strings.Join(required, " ")
}
func printHelpRows(stdout io.Writer, rows []helpCommandRow) {
width := 0
for _, row := range rows {
if len(row.Name) > width {
width = len(row.Name)
}
}
for _, row := range rows {
summary := row.Summary
switch {
case row.IntegrationOnly:
summary += " [integration-only]"
case row.IncludesIntegrationOnly:
summary += " [includes integration-only]"
}
fmt.Fprintf(stdout, " %-*s %s\n", width, row.Name, summary)
printHelpChildRows(stdout, row)
}
}
func printHelpChildRows(stdout io.Writer, row helpCommandRow) {
if len(row.Children) == 0 && row.RemainingChildren == 0 {
return
}
childWidth := 0
for _, child := range row.Children {
if len(child.Name) > childWidth {
childWidth = len(child.Name)
}
}
for _, child := range row.Children {
summary := child.Summary
if child.IntegrationOnly {
summary += " [integration-only]"
}
fmt.Fprintf(stdout, " %-*s %s\n", childWidth, child.Name, summary)
}
if row.RemainingChildren > 0 {
fmt.Fprintf(stdout, " ... %d more commands\n", row.RemainingChildren)
}
}
func printIntegrationOnlyNotice(stdout io.Writer, commands []daemon.Capability) {
if !hasIntegrationCapability(commands) {
return
}
fmt.Fprintln(stdout)
fmt.Fprintln(stdout, "Integration-only commands are app-runtime plumbing. Do not expose or forward them as ordinary user or Agent actions; prefer public app commands.")
}
func hasIntegrationCapability(commands []daemon.Capability) bool {
for _, command := range commands {
if isIntegrationCapability(command) {
return true
}
}
return false
}
func isIntegrationCapability(command daemon.Capability) bool {
return strings.TrimSpace(command.Visibility) == "integration"
}
func printDocumentationHints(stdout io.Writer, commands []daemon.Capability) {
hints := documentationHints(commands)
if len(hints) == 0 {
return
}
fmt.Fprintln(stdout)
fmt.Fprintln(stdout, "More documentation:")
for _, hint := range hints {
fmt.Fprintf(stdout, " %s\n", hint.File)
}
}
type documentationHint struct {
File string
}
func documentationHints(commands []daemon.Capability) []documentationHint {
seen := map[string]struct{}{}
hints := make([]documentationHint, 0)
for _, command := range commands {
file := strings.TrimSpace(command.Source.DocumentationPath)
if file == "" {
file = strings.TrimSpace(command.Source.DocumentationFile)
}
if file == "" {
continue
}
if _, ok := seen[file]; ok {
continue
}
seen[file] = struct{}{}
hints = append(hints, documentationHint{File: file})
}
sort.SliceStable(hints, func(left, right int) bool {
return hints[left].File < hints[right].File
})
return hints
}