application

package
v1.0.69 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jul 13, 2026 License: MIT Imports: 7 Imported by: 0

Documentation

Overview

Package application provides shortcuts for Open Platform app self-management (slash commands of the current bound app).

Index

Constants

This section is empty.

Variables

View Source
var SlashCommandCreate = common.Shortcut{
	Service:     "application",
	Command:     "+slash-command-create",
	Description: "Register a slash command (/ command) on the current bound Open Platform app; --force converts a name collision into an update (idempotent re-run)",
	Risk:        "write",
	Scopes:      []string{"application:app_slash_command:write"},
	ConditionalScopes: []string{
		"application:app_slash_command:read",
	},
	AuthTypes: []string{"bot", "user"},
	Flags: []common.Flag{
		{Name: "command", Desc: "command name WITHOUT the leading slash (server enforces uniqueness per app; max 100 commands)", Required: true},
		{Name: "description", Desc: "default description shown in the client command panel (description.default_value)", Required: true},
		{Name: "description-i18n", Type: "string_array", Desc: "localized description, repeatable, format <lang>=<text> (e.g. zh_cn=发送问候); language codes are passed through to the server"},
		{Name: "icon-key", Desc: "icon key (server default: skill_outlined; invalid keys are rejected server-side with code 40000031)"},
		{Name: "force", Type: "bool", Desc: "on name collision, resolve the existing command by name and update it in place"},
	},
	Tips: []string{
		`lark-cli application +slash-command-create --command greet --description "say hi" --description-i18n zh_cn=问候 --as bot`,
		"changes take ~5 minutes to appear in clients (client-side cache); the server updates immediately",
		"user identity needs explicit authorization first: lark-cli auth login --scope application:app_slash_command:write",
	},
	Validate: func(ctx context.Context, runtime *common.RuntimeContext) error {
		if err := validateCommandName(runtime.Str("command"), "--command"); err != nil {
			return err
		}
		if len(strings.TrimSpace(runtime.Str("description"))) == 0 {
			return errs.NewValidationError(errs.SubtypeInvalidArgument,
				"--description must not be blank").WithParam("--description")
		}
		if _, err := parseDescriptionI18n(runtime.StrArray("description-i18n")); err != nil {
			return err
		}
		return nil
	},
	DryRun: func(ctx context.Context, runtime *common.RuntimeContext) *common.DryRunAPI {
		i18n, err := parseDescriptionI18n(runtime.StrArray("description-i18n"))
		if err != nil {

			return common.NewDryRunAPI().Set("error", err.Error())
		}
		name := strings.TrimSpace(runtime.Str("command"))
		body := buildSlashCommandBody(name, runtime.Str("description"), i18n, runtime.Str("icon-key"))
		d := common.NewDryRunAPI().
			Desc("Create a slash command on the current bound app").
			POST(slashCommandBasePath).
			Body(body)
		if runtime.Bool("force") {
			d.Desc("--force: on 'command already exists' (code 40000000), GET list to resolve command_id then PATCH the same body")
		}
		return d
	},
	Execute: func(ctx context.Context, runtime *common.RuntimeContext) error {
		name := strings.TrimSpace(runtime.Str("command"))
		i18n, err := parseDescriptionI18n(runtime.StrArray("description-i18n"))
		if err != nil {
			return err
		}
		body := buildSlashCommandBody(name, runtime.Str("description"), i18n, runtime.Str("icon-key"))

		data, err := runtime.CallAPITyped("POST", slashCommandBasePath, nil, body)
		action := "created"
		if err != nil {
			if !isCommandExists(err) {
				return err
			}
			if !runtime.Bool("force") {
				p, _ := errs.ProblemOf(err)
				rewrapped := errs.NewAPIError(errs.SubtypeAlreadyExists, "slash command %q already exists", name).
					WithHint("rerun with --force to update it, or use `lark-cli application +slash-command-update --command %q`", name).
					WithCause(err)
				if p.Code != 0 {
					rewrapped = rewrapped.WithCode(p.Code)
				}
				if p.LogID != "" {
					rewrapped = rewrapped.WithLogID(p.LogID)
				}
				return rewrapped
			}

			id, rerr := resolveCommandID(runtime, name)
			if rerr != nil {
				return rerr
			}
			patchBody := buildSlashCommandBody("", runtime.Str("description"), i18n, runtime.Str("icon-key"))
			data, err = runtime.CallAPITyped("PATCH", slashCommandBasePath+"/"+encodeCommandIDPathSegment(id), nil, patchBody)
			if err != nil {
				return err
			}
			action = "updated"
		}
		if data == nil {
			data = map[string]interface{}{}
		}
		data["action"] = action
		fmt.Fprintln(runtime.IO().ErrOut, clientCacheHint)
		runtime.OutFormat(data, nil, func(w io.Writer) {
			fmt.Fprintf(w, "%s /%v (command_id: %v)\n", action, data["command"], data["command_id"])
		})
		return nil
	},
}

SlashCommandCreate registers a new slash command on the current bound app.

View Source
var SlashCommandDelete = common.Shortcut{
	Service:     "application",
	Command:     "+slash-command-delete",
	Description: "Delete a slash command from the current bound app (high-risk: irreversible; recreating the same name yields a new command_id)",
	Risk:        "high-risk-write",
	Scopes:      []string{"application:app_slash_command:write"},
	ConditionalScopes: []string{
		"application:app_slash_command:read",
	},
	AuthTypes: []string{"bot", "user"},
	Flags: []common.Flag{
		{Name: "command-id", Desc: "target command_id; mutually exclusive with --command"},
		{Name: "command", Desc: "target command name WITHOUT leading slash (resolved via live list, needs read scope); mutually exclusive with --command-id"},
	},
	Tips: []string{
		"lark-cli application +slash-command-delete --command greet --yes --as bot",
		"deleted commands may linger in clients for ~5 minutes (client cache)",
	},
	Validate: func(ctx context.Context, runtime *common.RuntimeContext) error {
		id := strings.TrimSpace(runtime.Str("command-id"))
		name := strings.TrimSpace(runtime.Str("command"))
		if (id == "") == (name == "") {
			return errs.NewValidationError(errs.SubtypeInvalidArgument,
				"provide exactly one of --command-id or --command").WithParam("--command-id")
		}
		if name != "" {
			return validateCommandName(name, "--command")
		}
		return nil
	},
	DryRun: func(ctx context.Context, runtime *common.RuntimeContext) *common.DryRunAPI {
		d := common.NewDryRunAPI().Desc("HIGH-RISK: delete a slash command (irreversible; same-name recreate gets a NEW command_id)")
		target := strings.TrimSpace(runtime.Str("command-id"))
		if target == "" {
			name := strings.TrimSpace(runtime.Str("command"))
			d.GET(slashCommandBasePath).
				Desc(fmt.Sprintf("resolve command_id by name %q via GET list first", name))
			target = "<resolved_command_id>"
		} else {
			target = encodeCommandIDPathSegment(target)
		}
		return d.DELETE(slashCommandBasePath + "/" + target)
	},
	Execute: func(ctx context.Context, runtime *common.RuntimeContext) error {
		id := strings.TrimSpace(runtime.Str("command-id"))
		name := strings.TrimSpace(runtime.Str("command"))
		if id == "" {
			resolved, err := resolveCommandID(runtime, name)
			if err != nil {
				return err
			}
			id = resolved
		}
		if _, err := runtime.CallAPITyped("DELETE", slashCommandBasePath+"/"+encodeCommandIDPathSegment(id), nil, nil); err != nil {
			return err
		}
		out := map[string]interface{}{"action": "deleted", "command_id": id}
		if name != "" {
			out["command"] = name
		}
		fmt.Fprintln(runtime.IO().ErrOut, clientCacheHint)
		fmt.Fprintln(runtime.IO().ErrOut, "note: recreating the same command name will yield a NEW command_id.")
		runtime.OutFormat(out, nil, func(w io.Writer) {
			fmt.Fprintf(w, "deleted command_id %s\n", id)
		})
		return nil
	},
}

SlashCommandDelete removes a slash command (irreversible; command_id is not reused - recreating the same name yields a NEW id).

View Source
var SlashCommandList = common.Shortcut{
	Service:     "application",
	Command:     "+slash-command-list",
	Description: "List all slash commands (/ commands) registered on the currently bound Open Platform app; source of command_id for update/delete",
	Risk:        "read",
	Scopes:      []string{"application:app_slash_command:read"},
	AuthTypes:   []string{"bot", "user"},
	Tips: []string{
		"lark-cli application +slash-command-list --as bot",
		"user identity needs explicit authorization first: lark-cli auth login --scope application:app_slash_command:read",
		"the upstream API returns all commands at once (max 100 per app, no pagination)",
	},
	DryRun: func(ctx context.Context, runtime *common.RuntimeContext) *common.DryRunAPI {
		return common.NewDryRunAPI().
			Desc("List all slash commands of the current bound app (read-only)").
			GET(slashCommandBasePath)
	},
	Execute: func(ctx context.Context, runtime *common.RuntimeContext) error {
		data, err := runtime.CallAPITyped("GET", slashCommandBasePath, nil, nil)
		if err != nil {
			return err
		}
		items, _ := data["items"].([]interface{})
		if items == nil {
			items = []interface{}{}
		}
		out := map[string]interface{}{"items": items, "count": len(items)}
		runtime.OutFormat(out, nil, func(w io.Writer) {
			fmt.Fprintf(w, "%d slash command(s)\n", len(items))
			for _, it := range items {
				m, ok := it.(map[string]interface{})
				if !ok {
					continue
				}
				desc := ""
				if d, ok := m["description"].(map[string]interface{}); ok {
					desc, _ = d["default_value"].(string)
				}
				fmt.Fprintf(w, "  /%v\t%v\t%s\n", m["command"], m["command_id"], desc)
			}
		})
		return nil
	},
}

SlashCommandList lists all slash commands of the current bound app.

View Source
var SlashCommandUpdate = common.Shortcut{
	Service:     "application",
	Command:     "+slash-command-update",
	Description: "Update description / localized descriptions / icon of a slash command on the current bound app, addressed by --command-id or by name via --command",
	Risk:        "write",
	Scopes:      []string{"application:app_slash_command:write"},
	ConditionalScopes: []string{
		"application:app_slash_command:read",
	},
	AuthTypes: []string{"bot", "user"},
	Flags: []common.Flag{
		{Name: "command-id", Desc: "target command_id (from +slash-command-list or create output); mutually exclusive with --command"},
		{Name: "command", Desc: "target command name WITHOUT leading slash; resolved via live list (needs read scope); mutually exclusive with --command-id"},
		{Name: "description", Desc: "new default description (description.default_value)"},
		{Name: "description-i18n", Type: "string_array", Desc: "localized description, repeatable <lang>=<text>; REPLACES the whole i18n map (missing languages are dropped); requires --description"},
		{Name: "icon-key", Desc: "new icon key (invalid keys rejected server-side with code 40000031)"},
	},
	Tips: []string{
		`lark-cli application +slash-command-update --command greet --description "new text" --as bot`,
		"PATCH is field-level partial: fields you do not pass are preserved server-side",
		"the command NAME itself cannot be changed (API limitation): rename = delete + create (new command_id)",
	},
	Validate: func(ctx context.Context, runtime *common.RuntimeContext) error {
		return validateUpdateTarget(runtime)
	},
	DryRun: func(ctx context.Context, runtime *common.RuntimeContext) *common.DryRunAPI {
		i18n, err := parseDescriptionI18n(runtime.StrArray("description-i18n"))
		if err != nil {

			return common.NewDryRunAPI().Set("error", err.Error())
		}
		body := buildSlashCommandBody("", runtime.Str("description"), i18n, runtime.Str("icon-key"))
		d := common.NewDryRunAPI()
		target := strings.TrimSpace(runtime.Str("command-id"))
		if target == "" {
			name := strings.TrimSpace(runtime.Str("command"))
			d.GET(slashCommandBasePath).
				Desc(fmt.Sprintf("resolve command_id by name %q via GET list first", name))
			target = "<resolved_command_id>"
		} else {
			target = encodeCommandIDPathSegment(target)
		}
		return d.PATCH(slashCommandBasePath + "/" + target).
			Desc("Update a slash command by command_id").
			Body(body)
	},
	Execute: func(ctx context.Context, runtime *common.RuntimeContext) error {
		id := strings.TrimSpace(runtime.Str("command-id"))
		if id == "" {
			resolved, err := resolveCommandID(runtime, strings.TrimSpace(runtime.Str("command")))
			if err != nil {
				return err
			}
			id = resolved
		}
		i18n, err := parseDescriptionI18n(runtime.StrArray("description-i18n"))
		if err != nil {
			return err
		}
		body := buildSlashCommandBody("", runtime.Str("description"), i18n, runtime.Str("icon-key"))
		data, err := runtime.CallAPITyped("PATCH", slashCommandBasePath+"/"+encodeCommandIDPathSegment(id), nil, body)
		if err != nil {
			return err
		}
		if data == nil {
			data = map[string]interface{}{}
		}
		data["action"] = "updated"
		fmt.Fprintln(runtime.IO().ErrOut, clientCacheHint)
		runtime.OutFormat(data, nil, func(w io.Writer) {
			fmt.Fprintf(w, "updated /%v (command_id: %v)\n", data["command"], data["command_id"])
		})
		return nil
	},
}

SlashCommandUpdate updates description/i18n/icon of an existing slash command.

Functions

func Shortcuts

func Shortcuts() []common.Shortcut

Shortcuts returns all shortcuts of the application domain.

Types

This section is empty.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL