base

package
v1.0.58 Latest Latest
Warning

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

Go to latest
Published: Jun 25, 2026 License: MIT Imports: 31 Imported by: 0

Documentation

Index

Constants

This section is empty.

Variables

View Source
var BaseAdvpermDisable = common.Shortcut{
	Service:     "base",
	Command:     "+advperm-disable",
	Description: "Disable advanced permissions for a Base",
	Risk:        "high-risk-write",
	Scopes:      []string{"base:app:update"},
	AuthTypes:   []string{"user", "bot"},
	Flags: []common.Flag{
		{Name: "base-token", Desc: "base token", Required: true},
	},
	Tips: []string{
		baseHighRiskYesTip,
		"Disabling advanced permissions invalidates existing custom roles; confirm the target Base before passing --yes.",
	},
	Validate: func(ctx context.Context, runtime *common.RuntimeContext) error {
		if strings.TrimSpace(runtime.Str("base-token")) == "" {
			return baseFlagErrorf("--base-token must not be blank")
		}
		return nil
	},
	DryRun: func(ctx context.Context, runtime *common.RuntimeContext) *common.DryRunAPI {
		return common.NewDryRunAPI().
			PUT("/open-apis/base/v3/bases/:base_token/advperm/enable?enable=false").
			Set("base_token", runtime.Str("base-token"))
	},
	Execute: func(ctx context.Context, runtime *common.RuntimeContext) error {
		baseToken := runtime.Str("base-token")

		queryParams := make(larkcore.QueryParams)
		queryParams.Set("enable", "false")

		apiResp, err := runtime.DoAPI(&larkcore.ApiReq{
			HttpMethod:  http.MethodPut,
			ApiPath:     fmt.Sprintf("/open-apis/base/v3/bases/%s/advperm/enable", validate.EncodePathSegment(baseToken)),
			QueryParams: queryParams,
		})
		if err != nil {
			return err
		}

		return handleRoleAPIResponse(runtime, apiResp, "disable advanced permissions failed")
	},
}
View Source
var BaseAdvpermEnable = common.Shortcut{
	Service:     "base",
	Command:     "+advperm-enable",
	Description: "Enable advanced permissions for a Base",
	Risk:        "write",
	Scopes:      []string{"base:app:update"},
	AuthTypes:   []string{"user", "bot"},
	Flags: []common.Flag{
		{Name: "base-token", Desc: "base token", Required: true},
	},
	Tips: []string{
		"Caller must be a Base admin; enable advanced permissions before creating or updating roles.",
	},
	Validate: func(ctx context.Context, runtime *common.RuntimeContext) error {
		if strings.TrimSpace(runtime.Str("base-token")) == "" {
			return baseFlagErrorf("--base-token must not be blank")
		}
		return nil
	},
	DryRun: func(ctx context.Context, runtime *common.RuntimeContext) *common.DryRunAPI {
		return common.NewDryRunAPI().
			PUT("/open-apis/base/v3/bases/:base_token/advperm/enable?enable=true").
			Set("base_token", runtime.Str("base-token"))
	},
	Execute: func(ctx context.Context, runtime *common.RuntimeContext) error {
		baseToken := runtime.Str("base-token")

		queryParams := make(larkcore.QueryParams)
		queryParams.Set("enable", "true")

		apiResp, err := runtime.DoAPI(&larkcore.ApiReq{
			HttpMethod:  http.MethodPut,
			ApiPath:     fmt.Sprintf("/open-apis/base/v3/bases/%s/advperm/enable", validate.EncodePathSegment(baseToken)),
			QueryParams: queryParams,
		})
		if err != nil {
			return err
		}

		return handleRoleAPIResponse(runtime, apiResp, "enable advanced permissions failed")
	},
}
View Source
var BaseBaseBlockCreate = common.Shortcut{
	Service:     "base",
	Command:     "+base-block-create",
	Description: "Create a block",
	Risk:        "write",
	Scopes:      []string{"base:block:create"},
	AuthTypes:   authTypes(),
	Flags: []common.Flag{
		baseTokenFlag(true),
		{Name: "type", Desc: "resource type", Required: true, Enum: baseBlockTypeEnums},
		{Name: "name", Desc: "block name", Required: true},
		{Name: "parent-id", Desc: "folder block id; when omitted, create at root"},
	},
	Tips: []string{
		"Example: lark-cli base +base-block-create --base-token <base_token> --type folder --name \"Project Docs\"",
		"Example: lark-cli base +base-block-create --base-token <base_token> --type table --name \"Tasks\"",
		"Example: lark-cli base +base-block-create --base-token <base_token> --type docx --name \"Spec\" --parent-id <folder_block_id>",
		"Example: lark-cli base +base-block-create --base-token <base_token> --type dashboard --name \"Metrics\"",
		"Example: lark-cli base +base-block-create --base-token <base_token> --type workflow --name \"Approval Flow\"",
		"Creates a folder, table, docx, dashboard, or workflow entry.",
		"Do not pass null for --parent-id. Omit it to create at the root level.",
		"Created resources still use their own commands for content operations, such as table/field/record/docx/dashboard/workflow commands.",
	},
	Validate: func(ctx context.Context, runtime *common.RuntimeContext) error {
		return validateBaseBlockCreate(runtime)
	},
	DryRun: dryRunBaseBlockCreate,
	Execute: func(ctx context.Context, runtime *common.RuntimeContext) error {
		return executeBaseBlockCreate(runtime)
	},
}
View Source
var BaseBaseBlockDelete = common.Shortcut{
	Service:     "base",
	Command:     "+base-block-delete",
	Description: "Delete a block",
	Risk:        "high-risk-write",
	Scopes:      []string{"base:block:delete"},
	AuthTypes:   authTypes(),
	Flags: []common.Flag{
		baseTokenFlag(true),
		baseBlockIDFlag(true),
	},
	Tips: []string{
		"Example: lark-cli base +base-block-delete --base-token <base_token> --block-id <block_id> --yes",
		"Deletes the block identified by --block-id.",
		"Recursive folder deletion is not supported. If a folder is not empty, move or delete its children first.",
		"Different block types may have independent backing resources; deletion follows backend semantics.",
		"Use +base-block-list first when you need to confirm the target block id.",
		"If the user already explicitly confirmed this exact delete target, pass --yes without asking again.",
	},
	DryRun: dryRunBaseBlockDelete,
	Execute: func(ctx context.Context, runtime *common.RuntimeContext) error {
		return executeBaseBlockDelete(runtime)
	},
}
View Source
var BaseBaseBlockList = common.Shortcut{
	Service:     "base",
	Command:     "+base-block-list",
	Description: "List blocks in a base",
	Risk:        "read",
	Scopes:      []string{"base:block:read"},
	AuthTypes:   authTypes(),
	Flags: []common.Flag{
		baseTokenFlag(true),
		{Name: "type", Desc: "filter by resource type", Enum: baseBlockTypeEnums},
		{Name: "parent-id", Desc: "folder block id; when omitted, list all blocks"},
	},
	Tips: []string{
		"Example: lark-cli base +base-block-list --base-token <base_token>",
		"Example: lark-cli base +base-block-list --base-token <base_token> --type table",
		"Example: lark-cli base +base-block-list --base-token <base_token> --parent-id <folder_block_id>",
		`JQ crop: lark-cli base +base-block-list --base-token <base_token> | jq '.blocks[] | {type, name, block_id: .id, parent_id}'`,
		`JQ crop docx: lark-cli base +base-block-list --base-token <base_token> --type docx | jq '.blocks[] | {name, docx_token}'`,
		"Blocks are resources managed directly by the base, such as folder, table, docx, dashboard, and workflow.",
		"For table, dashboard, and workflow blocks, returned id is the table-id, dashboard-id, or workflow-id used by the corresponding commands.",
		"For docx blocks, use the returned docx_token with docx commands.",
		"For folder blocks, pass the returned id as --parent-id when creating, listing, or moving blocks inside that folder.",
		"This command returns the full backend list. It intentionally does not expose limit or offset.",
		"Pass --type to list only one resource type.",
		"Pass --parent-id to list only direct children of a folder.",
		"Dashboard blocks are chart/widget blocks inside a dashboard; use +dashboard-block-* for those.",
	},
	DryRun: dryRunBaseBlockList,
	Execute: func(ctx context.Context, runtime *common.RuntimeContext) error {
		return executeBaseBlockList(runtime)
	},
}
View Source
var BaseBaseBlockMove = common.Shortcut{
	Service:     "base",
	Command:     "+base-block-move",
	Description: "Move a block",
	Risk:        "write",
	Scopes:      []string{"base:block:update"},
	AuthTypes:   authTypes(),
	Flags: []common.Flag{
		baseTokenFlag(true),
		baseBlockIDFlag(true),
		{Name: "parent-id", Desc: "target folder block id; when omitted, move to root"},
		{Name: "before-id", Desc: "sibling block id; move the block before this sibling in the target folder/root order"},
		{Name: "after-id", Desc: "sibling block id; move the block after this sibling in the target folder/root order"},
	},
	Tips: []string{
		"Example: lark-cli base +base-block-move --base-token <base_token> --block-id <block_id> --parent-id <folder_block_id>",
		"Example: lark-cli base +base-block-move --base-token <base_token> --block-id <block_id> --after-id <sibling_block_id>",
		"Example: lark-cli base +base-block-move --base-token <base_token> --block-id <block_id> --before-id <sibling_block_id>",
		"Example: lark-cli base +base-block-move --base-token <base_token> --block-id <block_id>",
		"Omit --parent-id to move the block to root; do not pass null.",
		"--before-id and --after-id are mutually exclusive.",
		"When moving a folder, its children remain under that folder.",
	},
	Validate: func(ctx context.Context, runtime *common.RuntimeContext) error {
		return validateBaseBlockMove(runtime)
	},
	DryRun: dryRunBaseBlockMove,
	Execute: func(ctx context.Context, runtime *common.RuntimeContext) error {
		return executeBaseBlockMove(runtime)
	},
}
View Source
var BaseBaseBlockRename = common.Shortcut{
	Service:     "base",
	Command:     "+base-block-rename",
	Description: "Rename a block",
	Risk:        "write",
	Scopes:      []string{"base:block:update"},
	AuthTypes:   authTypes(),
	Flags: []common.Flag{
		baseTokenFlag(true),
		baseBlockIDFlag(true),
		{Name: "name", Desc: "new unique block name; must not duplicate another block name in this base", Required: true},
	},
	Tips: []string{
		"Example: lark-cli base +base-block-rename --base-token <base_token> --block-id <block_id> --name \"New name\"",
		"Renames the block identified by --block-id.",
		"Block names must be unique in the base; use +base-block-list first when you need to check existing names.",
		"Use +base-block-list first when you need to resolve the target block id from a visible name.",
	},
	Validate: func(ctx context.Context, runtime *common.RuntimeContext) error {
		return validateBaseBlockRename(runtime)
	},
	DryRun: dryRunBaseBlockRename,
	Execute: func(ctx context.Context, runtime *common.RuntimeContext) error {
		return executeBaseBlockRename(runtime)
	},
}
View Source
var BaseBaseCopy = common.Shortcut{
	Service:     "base",
	Command:     "+base-copy",
	Description: "Copy a base resource",
	Risk:        "write",
	UserScopes:  []string{"base:app:copy"},
	BotScopes:   []string{"base:app:copy", "docs:permission.member:create"},
	AuthTypes:   authTypes(),
	Flags: []common.Flag{
		baseTokenFlag(true),
		{Name: "name", Desc: "new base name"},
		{Name: "folder-token", Desc: "folder token for destination"},
		{Name: "without-content", Type: "bool", Desc: "copy structure only"},
		{Name: "time-zone", Desc: "time zone, e.g. Asia/Shanghai"},
	},
	Tips: []string{
		`Example: lark-cli base +base-copy --base-token <base_token> --name "Copy of Project Tracker"`,
		"Use --without-content when the user wants only structure.",
		"If copied as bot, output may include permission_grant; report it so the user knows whether they can open the new Base.",
	},
	DryRun: dryRunBaseCopy,
	Execute: func(ctx context.Context, runtime *common.RuntimeContext) error {
		return executeBaseCopy(runtime)
	},
}
View Source
var BaseBaseCreate = common.Shortcut{
	Service:     "base",
	Command:     "+base-create",
	Description: "Create a new base resource",
	Risk:        "write",
	UserScopes: []string{
		"base:app:create",
		"base:table:read",
		"base:table:create",
		"base:table:update",
		"base:table:delete",
	},
	BotScopes: []string{
		"base:app:create",
		"base:table:read",
		"base:table:create",
		"base:table:update",
		"base:table:delete",
		"docs:permission.member:create",
	},
	AuthTypes: authTypes(),
	Flags: []common.Flag{
		{Name: "name", Desc: "base name", Required: true},
		{Name: "folder-token", Desc: "folder token for destination"},
		{Name: "time-zone", Desc: "time zone, e.g. Asia/Shanghai"},
		{Name: "fields", Desc: `field JSON array for the first table schema; use with --table-name, e.g. [{"name":"Title","type":"text"},{"name":"Status","type":"select","options":[{"name":"Todo"},{"name":"Done"}]}]`},
		{Name: "table-name", Desc: "first table name for the custom first table schema; use with --fields"},
	},
	Tips: []string{
		`Example: lark-cli base +base-create --name "Project Tracker" --time-zone Asia/Shanghai`,
		`Strongly recommended initial table schema: lark-cli base +base-create --name "Project Tracker" --table-name "Tasks" --fields '[{"name":"Title","type":"text"},{"name":"Status","type":"select","options":[{"name":"Todo"},{"name":"Done"}]}]'`,
		"Before using --fields, read lark-base-field-json.md or rely on the same field JSON shape used by +field-create; do not invent field properties.",
		"If --table-name and --fields are both omitted, Base creates one initial table with the platform default schema.",
		"If created as bot, output may include permission_grant; report it so the user knows whether they can open the new Base.",
	},
	Validate: func(ctx context.Context, runtime *common.RuntimeContext) error {
		return validateBaseCreate(runtime)
	},
	DryRun: dryRunBaseCreate,
	Execute: func(ctx context.Context, runtime *common.RuntimeContext) error {
		return executeBaseCreate(runtime)
	},
}
View Source
var BaseBaseGet = common.Shortcut{
	Service:     "base",
	Command:     "+base-get",
	Description: "Get a base resource",
	Risk:        "read",
	Scopes:      []string{"base:app:read"},
	AuthTypes:   authTypes(),
	Flags:       []common.Flag{baseTokenFlag(true)},
	Tips: []string{
		"Use a real Base token; workspace tokens and wiki tokens are not accepted by this command.",
	},
	DryRun: dryRunBaseGet,
	Execute: func(ctx context.Context, runtime *common.RuntimeContext) error {
		return executeBaseGet(runtime)
	},
}
View Source
var BaseDashboardArrange = common.Shortcut{
	Service:     "base",
	Command:     "+dashboard-arrange",
	Description: "Auto-arrange dashboard blocks layout (server-side smart layout)",
	Risk:        "write",
	Scopes:      []string{"base:dashboard:update"},
	AuthTypes:   authTypes(),
	HasFormat:   true,
	Flags: []common.Flag{
		baseTokenFlag(true),
		dashboardIDFlag(true),
		{Name: "user-id-type", Desc: "user ID type: open_id / union_id / user_id"},
	},
	Tips: []string{
		"Server-side smart layout is not deterministic or position-specific; use only when the user asks to arrange or beautify a dashboard.",
	},
	DryRun: dryRunDashboardArrange,
	Execute: func(ctx context.Context, runtime *common.RuntimeContext) error {
		return executeDashboardArrange(runtime)
	},
}
View Source
var BaseDashboardBlockCreate = common.Shortcut{
	Service:     "base",
	Command:     "+dashboard-block-create",
	Description: "Create a block in a dashboard",
	Risk:        "write",
	Scopes:      []string{"base:dashboard:create"},
	AuthTypes:   authTypes(),
	HasFormat:   true,
	Flags: []common.Flag{
		baseTokenFlag(true),
		dashboardIDFlag(true),
		{Name: "name", Desc: "block name", Required: true},
		{Name: "type", Desc: "block type: column(柱状图)|bar(条形图)|line(折线图)|pie(饼图)|ring(环形图)|area(面积图)|combo(组合图)|scatter(散点图)|funnel(漏斗图)|wordCloud(词云)|radar(雷达图)|statistics(指标卡)|text(文本). Read dashboard-block-data-config.md before creating.", Required: true},
		{Name: "data-config", Desc: "data_config JSON object; read dashboard-block-data-config.md for the SSOT"},
		{Name: "user-id-type", Desc: "user ID type for user fields in filters: open_id / union_id / user_id"},
		{Name: "no-validate", Type: "bool", Desc: "skip local data_config validation"},
	},
	Tips: []string{
		`lark-cli base +dashboard-block-create --base-token <base_token> --dashboard-id <dashboard_id> --name "Order Count" --type statistics --data-config '{"table_name":"Orders","count_all":true}'`,
		`lark-cli base +dashboard-block-create --base-token <base_token> --dashboard-id <dashboard_id> --name "Dashboard Note" --type text --data-config '{"text":"# Sales Dashboard"}'`,
		"Before creating data-backed blocks, use +table-list and +field-list to confirm real table and field names.",
		"data_config uses table and field names, not table_id or field_id.",
		"Read dashboard-block-data-config.md as the SSOT for chart templates, filters, metric rules, and type-specific fields; do not invent data_config from natural language.",
		"Record the returned block_id; block update/delete/get-data commands need it.",
		"Create dashboard blocks sequentially; do not parallelize multiple block creates for the same dashboard.",
	},
	Validate: func(ctx context.Context, runtime *common.RuntimeContext) error {
		pc := newParseCtx(runtime)
		if runtime.Bool("no-validate") {
			return nil
		}
		raw := runtime.Str("data-config")
		if strings.TrimSpace(raw) == "" {

			if strings.ToLower(runtime.Str("type")) == "text" {
				return errs.NewValidationError(errs.SubtypeInvalidArgument, "text 类型组件必须提供 data-config,包含必填字段 text").WithParam("--data-config")
			}
			return nil
		}
		cfg, err := parseJSONObject(pc, raw, "data-config")
		if err != nil {
			return err
		}
		norm := normalizeDataConfig(cfg)
		if errs := validateBlockDataConfig(runtime.Str("type"), norm); len(errs) > 0 {
			return formatDataConfigErrors(errs)
		}

		b, _ := json.Marshal(norm)
		_ = runtime.Cmd.Flags().Set("data-config", string(b))
		return nil
	},
	DryRun: func(ctx context.Context, runtime *common.RuntimeContext) *common.DryRunAPI {
		pc := newParseCtx(runtime)
		body := map[string]interface{}{}
		if name := runtime.Str("name"); name != "" {
			body["name"] = name
		}
		if t := runtime.Str("type"); t != "" {
			body["type"] = t
		}
		if raw := runtime.Str("data-config"); raw != "" {
			if parsed, err := parseJSONObject(pc, raw, "data-config"); err == nil {
				body["data_config"] = parsed
			}
		}
		params := map[string]interface{}{}
		if uid := runtime.Str("user-id-type"); uid != "" {
			params["user_id_type"] = uid
		}
		return common.NewDryRunAPI().
			POST("/open-apis/base/v3/bases/:base_token/dashboards/:dashboard_id/blocks").
			Params(params).
			Body(body).
			Set("base_token", runtime.Str("base-token")).
			Set("dashboard_id", runtime.Str("dashboard-id"))
	},
	Execute: func(ctx context.Context, runtime *common.RuntimeContext) error {
		return executeDashboardBlockCreate(runtime)
	},
}
View Source
var BaseDashboardBlockDelete = common.Shortcut{
	Service:     "base",
	Command:     "+dashboard-block-delete",
	Description: "Delete a dashboard block",
	Risk:        "high-risk-write",
	Scopes:      []string{"base:dashboard:delete"},
	AuthTypes:   authTypes(),
	HasFormat:   true,
	Flags: []common.Flag{
		baseTokenFlag(true),
		dashboardIDFlag(true),
		blockIDFlag(true),
	},
	Tips: []string{
		"lark-cli base +dashboard-block-delete --base-token <base_token> --dashboard-id <dashboard_id> --block-id <block_id> --yes",
		baseHighRiskYesTip,
	},
	DryRun: func(ctx context.Context, runtime *common.RuntimeContext) *common.DryRunAPI {
		return common.NewDryRunAPI().
			DELETE("/open-apis/base/v3/bases/:base_token/dashboards/:dashboard_id/blocks/:block_id").
			Set("base_token", runtime.Str("base-token")).
			Set("dashboard_id", runtime.Str("dashboard-id")).
			Set("block_id", runtime.Str("block-id"))
	},
	Execute: func(ctx context.Context, runtime *common.RuntimeContext) error {
		return executeDashboardBlockDelete(runtime)
	},
}
View Source
var BaseDashboardBlockGet = common.Shortcut{
	Service:     "base",
	Command:     "+dashboard-block-get",
	Description: "Get a dashboard block by ID",
	Risk:        "read",
	Scopes:      []string{"base:dashboard:read"},
	AuthTypes:   authTypes(),
	HasFormat:   true,
	Flags: []common.Flag{
		baseTokenFlag(true),
		dashboardIDFlag(true),
		blockIDFlag(true),
		{Name: "user-id-type", Desc: "user ID type: open_id / union_id / user_id"},
	},
	Tips: []string{
		"lark-cli base +dashboard-block-get --base-token <base_token> --dashboard-id <dashboard_id> --block-id <block_id>",
		"Use this command for block metadata such as name, type, layout, and data_config.",
		"Use +dashboard-block-get-data when you need the computed chart result instead of metadata.",
	},
	DryRun: func(ctx context.Context, runtime *common.RuntimeContext) *common.DryRunAPI {
		params := map[string]interface{}{}
		if uid := strings.TrimSpace(runtime.Str("user-id-type")); uid != "" {
			params["user_id_type"] = uid
		}
		return common.NewDryRunAPI().
			GET("/open-apis/base/v3/bases/:base_token/dashboards/:dashboard_id/blocks/:block_id").
			Params(params).
			Set("base_token", runtime.Str("base-token")).
			Set("dashboard_id", runtime.Str("dashboard-id")).
			Set("block_id", runtime.Str("block-id"))
	},
	Execute: func(ctx context.Context, runtime *common.RuntimeContext) error {
		return executeDashboardBlockGet(runtime)
	},
}
View Source
var BaseDashboardBlockGetData = common.Shortcut{
	Service:     "base",
	Command:     "+dashboard-block-get-data",
	Description: "Get computed data for a dashboard chart block",
	Risk:        "read",
	Scopes:      []string{"base:dashboard:read"},
	AuthTypes:   authTypes(),
	HasFormat:   true,
	Flags: []common.Flag{
		baseTokenFlag(true),
		blockIDFlag(true),
	},
	Tips: []string{
		"lark-cli base +dashboard-block-get-data --base-token <base_token> --block-id <block_id>",
		"This command does not need --dashboard-id.",
		"Use +dashboard-block-get first when you need block metadata like name, type, or data_config.",
		"This command returns computed chart protocol JSON directly, not wrapped block metadata.",
		"Text blocks do not have computed chart data; this shortcut is for chart/statistics blocks.",
	},
	DryRun: func(ctx context.Context, runtime *common.RuntimeContext) *common.DryRunAPI {
		return dryRunDashboardBlockGetData(ctx, runtime)
	},
	Execute: func(ctx context.Context, runtime *common.RuntimeContext) error {
		return executeDashboardBlockGetData(runtime)
	},
}
View Source
var BaseDashboardBlockList = common.Shortcut{
	Service:     "base",
	Command:     "+dashboard-block-list",
	Description: "List blocks in a dashboard",
	Risk:        "read",
	Scopes:      []string{"base:dashboard:read"},
	AuthTypes:   authTypes(),
	HasFormat:   true,
	Flags: []common.Flag{
		baseTokenFlag(true),
		dashboardIDFlag(true),
		{Name: "page-size", Type: "int", Default: "20", Desc: "page size, range 1-100"},
		{Name: "page-token", Desc: "pagination token"},
	},
	Tips: []string{
		"lark-cli base +dashboard-block-list --base-token <base_token> --dashboard-id <dashboard_id>",
		"Use returned block_id and type values for +dashboard-block-get/update/delete/get-data.",
	},
	Validate: func(ctx context.Context, runtime *common.RuntimeContext) error {
		_, err := common.ValidatePageSizeTyped(runtime, "page-size", 20, 1, 100)
		return err
	},
	DryRun: func(ctx context.Context, runtime *common.RuntimeContext) *common.DryRunAPI {
		params := map[string]interface{}{}
		params["page_size"] = runtime.Int("page-size")
		if pt := strings.TrimSpace(runtime.Str("page-token")); pt != "" {
			params["page_token"] = pt
		}
		return common.NewDryRunAPI().
			GET("/open-apis/base/v3/bases/:base_token/dashboards/:dashboard_id/blocks").
			Params(params).
			Set("base_token", runtime.Str("base-token")).
			Set("dashboard_id", runtime.Str("dashboard-id"))
	},
	Execute: func(ctx context.Context, runtime *common.RuntimeContext) error {
		return executeDashboardBlockList(runtime)
	},
}
View Source
var BaseDashboardBlockUpdate = common.Shortcut{
	Service:     "base",
	Command:     "+dashboard-block-update",
	Description: "Update a dashboard block",
	Risk:        "write",
	Scopes:      []string{"base:dashboard:update"},
	AuthTypes:   authTypes(),
	HasFormat:   true,
	Flags: []common.Flag{
		baseTokenFlag(true),
		dashboardIDFlag(true),
		blockIDFlag(true),
		{Name: "name", Desc: "new block name"},
		{Name: "data-config", Desc: "data_config JSON object; read dashboard-block-data-config.md for the SSOT"},
		{Name: "user-id-type", Desc: "user ID type for user fields in filters: open_id / union_id / user_id"},
		{Name: "no-validate", Type: "bool", Desc: "skip local data_config validation"},
	},
	Tips: []string{
		`lark-cli base +dashboard-block-update --base-token <base_token> --dashboard-id <dashboard_id> --block-id <block_id> --name "Total Sales"`,
		`lark-cli base +dashboard-block-update --base-token <base_token> --dashboard-id <dashboard_id> --block-id <block_id> --data-config '{"series":[{"field_name":"Amount","rollup":"SUM"}]}'`,
		"Read dashboard-block-data-config.md as the SSOT for data_config templates, filters, metric rules, and type-specific fields; do not invent data_config from natural language.",
		"Use +dashboard-block-get first to inspect the current data_config before replacing nested values.",
		"Block type cannot be changed; delete and recreate the block to change chart type.",
		"data_config update merges top-level keys, but each provided key is replaced as a whole.",
	},
	Validate: func(ctx context.Context, runtime *common.RuntimeContext) error {
		pc := newParseCtx(runtime)
		if runtime.Bool("no-validate") {
			return nil
		}
		raw := runtime.Str("data-config")
		if strings.TrimSpace(raw) == "" {
			return nil
		}
		cfg, err := parseJSONObject(pc, raw, "data-config")
		if err != nil {
			return err
		}
		norm := normalizeDataConfig(cfg)

		b, _ := json.Marshal(norm)
		_ = runtime.Cmd.Flags().Set("data-config", string(b))
		return nil
	},
	DryRun: func(ctx context.Context, runtime *common.RuntimeContext) *common.DryRunAPI {
		pc := newParseCtx(runtime)
		body := map[string]interface{}{}
		if name := runtime.Str("name"); name != "" {
			body["name"] = name
		}
		if raw := runtime.Str("data-config"); raw != "" {
			if parsed, err := parseJSONObject(pc, raw, "data-config"); err == nil {
				body["data_config"] = parsed
			}
		}
		params := map[string]interface{}{}
		if uid := runtime.Str("user-id-type"); uid != "" {
			params["user_id_type"] = uid
		}
		return common.NewDryRunAPI().
			PATCH("/open-apis/base/v3/bases/:base_token/dashboards/:dashboard_id/blocks/:block_id").
			Params(params).
			Body(body).
			Set("base_token", runtime.Str("base-token")).
			Set("dashboard_id", runtime.Str("dashboard-id")).
			Set("block_id", runtime.Str("block-id"))
	},
	Execute: func(ctx context.Context, runtime *common.RuntimeContext) error {
		return executeDashboardBlockUpdate(runtime)
	},
}
View Source
var BaseDashboardCreate = common.Shortcut{
	Service:     "base",
	Command:     "+dashboard-create",
	Description: "Create a dashboard in a base",
	Risk:        "write",
	Scopes:      []string{"base:dashboard:create"},
	AuthTypes:   authTypes(),
	HasFormat:   true,
	Flags: []common.Flag{
		baseTokenFlag(true),
		{Name: "name", Desc: "dashboard name", Required: true},
		{Name: "theme-style", Desc: "theme style, defaults to platform default when omitted"},
	},
	Tips: []string{
		"Record the returned dashboard_id; dashboard block create/get/update/delete/arrange commands need it.",
	},
	DryRun: func(ctx context.Context, runtime *common.RuntimeContext) *common.DryRunAPI {
		body := map[string]interface{}{}
		if name := runtime.Str("name"); name != "" {
			body["name"] = name
		}
		if themeStyle := runtime.Str("theme-style"); themeStyle != "" {
			body["theme"] = map[string]interface{}{"theme_style": themeStyle}
		}
		return common.NewDryRunAPI().
			POST("/open-apis/base/v3/bases/:base_token/dashboards").
			Body(body).
			Set("base_token", runtime.Str("base-token"))
	},
	Execute: func(ctx context.Context, runtime *common.RuntimeContext) error {
		return executeDashboardCreate(runtime)
	},
}
View Source
var BaseDashboardDelete = common.Shortcut{
	Service:     "base",
	Command:     "+dashboard-delete",
	Description: "Delete a dashboard",
	Risk:        "high-risk-write",
	Scopes:      []string{"base:dashboard:delete"},
	AuthTypes:   authTypes(),
	HasFormat:   true,
	Flags: []common.Flag{
		baseTokenFlag(true),
		dashboardIDFlag(true),
	},
	Tips: []string{
		"lark-cli base +dashboard-delete --base-token <base_token> --dashboard-id <dashboard_id> --yes",
		"Deleting a dashboard also deletes its blocks and cannot be recovered.",
		baseHighRiskYesTip,
	},
	DryRun: func(ctx context.Context, runtime *common.RuntimeContext) *common.DryRunAPI {
		return common.NewDryRunAPI().
			DELETE("/open-apis/base/v3/bases/:base_token/dashboards/:dashboard_id").
			Set("base_token", runtime.Str("base-token")).
			Set("dashboard_id", runtime.Str("dashboard-id"))
	},
	Execute: func(ctx context.Context, runtime *common.RuntimeContext) error {
		return executeDashboardDelete(runtime)
	},
}
View Source
var BaseDashboardGet = common.Shortcut{
	Service:     "base",
	Command:     "+dashboard-get",
	Description: "Get a dashboard by ID",
	Risk:        "read",
	Scopes:      []string{"base:dashboard:read"},
	AuthTypes:   authTypes(),
	HasFormat:   true,
	Flags: []common.Flag{
		baseTokenFlag(true),
		dashboardIDFlag(true),
	},
	Tips: []string{
		"Use +dashboard-block-list or +dashboard-block-get when you need block-level details.",
	},
	DryRun: func(ctx context.Context, runtime *common.RuntimeContext) *common.DryRunAPI {
		return common.NewDryRunAPI().
			GET("/open-apis/base/v3/bases/:base_token/dashboards/:dashboard_id").
			Set("base_token", runtime.Str("base-token")).
			Set("dashboard_id", runtime.Str("dashboard-id"))
	},
	Execute: func(ctx context.Context, runtime *common.RuntimeContext) error {
		return executeDashboardGet(runtime)
	},
}
View Source
var BaseDashboardList = common.Shortcut{
	Service:     "base",
	Command:     "+dashboard-list",
	Description: "List dashboards in a base",
	Risk:        "read",
	Scopes:      []string{"base:dashboard:read"},
	AuthTypes:   authTypes(),
	HasFormat:   true,
	Flags: []common.Flag{
		baseTokenFlag(true),
		{Name: "page-size", Type: "int", Default: "100", Desc: "page size, range 1-100"},
		{Name: "page-token", Desc: "pagination token"},
	},
	Tips: []string{
		"Use returned dashboard_id values for +dashboard-get, +dashboard-block-list, and +dashboard-block-create.",
	},
	Validate: func(ctx context.Context, runtime *common.RuntimeContext) error {
		_, err := common.ValidatePageSizeTyped(runtime, "page-size", 100, 1, 100)
		return err
	},
	DryRun: func(ctx context.Context, runtime *common.RuntimeContext) *common.DryRunAPI {
		params := map[string]interface{}{}
		params["page_size"] = runtime.Int("page-size")
		if pt := strings.TrimSpace(runtime.Str("page-token")); pt != "" {
			params["page_token"] = pt
		}
		return common.NewDryRunAPI().
			GET("/open-apis/base/v3/bases/:base_token/dashboards").
			Params(params).
			Set("base_token", runtime.Str("base-token"))
	},
	Execute: func(ctx context.Context, runtime *common.RuntimeContext) error {
		return executeDashboardList(runtime)
	},
}
View Source
var BaseDashboardUpdate = common.Shortcut{
	Service:     "base",
	Command:     "+dashboard-update",
	Description: "Update a dashboard",
	Risk:        "write",
	Scopes:      []string{"base:dashboard:update"},
	AuthTypes:   authTypes(),
	HasFormat:   true,
	Flags: []common.Flag{
		baseTokenFlag(true),
		dashboardIDFlag(true),
		{Name: "name", Desc: "new dashboard name"},
		{Name: "theme-style", Desc: "theme style, leave empty to keep current theme"},
	},
	DryRun: func(ctx context.Context, runtime *common.RuntimeContext) *common.DryRunAPI {
		body := map[string]interface{}{}
		if name := runtime.Str("name"); name != "" {
			body["name"] = name
		}
		if themeStyle := runtime.Str("theme-style"); themeStyle != "" {
			body["theme"] = map[string]interface{}{"theme_style": themeStyle}
		}
		return common.NewDryRunAPI().
			PATCH("/open-apis/base/v3/bases/:base_token/dashboards/:dashboard_id").
			Body(body).
			Set("base_token", runtime.Str("base-token")).
			Set("dashboard_id", runtime.Str("dashboard-id"))
	},
	Execute: func(ctx context.Context, runtime *common.RuntimeContext) error {
		return executeDashboardUpdate(runtime)
	},
}
View Source
var BaseDataQuery = common.Shortcut{
	Service:     "base",
	Command:     "+data-query",
	Description: "Query and analyze Base data with JSON DSL (aggregation, filter, sort)",
	Risk:        "read",
	Scopes:      []string{"base:table:read"},
	AuthTypes:   authTypes(),
	Flags: []common.Flag{
		baseTokenFlag(true),
		{Name: "dsl", Desc: "query JSON DSL; read lark-base-data-query-guide.md first, then lark-base-data-query.md for the full DSL SSOT", Required: true},
	},
	Tips: []string{
		"Use +data-query for server-side aggregation, grouping, filtering, sorting, and Top N queries.",
		"Read lark-base-data-query-guide.md for common fewshots; use lark-base-data-query.md only when the full DSL reference is needed.",
		"`dimensions` and `measures` cannot both be empty.",
	},
	Validate: func(ctx context.Context, runtime *common.RuntimeContext) error {
		var dsl map[string]interface{}
		dec := json.NewDecoder(bytes.NewReader([]byte(runtime.Str("dsl"))))
		dec.UseNumber()
		if err := dec.Decode(&dsl); err != nil {
			return baseFlagErrorf("--dsl invalid JSON: %v", err)
		}
		_, hasDim := dsl["dimensions"]
		_, hasMeas := dsl["measures"]
		if !hasDim && !hasMeas {
			return baseFlagErrorf("--dsl must contain at least one of 'dimensions' or 'measures'")
		}
		return nil
	},
	DryRun: func(ctx context.Context, runtime *common.RuntimeContext) *common.DryRunAPI {
		var dsl map[string]interface{}
		dec := json.NewDecoder(bytes.NewReader([]byte(runtime.Str("dsl"))))
		dec.UseNumber()
		dec.Decode(&dsl)
		return common.NewDryRunAPI().
			POST("/open-apis/base/v3/bases/:base_token/data/query").
			Body(dsl).
			Set("base_token", runtime.Str("base-token"))
	},
	Execute: func(ctx context.Context, runtime *common.RuntimeContext) error {
		baseToken := runtime.Str("base-token")

		var dsl map[string]interface{}
		dec := json.NewDecoder(bytes.NewReader([]byte(runtime.Str("dsl"))))
		dec.UseNumber()
		dec.Decode(&dsl)

		data, err := baseV3Call(runtime, "POST", baseV3Path("bases", baseToken, "data/query"), nil, dsl)
		if err != nil {
			return err
		}

		runtime.Out(data, nil)
		return nil
	},
}
View Source
var BaseFieldCreate = common.Shortcut{
	Service:     "base",
	Command:     "+field-create",
	Description: "Create a field",
	Risk:        "write",
	Scopes:      []string{"base:field:create"},
	AuthTypes:   authTypes(),
	Flags: []common.Flag{
		baseTokenFlag(true),
		tableRefFlag(true),
		{Name: "json", Desc: "field property JSON object", Required: true},
		{Name: "i-have-read-guide", Type: "bool", Desc: "set only after you have read the formula/lookup guide for those field types", Hidden: true},
	},
	Tips: []string{
		`Example text: lark-cli base +field-create --base-token <base_token> --table-id <table_id> --json '{"name":"Status","type":"text"}'`,
		`Example select: lark-cli base +field-create --base-token <base_token> --table-id <table_id> --json '{"name":"Status","type":"select","multiple":false,"options":[{"name":"Todo"},{"name":"Done"}]}'`,
		"Agent hint: use the lark-base skill's field-create guide for usage and limits.",
	},
	Validate: func(ctx context.Context, runtime *common.RuntimeContext) error {
		return validateFieldCreate(runtime)
	},
	DryRun: dryRunFieldCreate,
	Execute: func(ctx context.Context, runtime *common.RuntimeContext) error {
		return executeFieldCreate(runtime)
	},
}
View Source
var BaseFieldDelete = common.Shortcut{
	Service:     "base",
	Command:     "+field-delete",
	Description: "Delete a field by ID or name",
	Risk:        "high-risk-write",
	Scopes:      []string{"base:field:delete"},
	AuthTypes:   authTypes(),
	Flags:       []common.Flag{baseTokenFlag(true), tableRefFlag(true), fieldRefFlag(true)},
	Tips: []string{
		baseHighRiskYesTip,
		`Example: lark-cli base +field-delete --base-token <base_token> --table-id <table_id> --field-id "Status" --yes`,
	},
	DryRun: dryRunFieldDelete,
	Execute: func(ctx context.Context, runtime *common.RuntimeContext) error {
		return executeFieldDelete(runtime)
	},
}
View Source
var BaseFieldGet = common.Shortcut{
	Service:     "base",
	Command:     "+field-get",
	Description: "Get a field by ID or name",
	Risk:        "read",
	Scopes:      []string{"base:field:read"},
	AuthTypes:   authTypes(),
	Flags:       []common.Flag{baseTokenFlag(true), tableRefFlag(true), fieldRefFlag(true)},
	Tips: []string{
		`Example: lark-cli base +field-get --base-token <base_token> --table-id <table_id> --field-id "Status"`,
		"field-id accepts a field ID (fld...) or the field name from the current table.",
		"Returns full field configuration; use it as the baseline before +field-update.",
	},
	DryRun: dryRunFieldGet,
	Execute: func(ctx context.Context, runtime *common.RuntimeContext) error {
		return executeFieldGet(runtime)
	},
}
View Source
var BaseFieldList = common.Shortcut{
	Service:     "base",
	Command:     "+field-list",
	Description: "List fields in a table",
	Risk:        "read",
	Scopes:      []string{"base:field:read"},
	AuthTypes:   authTypes(),
	Flags: []common.Flag{
		baseTokenFlag(true),
		tableRefFlag(true),
		{Name: "offset", Type: "int", Default: "0", Desc: "pagination offset"},
		{Name: "limit", Type: "int", Default: "100", Desc: "pagination size, range 1-200"},
		pageSizeLimitAliasFlag(),
	},
	Validate: func(ctx context.Context, runtime *common.RuntimeContext) error {
		if err := validateLimitPageSizeAlias(runtime); err != nil {
			return err
		}
		if _, err := common.ValidatePageSizeTyped(runtime, "limit", 100, 1, 200); err != nil {
			return err
		}
		if runtime.Changed("page-size") {
			if _, err := common.ValidatePageSizeTyped(runtime, "page-size", 100, 1, 200); err != nil {
				return err
			}
		}
		return nil
	},
	DryRun: dryRunFieldList,
	Execute: func(ctx context.Context, runtime *common.RuntimeContext) error {
		return executeFieldList(runtime)
	},
}
View Source
var BaseFieldSearchOptions = common.Shortcut{
	Service:     "base",
	Command:     "+field-search-options",
	Description: "Search select options of a field",
	Risk:        "read",
	Scopes:      []string{"base:field:read"},
	AuthTypes:   authTypes(),
	Flags: []common.Flag{
		baseTokenFlag(true),
		tableRefFlag(true),
		fieldRefFlag(true),
		{Name: "keyword", Desc: "keyword for option query"},
		{Name: "offset", Type: "int", Default: "0", Desc: "pagination offset"},
		{Name: "limit", Type: "int", Default: "30", Desc: "pagination size, range 1-200"},
		pageSizeLimitAliasFlag(),
	},
	Tips: []string{
		`Example: lark-cli base +field-search-options --base-token <base_token> --table-id <table_id> --field-id "Status" --keyword "Do"`,
		"Use only for fields with options, such as select or multi-select fields.",
	},
	Validate: func(ctx context.Context, runtime *common.RuntimeContext) error {
		if err := validateLimitPageSizeAlias(runtime); err != nil {
			return err
		}
		if _, err := common.ValidatePageSizeTyped(runtime, "limit", 30, 1, 200); err != nil {
			return err
		}
		if runtime.Changed("page-size") {
			if _, err := common.ValidatePageSizeTyped(runtime, "page-size", 30, 1, 200); err != nil {
				return err
			}
		}
		return nil
	},
	DryRun: dryRunFieldSearchOptions,
	Execute: func(ctx context.Context, runtime *common.RuntimeContext) error {
		return executeFieldSearchOptions(runtime)
	},
}
View Source
var BaseFieldUpdate = common.Shortcut{
	Service:     "base",
	Command:     "+field-update",
	Description: "Update a field by ID or name",
	Risk:        "high-risk-write",
	Scopes:      []string{"base:field:update"},
	AuthTypes:   authTypes(),
	Flags: []common.Flag{
		baseTokenFlag(true),
		tableRefFlag(true),
		fieldRefFlag(true),
		{Name: "json", Desc: "complete field definition JSON object; update uses full PUT semantics, not a patch", Required: true},
		{Name: "i-have-read-guide", Type: "bool", Desc: "acknowledge reading formula/lookup guide before creating or updating those field types", Hidden: true},
	},
	Tips: []string{
		baseHighRiskYesTip,
		`Example text: lark-cli base +field-update --base-token <base_token> --table-id <table_id> --field-id "Status" --json '{"name":"Status","type":"text"}' --yes`,
		`Example select: lark-cli base +field-update --base-token <base_token> --table-id <table_id> --field-id "Status" --json '{"name":"Status","type":"select","multiple":false,"options":[{"name":"Todo"},{"name":"Done"}]}' --yes`,
		"Update uses full field-definition PUT semantics. Read the current field first with +field-get, then send the target state.",
		"Type conversion is allowlist-based: only use CLI for safe conversions; otherwise migrate through a new field, or ask the user to finish high-risk conversions in the web UI.",
		"Formula and lookup updates require reading the corresponding guide first.",
		"Agent hint: use the lark-base skill's field-update guide for JSON shape, type-conversion rules, and limits.",
	},
	Validate: func(ctx context.Context, runtime *common.RuntimeContext) error {
		return validateFieldUpdate(runtime)
	},
	DryRun: dryRunFieldUpdate,
	Execute: func(ctx context.Context, runtime *common.RuntimeContext) error {
		return executeFieldUpdate(runtime)
	},
}
View Source
var BaseFormCreate = common.Shortcut{
	Service:     "base",
	Command:     "+form-create",
	Description: "Create a form in a Base table",
	Risk:        "write",
	Scopes:      []string{"base:form:create"},
	AuthTypes:   []string{"user", "bot"},
	HasFormat:   true,
	Flags: []common.Flag{
		{Name: "base-token", Desc: "Base token (base_token)", Required: true},
		{Name: "table-id", Desc: "table ID", Required: true},
		{Name: "name", Desc: "form name", Required: true},
		{Name: "description", Desc: `form description (plain text or markdown link like [text](https://example.com))`},
	},
	Tips: []string{
		"Record the returned form_id; form question create/list/update/delete commands need it.",
	},
	DryRun: func(ctx context.Context, runtime *common.RuntimeContext) *common.DryRunAPI {
		return common.NewDryRunAPI().
			POST("/open-apis/base/v3/bases/:base_token/tables/:table_id/forms").
			Set("base_token", runtime.Str("base-token")).
			Set("table_id", runtime.Str("table-id"))
	},
	Execute: func(ctx context.Context, runtime *common.RuntimeContext) error {
		baseToken := runtime.Str("base-token")
		tableId := runtime.Str("table-id")
		name := runtime.Str("name")
		description := runtime.Str("description")

		body := map[string]interface{}{"name": name}
		if description != "" {
			body["description"] = description
		}

		data, err := baseV3Call(runtime, "POST",
			baseV3Path("bases", baseToken, "tables", tableId, "forms"), nil, body)
		if err != nil {
			return err
		}

		runtime.OutFormat(data, nil, func(w io.Writer) {
			output.PrintTable(w, []map[string]interface{}{
				{
					"id":          data["id"],
					"name":        data["name"],
					"description": data["description"],
				},
			})
		})
		return nil
	},
}
View Source
var BaseFormDelete = common.Shortcut{
	Service:     "base",
	Command:     "+form-delete",
	Description: "Delete a form in a Base table",
	Risk:        "high-risk-write",
	Scopes:      []string{"base:form:delete"},
	AuthTypes:   []string{"user", "bot"},
	HasFormat:   true,
	Flags: []common.Flag{
		baseTokenFlag(true),
		{Name: "table-id", Desc: "table ID", Required: true},
		{Name: "form-id", Desc: "form ID", Required: true},
	},
	Tips: []string{
		"Use +form-list or +form-get first when the form target is ambiguous.",
		baseHighRiskYesTip,
	},
	DryRun: func(ctx context.Context, runtime *common.RuntimeContext) *common.DryRunAPI {
		return common.NewDryRunAPI().
			DELETE("/open-apis/base/v3/bases/:base_token/tables/:table_id/forms/:form_id").
			Set("base_token", runtime.Str("base-token")).
			Set("table_id", runtime.Str("table-id")).
			Set("form_id", runtime.Str("form-id"))
	},
	Execute: func(ctx context.Context, runtime *common.RuntimeContext) error {
		baseToken := runtime.Str("base-token")
		tableId := runtime.Str("table-id")
		formId := runtime.Str("form-id")

		_, err := baseV3Call(runtime, "DELETE",
			baseV3Path("bases", baseToken, "tables", tableId, "forms", formId), nil, nil)
		if err != nil {
			return err
		}

		runtime.Out(map[string]interface{}{"deleted": true, "form_id": formId}, nil)
		return nil
	},
}
View Source
var BaseFormDetail = common.Shortcut{
	Service:     "base",
	Command:     "+form-detail",
	Description: "Get form detail by share token",
	Risk:        "read",
	Scopes:      []string{"base:form:read"},
	AuthTypes:   []string{"user", "bot"},
	HasFormat:   true,
	Flags: []common.Flag{
		{Name: "share-token", Desc: "Form share token (share_token)", Required: true},
	},
	DryRun: func(ctx context.Context, runtime *common.RuntimeContext) *common.DryRunAPI {
		return common.NewDryRunAPI().
			POST("/open-apis/base/v3/bases/tables/forms/detail").
			Body(map[string]interface{}{
				"share_token": runtime.Str("share-token"),
			})
	},
	Execute: func(ctx context.Context, runtime *common.RuntimeContext) error {
		body := map[string]interface{}{
			"share_token": runtime.Str("share-token"),
		}

		data, err := baseV3Call(runtime, "POST",
			baseV3Path("bases", "tables", "forms", "detail"), nil, body)
		if err != nil {
			return err
		}

		runtime.Out(data, nil)
		return nil
	},
}
View Source
var BaseFormGet = common.Shortcut{
	Service:     "base",
	Command:     "+form-get",
	Description: "Get a form in a Base table",
	Risk:        "read",
	Scopes:      []string{"base:form:read"},
	AuthTypes:   []string{"user", "bot"},
	HasFormat:   true,
	Flags: []common.Flag{
		baseTokenFlag(true),
		{Name: "table-id", Desc: "table ID", Required: true},
		{Name: "form-id", Desc: "form ID", Required: true},
	},
	DryRun: func(ctx context.Context, runtime *common.RuntimeContext) *common.DryRunAPI {
		return common.NewDryRunAPI().
			GET("/open-apis/base/v3/bases/:base_token/tables/:table_id/forms/:form_id").
			Set("base_token", runtime.Str("base-token")).
			Set("table_id", runtime.Str("table-id")).
			Set("form_id", runtime.Str("form-id"))
	},
	Execute: func(ctx context.Context, runtime *common.RuntimeContext) error {
		baseToken := runtime.Str("base-token")
		tableId := runtime.Str("table-id")
		formId := runtime.Str("form-id")

		data, err := baseV3Call(runtime, "GET",
			baseV3Path("bases", baseToken, "tables", tableId, "forms", formId), nil, nil)
		if err != nil {
			return err
		}

		runtime.OutFormat(data, nil, func(w io.Writer) {
			output.PrintTable(w, []map[string]interface{}{
				{
					"id":          data["id"],
					"name":        data["name"],
					"description": data["description"],
				},
			})
		})
		return nil
	},
}
View Source
var BaseFormQuestionsCreate = common.Shortcut{
	Service:     "base",
	Command:     "+form-questions-create",
	Description: "Create questions for a form in a Base table",
	Risk:        "write",
	Scopes:      []string{"base:form:update"},
	AuthTypes:   []string{"user", "bot"},
	HasFormat:   true,
	Flags: []common.Flag{
		{Name: "base-token", Desc: "Base token (base_token)", Required: true},
		{Name: "table-id", Desc: "table ID", Required: true},
		{Name: "form-id", Desc: "form ID", Required: true},
		{Name: "questions", Desc: `questions JSON array, max 10 items. Each item requires "title"(field title) and "type"(text/number/select/datetime/user/attachment/location). Optional fields: "description"(plain text or markdown link like [text](https://example.com)),"required","option_display_mode"(0=dropdown/1=vertical/2=horizontal,select only),"multiple"(bool,select/user),"options"([{"name":"opt","hue":"Blue"}],select only),"style"({"type":"plain/phone/url/email/barcode/rating","precision":2,"format":"yyyy/MM/dd","icon":"star","min":1,"max":5}). E.g. '[{"type":"text","title":"Your name","required":true}]'`, Required: true},
	},
	DryRun: func(ctx context.Context, runtime *common.RuntimeContext) *common.DryRunAPI {
		return common.NewDryRunAPI().
			POST("/open-apis/base/v3/bases/:base_token/tables/:table_id/forms/:form_id/questions").
			Set("base_token", runtime.Str("base-token")).
			Set("table_id", runtime.Str("table-id")).
			Set("form_id", runtime.Str("form-id"))
	},
	Execute: func(ctx context.Context, runtime *common.RuntimeContext) error {
		baseToken := runtime.Str("base-token")
		tableId := runtime.Str("table-id")
		formId := runtime.Str("form-id")
		questionsJSON := runtime.Str("questions")

		var questions []interface{}
		if err := json.Unmarshal([]byte(questionsJSON), &questions); err != nil {
			return baseValidationErrorf("--questions must be a valid JSON array: %s", err)
		}

		data, err := baseV3Call(runtime, "POST",
			baseV3Path("bases", baseToken, "tables", tableId, "forms", formId, "questions"),
			nil, map[string]interface{}{"questions": questions})
		if err != nil {
			return err
		}

		items, _ := data["questions"].([]interface{})
		outData := map[string]interface{}{"questions": items}

		runtime.OutFormat(outData, nil, func(w io.Writer) {
			var rows []map[string]interface{}
			for _, item := range items {
				m, _ := item.(map[string]interface{})
				rows = append(rows, map[string]interface{}{
					"id":       m["id"],
					"title":    m["title"],
					"required": m["required"],
				})
			}
			output.PrintTable(w, rows)
			fmt.Fprintf(w, "\n%d question(s) created\n", len(items))
		})
		return nil
	},
}
View Source
var BaseFormQuestionsDelete = common.Shortcut{
	Service:     "base",
	Command:     "+form-questions-delete",
	Description: "Delete questions from a form in a Base table",
	Risk:        "high-risk-write",
	Scopes:      []string{"base:form:update"},
	AuthTypes:   []string{"user", "bot"},
	HasFormat:   true,
	Flags: []common.Flag{
		{Name: "base-token", Desc: "Base token (base_token)", Required: true},
		{Name: "table-id", Desc: "table ID", Required: true},
		{Name: "form-id", Desc: "form ID", Required: true},
		{Name: "question-ids", Desc: `JSON array of question IDs to delete, max 10 items, e.g. '["q_001","q_002"]'`, Required: true},
	},
	Tips: []string{
		baseHighRiskYesTip,
	},
	DryRun: func(ctx context.Context, runtime *common.RuntimeContext) *common.DryRunAPI {
		return common.NewDryRunAPI().
			DELETE("/open-apis/base/v3/bases/:base_token/tables/:table_id/forms/:form_id/questions").
			Set("base_token", runtime.Str("base-token")).
			Set("table_id", runtime.Str("table-id")).
			Set("form_id", runtime.Str("form-id"))
	},
	Execute: func(ctx context.Context, runtime *common.RuntimeContext) error {
		baseToken := runtime.Str("base-token")
		tableId := runtime.Str("table-id")
		formId := runtime.Str("form-id")
		questionIdsJSON := runtime.Str("question-ids")

		var questionIds []string
		if err := json.Unmarshal([]byte(questionIdsJSON), &questionIds); err != nil {
			return baseValidationErrorf("--question-ids must be a valid JSON array of strings: %s", err)
		}

		_, err := baseV3Call(runtime, "DELETE",
			baseV3Path("bases", baseToken, "tables", tableId, "forms", formId, "questions"),
			nil, map[string]interface{}{"question_ids": questionIds})
		if err != nil {
			return err
		}

		runtime.Out(map[string]interface{}{
			"deleted":      true,
			"question_ids": questionIds,
		}, nil)
		return nil
	},
}
View Source
var BaseFormQuestionsList = common.Shortcut{
	Service:     "base",
	Command:     "+form-questions-list",
	Description: "List questions of a form in a Base table",
	Risk:        "read",
	Scopes:      []string{"base:form:read"},
	AuthTypes:   []string{"user", "bot"},
	HasFormat:   true,
	Flags: []common.Flag{
		baseTokenFlag(true),
		{Name: "table-id", Desc: "table ID", Required: true},
		{Name: "form-id", Desc: "form ID", Required: true},
	},
	Tips: []string{
		"Use returned question id values for +form-questions-update and +form-questions-delete.",
	},
	DryRun: func(ctx context.Context, runtime *common.RuntimeContext) *common.DryRunAPI {
		return common.NewDryRunAPI().
			GET("/open-apis/base/v3/bases/:base_token/tables/:table_id/forms/:form_id/questions").
			Set("base_token", runtime.Str("base-token")).
			Set("table_id", runtime.Str("table-id")).
			Set("form_id", runtime.Str("form-id"))
	},
	Execute: func(ctx context.Context, runtime *common.RuntimeContext) error {
		baseToken := runtime.Str("base-token")
		tableId := runtime.Str("table-id")
		formId := runtime.Str("form-id")

		data, err := baseV3Call(runtime, "GET",
			baseV3Path("bases", baseToken, "tables", tableId, "forms", formId, "questions"), nil, nil)
		if err != nil {
			return err
		}

		items, _ := data["questions"].([]interface{})
		outData := map[string]interface{}{
			"questions": items,
			"total":     data["total"],
		}

		runtime.OutFormat(outData, nil, func(w io.Writer) {
			if len(items) == 0 {
				fmt.Fprintln(w, "No questions found.")
				return
			}
			var rows []map[string]interface{}
			for _, item := range items {
				m, _ := item.(map[string]interface{})
				rows = append(rows, map[string]interface{}{
					"id":          m["id"],
					"title":       m["title"],
					"description": m["description"],
					"required":    m["required"],
				})
			}
			output.PrintTable(w, rows)
			fmt.Fprintf(w, "\n%v question(s) total\n", data["total"])
		})
		return nil
	},
}
View Source
var BaseFormQuestionsUpdate = common.Shortcut{
	Service:     "base",
	Command:     "+form-questions-update",
	Description: "Update questions of a form in a Base table",
	Risk:        "write",
	Scopes:      []string{"base:form:update"},
	AuthTypes:   []string{"user", "bot"},
	HasFormat:   true,
	Flags: []common.Flag{
		{Name: "base-token", Desc: "Base token (base_token)", Required: true},
		{Name: "table-id", Desc: "table ID", Required: true},
		{Name: "form-id", Desc: "form ID", Required: true},
		{Name: "questions", Desc: `questions JSON array, max 10 items, each item must include "id". Supported fields: "id"(required),"title","description"(plain text or markdown link like [text](https://example.com)),"required","option_display_mode"(0=dropdown,1=vertical,2=horizontal,select only). E.g. '[{"id":"q_001","title":"Updated?","required":true}]'`, Required: true},
	},
	DryRun: func(ctx context.Context, runtime *common.RuntimeContext) *common.DryRunAPI {
		return common.NewDryRunAPI().
			PATCH("/open-apis/base/v3/bases/:base_token/tables/:table_id/forms/:form_id/questions").
			Set("base_token", runtime.Str("base-token")).
			Set("table_id", runtime.Str("table-id")).
			Set("form_id", runtime.Str("form-id"))
	},
	Execute: func(ctx context.Context, runtime *common.RuntimeContext) error {
		baseToken := runtime.Str("base-token")
		tableId := runtime.Str("table-id")
		formId := runtime.Str("form-id")
		questionsJSON := runtime.Str("questions")

		var questions []interface{}
		if err := json.Unmarshal([]byte(questionsJSON), &questions); err != nil {
			return baseValidationErrorf("--questions must be a valid JSON array: %s", err)
		}

		data, err := baseV3Call(runtime, "PATCH",
			baseV3Path("bases", baseToken, "tables", tableId, "forms", formId, "questions"),
			nil, map[string]interface{}{"questions": questions})
		if err != nil {
			return err
		}

		items, _ := data["items"].([]interface{})
		if len(items) == 0 {
			items, _ = data["questions"].([]interface{})
		}
		outData := map[string]interface{}{"questions": items}

		runtime.OutFormat(outData, nil, func(w io.Writer) {
			var rows []map[string]interface{}
			for _, item := range items {
				m, _ := item.(map[string]interface{})
				rows = append(rows, map[string]interface{}{
					"id":       m["id"],
					"title":    m["title"],
					"required": m["required"],
				})
			}
			output.PrintTable(w, rows)
			fmt.Fprintf(w, "\n%d question(s) updated\n", len(items))
		})
		return nil
	},
}
View Source
var BaseFormSubmit = common.Shortcut{
	Service:     "base",
	Command:     "+form-submit",
	Description: "Submit a form (fill and submit form data)",
	Risk:        "write",
	Scopes:      []string{"base:form:update", "docs:document.media:upload"},
	AuthTypes:   authTypes(),
	HasFormat:   true,
	Flags: []common.Flag{
		{Name: "share-token", Desc: "Form share token (required), extracted from the form share link", Required: true},
		{Name: "base-token", Desc: "Base token (required when --json contains attachments, used for uploading attachments to Base Drive Media)"},
		{Name: "json", Desc: `JSON object containing "fields" (field values) and "attachments" (attachment file paths). Example: '{"fields":{"Rating":5,"Review":"Good"},"attachments":{"Attachment":["./a.pdf","./b.png"]}}'`, Required: true},
	},
	Tips: []string{
		`Example (no attachments): --share-token shrXXXX --json '{"fields":{"Service Rating":5,"Review":"Good service"}}'`,
		`Example (with attachments): --share-token shrXXXX --base-token basXXX --json '{"fields":{"Service Rating":5},"attachments":{"Attachment":["./report.pdf"]}}'`,
		`Cell values in "fields" follow lark-base-cell-value.md conventions; "attachments" maps field names to local file path arrays — the CLI uploads them in parallel and merges them into the submission.`,
	},
	Validate: func(ctx context.Context, runtime *common.RuntimeContext) error {
		return validateFormSubmit(runtime)
	},
	DryRun: dryRunFormSubmit,
	Execute: func(ctx context.Context, runtime *common.RuntimeContext) error {
		return executeFormSubmit(runtime)
	},
}
View Source
var BaseFormUpdate = common.Shortcut{
	Service:     "base",
	Command:     "+form-update",
	Description: "Update a form in a Base table",
	Risk:        "write",
	Scopes:      []string{"base:form:update"},
	AuthTypes:   []string{"user", "bot"},
	HasFormat:   true,
	Flags: []common.Flag{
		{Name: "base-token", Desc: "Base token (base_token)", Required: true},
		{Name: "table-id", Desc: "table ID", Required: true},
		{Name: "form-id", Desc: "form ID", Required: true},
		{Name: "name", Desc: "new form name"},
		{Name: "description", Desc: "new form description (plain text or markdown link like [text](https://example.com))"},
	},
	DryRun: func(ctx context.Context, runtime *common.RuntimeContext) *common.DryRunAPI {
		return common.NewDryRunAPI().
			PATCH("/open-apis/base/v3/bases/:base_token/tables/:table_id/forms/:form_id").
			Set("base_token", runtime.Str("base-token")).
			Set("table_id", runtime.Str("table-id")).
			Set("form_id", runtime.Str("form-id"))
	},
	Execute: func(ctx context.Context, runtime *common.RuntimeContext) error {
		baseToken := runtime.Str("base-token")
		tableId := runtime.Str("table-id")
		formId := runtime.Str("form-id")
		name := runtime.Str("name")
		description := runtime.Str("description")

		body := map[string]interface{}{}
		if name != "" {
			body["name"] = name
		}
		if description != "" {
			body["description"] = description
		}

		data, err := baseV3Call(runtime, "PATCH",
			baseV3Path("bases", baseToken, "tables", tableId, "forms", formId), nil, body)
		if err != nil {
			return err
		}

		runtime.OutFormat(data, nil, func(w io.Writer) {
			output.PrintTable(w, []map[string]interface{}{
				{
					"id":          data["id"],
					"name":        data["name"],
					"description": data["description"],
				},
			})
		})
		return nil
	},
}
View Source
var BaseFormsList = common.Shortcut{
	Service:     "base",
	Command:     "+form-list",
	Description: "List all forms in a Base table (auto-paginated)",
	Risk:        "read",
	Scopes:      []string{"base:form:read"},
	AuthTypes:   []string{"user", "bot"},
	HasFormat:   true,
	Flags: []common.Flag{
		{Name: "base-token", Desc: "Base token (base_token)", Required: true},
		{Name: "table-id", Desc: "table ID", Required: true},
		{Name: "page-size", Type: "int", Default: "100", Desc: "page size per request, range 1-100"},
	},
	Validate: func(ctx context.Context, runtime *common.RuntimeContext) error {
		_, err := common.ValidatePageSizeTyped(runtime, "page-size", 100, 1, 100)
		return err
	},
	DryRun: func(ctx context.Context, runtime *common.RuntimeContext) *common.DryRunAPI {
		return common.NewDryRunAPI().
			GET("/open-apis/base/v3/bases/:base_token/tables/:table_id/forms").
			Params(map[string]interface{}{"page_size": runtime.Int("page-size")}).
			Set("base_token", runtime.Str("base-token")).
			Set("table_id", runtime.Str("table-id"))
	},
	Execute: func(ctx context.Context, runtime *common.RuntimeContext) error {
		baseToken := runtime.Str("base-token")
		tableId := runtime.Str("table-id")

		var allForms []interface{}
		pageToken := ""
		for {
			params := map[string]interface{}{
				"page_size": runtime.Int("page-size"),
			}
			if pageToken != "" {
				params["page_token"] = pageToken
			}

			data, err := baseV3Call(runtime, "GET",
				baseV3Path("bases", baseToken, "tables", tableId, "forms"), params, nil)
			if err != nil {
				return err
			}

			forms, _ := data["forms"].([]interface{})
			allForms = append(allForms, forms...)

			hasMore, _ := data["has_more"].(bool)
			if !hasMore {
				break
			}
			nextToken, _ := data["page_token"].(string)
			if nextToken == "" {
				break
			}
			pageToken = nextToken
		}

		outData := map[string]interface{}{
			"forms": allForms,
			"total": len(allForms),
		}
		runtime.OutFormat(outData, nil, func(w io.Writer) {
			if len(allForms) == 0 {
				fmt.Fprintln(w, "No forms found.")
				return
			}
			var rows []map[string]interface{}
			for _, item := range allForms {
				m, _ := item.(map[string]interface{})
				rows = append(rows, map[string]interface{}{
					"id":          m["id"],
					"name":        m["name"],
					"description": m["description"],
				})
			}
			output.PrintTable(w, rows)
			fmt.Fprintf(w, "\n%d form(s) total\n", len(allForms))
		})
		return nil
	},
}
View Source
var BaseRecordBatchCreate = common.Shortcut{
	Service:     "base",
	Command:     "+record-batch-create",
	Description: "Batch create records",
	Risk:        "write",
	Scopes:      []string{"base:record:create"},
	AuthTypes:   authTypes(),
	Flags: []common.Flag{
		baseTokenFlag(true),
		tableRefFlag(true),
		{Name: "json", Desc: `batch create JSON object, e.g. {"fields":["Name","Status"],"rows":[["Task A","Todo"],["Task B",null]]}; rows follow fields order`, Required: true},
	},
	Tips: append([]string{
		"Happy path fields: fields is the column order; rows is an array of row arrays; each row must match fields order and may use null for empty cells.",
		"Before writing, use +field-list to confirm real writable fields; do not write system fields, formula, lookup, or attachment fields as normal CellValue.",
		"Batch create supports max 200 rows per call.",
		"Use the record-batch-create guide for command limits and edge cases.",
	}, recordCellValueHappyPathTips...),
	Validate: func(ctx context.Context, runtime *common.RuntimeContext) error {
		return validateRecordJSON(runtime)
	},
	DryRun: dryRunRecordBatchCreate,
	Execute: func(ctx context.Context, runtime *common.RuntimeContext) error {
		return executeRecordBatchCreate(runtime)
	},
}
View Source
var BaseRecordBatchUpdate = common.Shortcut{
	Service:     "base",
	Command:     "+record-batch-update",
	Description: "Batch update records",
	Risk:        "write",
	Scopes:      []string{"base:record:update"},
	AuthTypes:   authTypes(),
	Flags: []common.Flag{
		baseTokenFlag(true),
		tableRefFlag(true),
		{Name: "json", Desc: `batch update JSON object, e.g. {"record_id_list":["rec_xxx"],"patch":{"Status":"Done"}}; same patch applies to all records`, Required: true},
	},
	Tips: append([]string{
		"Happy path fields: record_id_list is the target record IDs; patch is a field map applied unchanged to every target record.",
		"Do not use +record-batch-update for per-row different values; call +record-upsert per record or use another supported flow.",
		"Before writing, use +field-list to confirm real writable fields; do not write system fields, formula, lookup, or attachment fields as normal CellValue.",
		"Batch update supports max 200 records per call; use the record-batch-update guide for command limits and edge cases.",
	}, recordCellValueHappyPathTips...),
	Validate: func(ctx context.Context, runtime *common.RuntimeContext) error {
		return validateRecordJSON(runtime)
	},
	DryRun: dryRunRecordBatchUpdate,
	Execute: func(ctx context.Context, runtime *common.RuntimeContext) error {
		return executeRecordBatchUpdate(runtime)
	},
}
View Source
var BaseRecordDelete = common.Shortcut{
	Service:     "base",
	Command:     "+record-delete",
	Description: "Delete one or more records by ID",
	Risk:        "high-risk-write",
	Scopes:      []string{"base:record:delete"},
	AuthTypes:   authTypes(),
	Flags: []common.Flag{
		baseTokenFlag(true),
		tableRefFlag(true),
		{Name: "record-id", Type: "string_array", Desc: "record ID (repeatable)"},
		{Name: "json", Desc: `JSON object with record_id_list, e.g. {"record_id_list":["rec_xxx"]}`},
	},
	Tips: []string{
		baseHighRiskYesTip,
		`Example: lark-cli base +record-delete --base-token <base_token> --table-id <table_id> --record-id <record_id_1> --record-id <record_id_2> --yes`,
	},
	Validate: func(ctx context.Context, runtime *common.RuntimeContext) error {
		return validateRecordSelection(runtime)
	},
	DryRun: dryRunRecordDelete,
	Execute: func(ctx context.Context, runtime *common.RuntimeContext) error {
		return executeRecordDelete(runtime)
	},
}
View Source
var BaseRecordDownloadAttachment = common.Shortcut{
	Service:     "base",
	Command:     "+record-download-attachment",
	Description: "Download Base record attachments by record-id, optionally filtering by file-token",
	Risk:        "read",
	Scopes:      []string{"base:record:read", "docs:document.media:download"},
	AuthTypes:   authTypes(),
	Flags: []common.Flag{
		baseTokenFlag(true),
		tableRefFlag(true),
		recordRefFlag(true),
		{Name: "file-token", Type: "string_array", Desc: "attachment file_token returned by Base; repeat to download selected files; omit to download all attachments in the record", Required: false},
		{Name: "output", Desc: "local save path; with exactly one file token this may be a file path; with multiple or omitted file tokens this must be an existing directory", Required: true},
		{Name: "overwrite", Type: "bool", Desc: "overwrite existing output file"},
	},
	Tips: []string{
		`Example: lark-cli base +record-download-attachment --base-token <base_token> --table-id <table_id> --record-id <record_id> --file-token <file_token> --output ./downloads/`,
		`Omit --file-token to download every attachment in the record.`,
		`Base attachments should be downloaded with this command; other download commands may fail for Base attachment files.`,
		`With one --file-token, --output may be a file path or directory; with multiple or omitted --file-token values, --output must be an existing directory.`,
	},
	DryRun: dryRunRecordDownloadAttachment,
	Validate: func(ctx context.Context, runtime *common.RuntimeContext) error {
		return validateRecordDownloadAttachment(runtime)
	},
	Execute: func(ctx context.Context, runtime *common.RuntimeContext) error {
		return executeRecordDownloadAttachment(ctx, runtime)
	},
}
View Source
var BaseRecordGet = common.Shortcut{
	Service:     "base",
	Command:     "+record-get",
	Description: "Get one or more records by ID",
	Risk:        "read",
	Scopes:      []string{"base:record:read"},
	AuthTypes:   authTypes(),
	Flags: []common.Flag{
		baseTokenFlag(true),
		tableRefFlag(true),
		{Name: "record-id", Type: "string_array", Desc: "record ID (repeatable)"},
		{Name: "field-id", Type: "string_array", Desc: "field ID or name to project; repeat to keep only needed columns"},
		{Name: "json", Desc: `JSON object with record_id_list, e.g. {"record_id_list":["rec_xxx"]}`},
		recordReadFormatFlag(),
	},
	Validate: func(ctx context.Context, runtime *common.RuntimeContext) error {
		if err := validateRecordReadFormat(runtime); err != nil {
			return err
		}
		return validateRecordSelection(runtime)
	},
	Tips: []string{
		"Example: lark-cli base +record-get --base-token <base_token> --table-id <table_id> --record-id <record_id>",
		"Example with projection: lark-cli base +record-get --base-token <base_token> --table-id <table_id> --record-id rec_001 --record-id rec_002 --field-id Name --field-id Status",
		"Default output is markdown; pass --format json to get the raw JSON envelope.",
		"Use --field-id as a projection boundary to avoid loading large cell values into context when they are not needed.",
		"Use +record-get when record_id is already known; otherwise use +record-search or +record-list.",
		"Agent hint: follow the lark-base record read SOP for record read routing.",
	},
	DryRun: dryRunRecordGet,
	PostMount: func(cmd *cobra.Command) {
		preserveFlagOrder(cmd)
	},
	Execute: func(ctx context.Context, runtime *common.RuntimeContext) error {
		return executeRecordGet(runtime)
	},
}
View Source
var BaseRecordHistoryList = common.Shortcut{
	Service:     "base",
	Command:     "+record-history-list",
	Description: "List record change history",
	Risk:        "read",
	Scopes:      []string{"base:history:read"},
	AuthTypes:   authTypes(),
	Flags: []common.Flag{
		baseTokenFlag(true),
		tableRefFlag(true),
		recordRefFlag(true),
		{Name: "max-version", Type: "int", Desc: "max version for next page"},
		{Name: "page-size", Type: "int", Default: "30", Desc: "pagination size, range 1-50"},
	},
	Tips: []string{
		`Example: lark-cli base +record-history-list --base-token <base_token> --table-id <table_id> --record-id <record_id>`,
		"This reads one record's history only; it is not a table-wide audit scan.",
	},
	Validate: func(ctx context.Context, runtime *common.RuntimeContext) error {
		_, err := common.ValidatePageSizeTyped(runtime, "page-size", 30, 1, 50)
		return err
	},
	DryRun: dryRunRecordHistoryList,
	Execute: func(ctx context.Context, runtime *common.RuntimeContext) error {
		params := map[string]interface{}{
			"table_id":  baseTableID(runtime),
			"record_id": runtime.Str("record-id"),
			"page_size": runtime.Int("page-size"),
		}
		if value := runtime.Int("max-version"); value > 0 {
			params["max_version"] = value
		}
		data, err := baseV3Call(runtime, "GET", baseV3Path("bases", runtime.Str("base-token"), "record_history"), params, nil)
		if err != nil {
			return err
		}
		runtime.Out(data, nil)
		return nil
	},
}
View Source
var BaseRecordList = common.Shortcut{
	Service:     "base",
	Command:     "+record-list",
	Description: "List records in a table",
	Risk:        "read",
	Scopes:      []string{"base:record:read"},
	AuthTypes:   authTypes(),
	Flags: []common.Flag{
		baseTokenFlag(true),
		tableRefFlag(true),
		recordListFieldRefFlag(),
		recordListViewRefFlag(),
		recordFilterFlag(),
		recordSortFlag(),
		{Name: "offset", Type: "int", Default: "0", Desc: "pagination offset"},
		{Name: "limit", Type: "int", Default: "100", Desc: "pagination size, range 1-200"},
		pageSizeLimitAliasFlag(),
		recordReadFormatFlag(),
	},
	Tips: []string{
		"Example: lark-cli base +record-list --base-token <base_token> --table-id <table_id> --limit 50",
		"Example with projection: lark-cli base +record-list --base-token <base_token> --table-id <table_id> --field-id Name --field-id Status --limit 50",
		`Text equality filter: --filter-json '{"logic":"and","conditions":[["Title","==","Launch plan"]]}'`,
		`Text contains/like filter: --filter-json '{"logic":"and","conditions":[["Title","intersects","urgent"]]}'`,
		`Number equality filter: --filter-json '{"logic":"and","conditions":[["Score","==",95]]}'`,
		`Date equality filter: --filter-json '{"logic":"and","conditions":[["Due Date","==","ExactDate(2026-06-02)"]]}'`,
		`Option intersection filter: --filter-json '{"logic":"and","conditions":[["Tags","intersects",["P0","Blocked"]]]}'`,
		`Sort priority follows --sort-json array order: --sort-json '[{"field":"Updated","desc":true},{"field":"Title","desc":false}]'`,
		formatRecordQueryPriorityTip(),
		"Default output is markdown; pass --format json to get the raw JSON envelope.",
		"Use --field-id repeatedly to keep output small and aligned with the task.",
	},
	Validate: func(ctx context.Context, runtime *common.RuntimeContext) error {
		if err := validateRecordReadFormat(runtime); err != nil {
			return err
		}
		if err := validateLimitPageSizeAlias(runtime); err != nil {
			return err
		}
		if _, err := common.ValidatePageSizeTyped(runtime, "limit", 100, 1, 200); err != nil {
			return err
		}
		if runtime.Changed("page-size") {
			if _, err := common.ValidatePageSizeTyped(runtime, "page-size", 100, 1, 200); err != nil {
				return err
			}
		}
		return validateRecordQueryOptions(runtime)
	},
	DryRun: dryRunRecordList,
	PostMount: func(cmd *cobra.Command) {
		preserveFlagOrder(cmd)
	},
	Execute: func(ctx context.Context, runtime *common.RuntimeContext) error {
		return executeRecordList(runtime)
	},
}
View Source
var BaseRecordRemoveAttachment = common.Shortcut{
	Service:     "base",
	Command:     "+record-remove-attachment",
	Description: "Remove one or more file_token values from a Base record attachment cell",
	Risk:        "high-risk-write",
	Scopes:      []string{"base:record:update", "base:field:read"},
	AuthTypes:   authTypes(),
	Flags: []common.Flag{
		baseTokenFlag(true),
		tableRefFlag(true),
		recordRefFlag(true),
		fieldRefFlag(true),
		{Name: "file-token", Type: "string_array", Desc: "attachment file_token to remove from the target cell; repeat to remove multiple attachments; max 50 tokens", Required: true},
	},
	Tips: []string{
		baseHighRiskYesTip,
		`Example: lark-cli base +record-remove-attachment --base-token <base_token> --table-id <table_id> --record-id <record_id> --field-id <attachment_field_id> --file-token <file_token> --yes`,
		`Repeat --file-token to remove multiple attachments from the same cell in one call.`,
		`This is a high-risk write command and requires --yes.`,
	},
	DryRun: dryRunRecordRemoveAttachment,
	Validate: func(ctx context.Context, runtime *common.RuntimeContext) error {
		return validateRecordRemoveAttachment(runtime)
	},
	Execute: func(ctx context.Context, runtime *common.RuntimeContext) error {
		return executeRecordRemoveAttachment(runtime)
	},
}
View Source
var BaseRecordSearch = common.Shortcut{
	Service:     "base",
	Command:     "+record-search",
	Description: "Search records in a table",
	Risk:        "read",
	Scopes:      []string{"base:record:read"},
	AuthTypes:   authTypes(),
	Flags: []common.Flag{
		baseTokenFlag(true),
		tableRefFlag(true),
		{Name: "json", Desc: `record search JSON object for the full request body, e.g. {"keyword":"Alice","search_fields":["Name"],"select_fields":["Name","Status"],"filter":{"logic":"and","conditions":[]},"sort":[{"field":"Updated","desc":true}],"limit":50}; escape hatch for advanced cases`},
		{Name: "keyword", Desc: "keyword for record search; required unless --json is used"},
		{Name: "search-field", Type: "string_array", Desc: "field ID or name to search; repeat for multiple fields; required unless --json is used"},
		recordListFieldRefFlag(),
		recordListViewRefFlag(),
		recordFilterFlag(),
		recordSortFlag(),
		{Name: "offset", Type: "int", Default: "0", Desc: "pagination offset"},
		{Name: "limit", Type: "int", Default: "10", Desc: "pagination size, range 1-200"},
		pageSizeLimitAliasFlag(),
		recordReadFormatFlag(),
	},
	Tips: []string{
		`Happy path fields: keyword (string), search_fields (1-20 field names/ids), select_fields (optional projection, <=50), view_id (optional), offset (default 0), limit (default 10, range 1-200).`,
		"JSON constraints: keyword length >=1; search_fields length 1-20; select_fields length <=50; offset >=0 defaults to 0; limit range 1-200 defaults to 10.",
		"view_id scopes search to records in that view; when select_fields is omitted, returned fields follow that view's visible fields.",
		`Example: lark-cli base +record-search --base-token <base_token> --table-id <table_id> --keyword Alice --search-field Name --field-id Name --field-id Status --limit 20`,
		`Example with filter/sort JSON: lark-cli base +record-search --base-token <base_token> --table-id <table_id> --keyword Alice --search-field Name --filter-json @filter.json --sort-json '[{"field":"Updated","desc":true}]'`,
		`Text equality filter: --filter-json '{"logic":"and","conditions":[["Title","==","Launch plan"]]}'`,
		`Text contains/like filter: --filter-json '{"logic":"and","conditions":[["Title","intersects","urgent"]]}'`,
		`Option intersection filter: --filter-json '{"logic":"and","conditions":[["Tags","intersects",["P0","Blocked"]]]}'`,
		`Sort priority follows --sort-json array order.`,
		formatRecordQueryPriorityTip(),
		"Use +record-search for keyword matching; use --filter-json for structured conditions and --sort-json for result ordering.",
		"Use --json only when you need to pass the full search body directly.",
		"Default output is markdown; pass --format json to get the raw JSON envelope.",
	},
	Validate: func(ctx context.Context, runtime *common.RuntimeContext) error {
		return validateRecordSearchFlags(runtime)
	},
	DryRun: dryRunRecordSearch,
	PostMount: func(cmd *cobra.Command) {
		preserveFlagOrder(cmd)
	},
	Execute: func(ctx context.Context, runtime *common.RuntimeContext) error {
		return executeRecordSearch(runtime)
	},
}
View Source
var BaseRecordShareLinkCreate = common.Shortcut{
	Service:     "base",
	Command:     "+record-share-link-create",
	Description: "Generate share links for one or more records (max 100 per request)",
	Risk:        "read",
	Scopes:      []string{"base:record:read"},
	AuthTypes:   authTypes(),
	Flags: []common.Flag{
		baseTokenFlag(true),
		tableRefFlag(true),
		{Name: "record-ids", Type: "string_slice", Desc: "record IDs to generate share links for (comma-separated or repeatable, max 100)", Required: true},
	},
	Tips: []string{
		`Example: lark-cli base +record-share-link-create --base-token <base_token> --table-id <table_id> --record-ids <record_id>`,
		"Max 100 record IDs per call; duplicate IDs are ignored.",
		"Output record_share_links maps record_id to URL; records without permission or missing records may be absent.",
	},
	Validate: func(ctx context.Context, runtime *common.RuntimeContext) error {
		return validateRecordShareBatch(runtime)
	},
	DryRun: dryRunRecordShareBatch,
	Execute: func(ctx context.Context, runtime *common.RuntimeContext) error {
		return executeRecordShareBatch(runtime)
	},
}
View Source
var BaseRecordUploadAttachment = common.Shortcut{
	Service:     "base",
	Command:     "+record-upload-attachment",
	Description: "Upload one or more local files and append the returned file_token values to a Base attachment cell",
	Risk:        "write",
	Scopes:      []string{"base:record:update", "base:field:read", "docs:document.media:upload"},
	AuthTypes:   authTypes(),
	Flags: []common.Flag{
		baseTokenFlag(true),
		tableRefFlag(true),
		recordRefFlag(true),
		fieldRefFlag(true),
		{Name: "file", Type: "string_array", Desc: "local file path; repeat to append multiple attachments in one cell; max 50 files, max 2GB each; files > 20MB use multipart upload automatically", Required: true},
		{Name: "name", Desc: "deprecated; attachment names are derived from local file basenames", Hidden: true},
	},
	Tips: []string{
		`Example: lark-cli base +record-upload-attachment --base-token <base_token> --table-id <table_id> --record-id <record_id> --field-id <attachment_field_id> --file ./report.pdf`,
		`Repeat --file to append multiple attachments: --file ./report.pdf --file ./screenshot.png`,
		`Reuse returned file_token values for download/remove`,
	},
	DryRun: dryRunRecordUploadAttachment,
	Validate: func(ctx context.Context, runtime *common.RuntimeContext) error {
		return validateRecordUploadAttachment(runtime)
	},
	Execute: func(ctx context.Context, runtime *common.RuntimeContext) error {
		return executeRecordUploadAttachment(runtime)
	},
}
View Source
var BaseRecordUpsert = common.Shortcut{
	Service:     "base",
	Command:     "+record-upsert",
	Description: "Create or update a record",
	Risk:        "write",
	Scopes:      []string{"base:record:create", "base:record:update"},
	AuthTypes:   authTypes(),
	Flags: []common.Flag{
		baseTokenFlag(true),
		tableRefFlag(true),
		recordRefFlag(false),
		{Name: "json", Desc: `record field map JSON object, e.g. {"Name":"Alice","Status":"Todo"}; do not wrap in fields`, Required: true},
	},
	Tips: append([]string{
		"Happy path JSON is a top-level field map: each key is a real field name or field ID, each value is that field's CellValue.",
		"Without --record-id this creates a record; with --record-id this updates that record. It does not auto-upsert by business key.",
		"Before writing, use +field-list to confirm real writable fields; do not write system fields, formula, lookup, or attachment fields as normal CellValue.",
		"Use the record-upsert guide for command limits and edge cases.",
	}, recordCellValueHappyPathTips...),
	Validate: func(ctx context.Context, runtime *common.RuntimeContext) error {
		return validateRecordJSON(runtime)
	},
	DryRun: dryRunRecordUpsert,
	Execute: func(ctx context.Context, runtime *common.RuntimeContext) error {
		return executeRecordUpsert(runtime)
	},
}
View Source
var BaseRoleCreate = common.Shortcut{
	Service:     "base",
	Command:     "+role-create",
	Description: "Create a custom role in a Base",
	Risk:        "write",
	Scopes:      []string{"base:role:create"},
	AuthTypes:   []string{"user", "bot"},
	Flags: []common.Flag{
		{Name: "base-token", Desc: "base token", Required: true},
		{Name: "json", Desc: "role config JSON; read lark-base-role-guide.md and role-config.md before constructing permissions", Required: true},
	},
	Tips: []string{
		"Requires advanced permissions to be enabled and the caller to be a Base admin.",
		"Use lark-base-role-guide.md as the entry guide and role-config.md as the role permission JSON SSOT.",
		"Create supports custom_role only.",
	},
	Validate: func(ctx context.Context, runtime *common.RuntimeContext) error {
		if strings.TrimSpace(runtime.Str("base-token")) == "" {
			return baseFlagErrorf("--base-token must not be blank")
		}
		var body map[string]any
		if err := json.Unmarshal([]byte(runtime.Str("json")), &body); err != nil {
			return baseFlagErrorf("--json must be valid JSON: %v", err)
		}
		return nil
	},
	DryRun: func(ctx context.Context, runtime *common.RuntimeContext) *common.DryRunAPI {
		var body map[string]any
		json.Unmarshal([]byte(runtime.Str("json")), &body)
		return common.NewDryRunAPI().
			POST("/open-apis/base/v3/bases/:base_token/roles").
			Body(body).
			Set("base_token", runtime.Str("base-token"))
	},
	Execute: func(ctx context.Context, runtime *common.RuntimeContext) error {
		baseToken := runtime.Str("base-token")
		var body map[string]any
		json.Unmarshal([]byte(runtime.Str("json")), &body)

		apiResp, err := runtime.DoAPI(&larkcore.ApiReq{
			HttpMethod: http.MethodPost,
			ApiPath:    fmt.Sprintf("/open-apis/base/v3/bases/%s/roles", validate.EncodePathSegment(baseToken)),
			Body:       body,
		})
		if err != nil {
			return err
		}

		return handleRoleAPIResponse(runtime, apiResp, "create role failed")
	},
}
View Source
var BaseRoleDelete = common.Shortcut{
	Service:     "base",
	Command:     "+role-delete",
	Description: "Delete a custom role (system roles cannot be deleted)",
	Risk:        "high-risk-write",
	Scopes:      []string{"base:role:delete"},
	AuthTypes:   []string{"user", "bot"},
	Flags: []common.Flag{
		{Name: "base-token", Desc: "base token", Required: true},
		{Name: "role-id", Desc: "role ID (e.g. rolxxxxxx4)", Required: true},
	},
	Tips: []string{
		baseHighRiskYesTip,
		"Requires advanced permissions to be enabled and the caller to be a Base admin.",
		"Only custom roles can be deleted; system roles cannot be deleted.",
		"Use +role-get first if the role target is ambiguous, then pass --yes to confirm deletion.",
	},
	Validate: func(ctx context.Context, runtime *common.RuntimeContext) error {
		if strings.TrimSpace(runtime.Str("base-token")) == "" {
			return baseFlagErrorf("--base-token must not be blank")
		}
		if strings.TrimSpace(runtime.Str("role-id")) == "" {
			return baseFlagErrorf("--role-id must not be blank")
		}
		return nil
	},
	DryRun: func(ctx context.Context, runtime *common.RuntimeContext) *common.DryRunAPI {
		return common.NewDryRunAPI().
			DELETE("/open-apis/base/v3/bases/:base_token/roles/:role_id").
			Set("base_token", runtime.Str("base-token")).
			Set("role_id", runtime.Str("role-id"))
	},
	Execute: func(ctx context.Context, runtime *common.RuntimeContext) error {
		baseToken := runtime.Str("base-token")
		roleId := runtime.Str("role-id")

		apiResp, err := runtime.DoAPI(&larkcore.ApiReq{
			HttpMethod: http.MethodDelete,
			ApiPath:    fmt.Sprintf("/open-apis/base/v3/bases/%s/roles/%s", validate.EncodePathSegment(baseToken), validate.EncodePathSegment(roleId)),
			Body:       map[string]any{},
		})
		if err != nil {
			return err
		}

		return handleRoleAPIResponse(runtime, apiResp, "delete role failed")
	},
}
View Source
var BaseRoleGet = common.Shortcut{
	Service:     "base",
	Command:     "+role-get",
	Description: "Get full config of a role",
	Risk:        "read",
	Scopes:      []string{"base:role:read"},
	AuthTypes:   []string{"user", "bot"},
	HasFormat:   true,
	Flags: []common.Flag{
		{Name: "base-token", Desc: "base token", Required: true},
		{Name: "role-id", Desc: "role ID (e.g. rolxxxxxx4)", Required: true},
	},
	Tips: []string{
		"Requires advanced permissions to be enabled and the caller to be a Base admin.",
		"Use before +role-update to inspect the current full permission config.",
	},
	Validate: func(ctx context.Context, runtime *common.RuntimeContext) error {
		if strings.TrimSpace(runtime.Str("base-token")) == "" {
			return baseFlagErrorf("--base-token must not be blank")
		}
		if strings.TrimSpace(runtime.Str("role-id")) == "" {
			return baseFlagErrorf("--role-id must not be blank")
		}
		return nil
	},
	DryRun: func(ctx context.Context, runtime *common.RuntimeContext) *common.DryRunAPI {
		return common.NewDryRunAPI().
			GET("/open-apis/base/v3/bases/:base_token/roles/:role_id").
			Set("base_token", runtime.Str("base-token")).
			Set("role_id", runtime.Str("role-id"))
	},
	Execute: func(ctx context.Context, runtime *common.RuntimeContext) error {
		baseToken := runtime.Str("base-token")
		roleId := runtime.Str("role-id")

		apiResp, err := runtime.DoAPI(&larkcore.ApiReq{
			HttpMethod: http.MethodGet,
			ApiPath:    fmt.Sprintf("/open-apis/base/v3/bases/%s/roles/%s", validate.EncodePathSegment(baseToken), validate.EncodePathSegment(roleId)),
		})
		if err != nil {
			return err
		}

		return handleRoleAPIResponse(runtime, apiResp, "get role failed")
	},
}
View Source
var BaseRoleList = common.Shortcut{
	Service:     "base",
	Command:     "+role-list",
	Description: "List all roles in a Base",
	Risk:        "read",
	Scopes:      []string{"base:role:read"},
	AuthTypes:   []string{"user", "bot"},
	HasFormat:   true,
	Flags: []common.Flag{
		{Name: "base-token", Desc: "base token", Required: true},
	},
	Tips: []string{
		"Requires advanced permissions to be enabled and the caller to be a Base admin.",
		"Returns role summaries; use +role-get for the full permission config.",
	},
	Validate: func(ctx context.Context, runtime *common.RuntimeContext) error {
		if strings.TrimSpace(runtime.Str("base-token")) == "" {
			return baseFlagErrorf("--base-token must not be blank")
		}
		return nil
	},
	DryRun: func(ctx context.Context, runtime *common.RuntimeContext) *common.DryRunAPI {
		return common.NewDryRunAPI().
			GET("/open-apis/base/v3/bases/:base_token/roles").
			Set("base_token", runtime.Str("base-token"))
	},
	Execute: func(ctx context.Context, runtime *common.RuntimeContext) error {
		baseToken := runtime.Str("base-token")

		apiResp, err := runtime.DoAPI(&larkcore.ApiReq{
			HttpMethod: http.MethodGet,
			ApiPath:    fmt.Sprintf("/open-apis/base/v3/bases/%s/roles", validate.EncodePathSegment(baseToken)),
		})
		if err != nil {
			return err
		}

		return handleRoleAPIResponse(runtime, apiResp, "list roles failed")
	},
}
View Source
var BaseRoleUpdate = common.Shortcut{
	Service:     "base",
	Command:     "+role-update",
	Description: "Update a role config (delta merge, only changed fields needed)",
	Risk:        "high-risk-write",
	Scopes:      []string{"base:role:update"},
	AuthTypes:   []string{"user", "bot"},
	Flags: []common.Flag{
		{Name: "base-token", Desc: "base token", Required: true},
		{Name: "role-id", Desc: "role ID (e.g. rolxxxxxx4)", Required: true},
		{Name: "json", Desc: "delta role config JSON; read lark-base-role-guide.md and role-config.md before changing permissions", Required: true},
	},
	Tips: []string{
		baseHighRiskYesTip,
		"Requires advanced permissions to be enabled and the caller to be a Base admin.",
		"Update is a delta merge: only changed fields are updated, others remain unchanged.",
		"Use lark-base-role-guide.md as the entry guide and role-config.md as the role permission JSON SSOT.",
	},
	Validate: func(ctx context.Context, runtime *common.RuntimeContext) error {
		if strings.TrimSpace(runtime.Str("base-token")) == "" {
			return baseFlagErrorf("--base-token must not be blank")
		}
		if strings.TrimSpace(runtime.Str("role-id")) == "" {
			return baseFlagErrorf("--role-id must not be blank")
		}
		var body map[string]any
		if err := json.Unmarshal([]byte(runtime.Str("json")), &body); err != nil {
			return baseFlagErrorf("--json must be valid JSON: %v", err)
		}
		return nil
	},
	DryRun: func(ctx context.Context, runtime *common.RuntimeContext) *common.DryRunAPI {
		var body map[string]any
		json.Unmarshal([]byte(runtime.Str("json")), &body)
		return common.NewDryRunAPI().
			Desc("Delta merge: only changed fields are updated, others remain unchanged").
			PUT("/open-apis/base/v3/bases/:base_token/roles/:role_id").
			Body(body).
			Set("base_token", runtime.Str("base-token")).
			Set("role_id", runtime.Str("role-id"))
	},
	Execute: func(ctx context.Context, runtime *common.RuntimeContext) error {
		baseToken := runtime.Str("base-token")
		roleId := runtime.Str("role-id")
		var body map[string]any
		json.Unmarshal([]byte(runtime.Str("json")), &body)

		apiResp, err := runtime.DoAPI(&larkcore.ApiReq{
			HttpMethod: http.MethodPut,
			ApiPath:    fmt.Sprintf("/open-apis/base/v3/bases/%s/roles/%s", validate.EncodePathSegment(baseToken), validate.EncodePathSegment(roleId)),
			Body:       body,
		})
		if err != nil {
			return err
		}

		return handleRoleAPIResponse(runtime, apiResp, "update role failed")
	},
}
View Source
var BaseTableCreate = common.Shortcut{
	Service:     "base",
	Command:     "+table-create",
	Description: "Create a table and optional fields/views",
	Risk:        "write",
	Scopes:      []string{"base:table:create", "base:field:read", "base:field:create", "base:field:update", "base:view:write_only"},
	AuthTypes:   authTypes(),
	Flags: []common.Flag{
		baseTokenFlag(true),
		{Name: "name", Desc: "table name", Required: true},
		{Name: "view", Desc: "view JSON object/array for create"},
		{Name: "fields", Desc: `field JSON array for create, e.g. [{"name":"Title","type":"text"},{"name":"Status","type":"select","options":[{"name":"Todo"},{"name":"Done"}]}]`},
	},
	Tips: []string{
		"Before using --fields, read lark-base-field-json.md or rely on the same field JSON shape used by +field-create; do not invent field properties.",
		"The first --fields item becomes the primary field.",
	},
	Validate: func(ctx context.Context, runtime *common.RuntimeContext) error {
		return validateTableCreate(runtime)
	},
	DryRun: dryRunTableCreate,
	Execute: func(ctx context.Context, runtime *common.RuntimeContext) error {
		return executeTableCreate(runtime)
	},
}
View Source
var BaseTableDelete = common.Shortcut{
	Service:     "base",
	Command:     "+table-delete",
	Description: "Delete a table by ID or name",
	Risk:        "high-risk-write",
	Scopes:      []string{"base:table:delete"},
	AuthTypes:   authTypes(),
	Flags:       []common.Flag{baseTokenFlag(true), tableRefFlag(true)},
	Tips: []string{
		`Example: lark-cli base +table-delete --base-token <base_token> --table-id "Old Tasks" --yes`,
		"table-id accepts a table ID (tbl...) or the table name in the current Base.",
		baseHighRiskYesTip,
	},
	DryRun: dryRunTableDelete,
	Execute: func(ctx context.Context, runtime *common.RuntimeContext) error {
		return executeTableDelete(runtime)
	},
}
View Source
var BaseTableGet = common.Shortcut{
	Service:     "base",
	Command:     "+table-get",
	Description: "Get a table by ID or name",
	Risk:        "read",
	Scopes:      []string{"base:table:read", "base:field:read", "base:view:read"},
	AuthTypes:   authTypes(),
	Flags:       []common.Flag{baseTokenFlag(true), tableRefFlag(true)},
	Tips: []string{
		`Example: lark-cli base +table-get --base-token <base_token> --table-id "Tasks"`,
		"table-id accepts a table ID (tbl...) or the table name in the current Base.",
	},
	DryRun: dryRunTableGet,
	Execute: func(ctx context.Context, runtime *common.RuntimeContext) error {
		return executeTableGet(runtime)
	},
}
View Source
var BaseTableList = common.Shortcut{
	Service:     "base",
	Command:     "+table-list",
	Description: "List tables in a base",
	Risk:        "read",
	Scopes:      []string{"base:table:read"},
	AuthTypes:   authTypes(),
	Flags: []common.Flag{
		baseTokenFlag(true),
		{Name: "offset", Type: "int", Default: "0", Desc: "pagination offset"},
		{Name: "limit", Type: "int", Default: "50", Desc: "pagination size, range 1-100"},
		pageSizeLimitAliasFlag(),
	},
	Validate: func(ctx context.Context, runtime *common.RuntimeContext) error {
		if err := validateLimitPageSizeAlias(runtime); err != nil {
			return err
		}
		if _, err := common.ValidatePageSizeTyped(runtime, "limit", 50, 1, 100); err != nil {
			return err
		}
		if runtime.Changed("page-size") {
			if _, err := common.ValidatePageSizeTyped(runtime, "page-size", 50, 1, 100); err != nil {
				return err
			}
		}
		return nil
	},
	DryRun: dryRunTableList,
	Execute: func(ctx context.Context, runtime *common.RuntimeContext) error {
		return executeTableList(runtime)
	},
}
View Source
var BaseTableUpdate = common.Shortcut{
	Service:     "base",
	Command:     "+table-update",
	Description: "Rename a table by ID or name",
	Risk:        "write",
	Scopes:      []string{"base:table:update"},
	AuthTypes:   authTypes(),
	Flags: []common.Flag{
		baseTokenFlag(true),
		tableRefFlag(true),
		{Name: "name", Desc: "new table name", Required: true},
	},
	DryRun: dryRunTableUpdate,
	Execute: func(ctx context.Context, runtime *common.RuntimeContext) error {
		return executeTableUpdate(runtime)
	},
}
View Source
var BaseTitleResolve = common.Shortcut{
	Service:     "base",
	Command:     "+title-resolve",
	Description: "Resolve a Base title or keyword through Drive search",
	Risk:        "read",
	Scopes:      []string{"search:docs:read"},
	AuthTypes:   []string{"user"},
	HasFormat:   true,
	Flags: []common.Flag{
		{Name: "title", Desc: "Base title keyword to search via Drive (30 characters or fewer)"},
		{Name: "query", Hidden: true, Desc: "Alias for --title; accepted to recover from AI routing mistakes"},
		{Name: "url", Hidden: true, Desc: "Alias for --title; accepted to recover from AI routing mistakes"},
	},
	Tips: []string{
		`Example: lark-cli base +title-resolve --title "Sales pipeline"`,
		"Pass a short keyword from the Base title, 30 characters or fewer. Use +url-resolve for URLs.",
	},
	Validate: func(ctx context.Context, runtime *common.RuntimeContext) error {
		_, err := readTitleResolveQuery(runtime)
		return err
	},
	DryRun: func(ctx context.Context, runtime *common.RuntimeContext) *common.DryRunAPI {
		query, err := readTitleResolveQuery(runtime)
		if err != nil {
			return common.NewDryRunAPI().Set("error", err.Error())
		}
		return common.NewDryRunAPI().
			POST("/open-apis/search/v2/doc_wiki/search").
			Body(buildTitleResolveSearchBody(query))
	},
	Execute: func(ctx context.Context, runtime *common.RuntimeContext) error {
		return executeBaseTitleResolve(runtime)
	},
}
View Source
var BaseURLResolve = common.Shortcut{
	Service:     "base",
	Command:     "+url-resolve",
	Description: "Resolve a Base-related URL into Base coordinates",
	Risk:        "read",
	Scopes:      []string{},
	ConditionalScopes: []string{
		"base:field:read",
		"base:record:read",
		"wiki:node:retrieve",
	},
	AuthTypes: authTypes(),
	HasFormat: true,
	Flags: []common.Flag{
		{Name: "url", Desc: "Base/Wiki/record-share URL to resolve"},
		{Name: "query", Hidden: true, Desc: "Alias for --url; accepted to recover from AI routing mistakes"},
	},
	Tips: []string{
		`Example: lark-cli base +url-resolve --url "https://example.larkoffice.com/base/<base_token>?table=<table_id>&view=<view_id>"`,
		"Only URLs are accepted. For Base titles or keywords, use +title-resolve --title.",
	},
	Validate: func(ctx context.Context, runtime *common.RuntimeContext) error {
		_, err := readURLResolveInput(runtime)
		return err
	},
	DryRun: func(ctx context.Context, runtime *common.RuntimeContext) *common.DryRunAPI {
		raw, err := readURLResolveInput(runtime)
		if err != nil {
			return common.NewDryRunAPI().Set("error", err.Error())
		}
		parsed, err := parseResolveURL(raw)
		if err != nil {
			return common.NewDryRunAPI().Set("error", err.Error())
		}
		switch classifyBaseURL(parsed) {
		case "wiki_url":
			return common.NewDryRunAPI().
				GET("/open-apis/wiki/v2/spaces/get_node").
				Params(map[string]interface{}{"token": firstPathSegmentAfter(parsed.Path, "/wiki/")})
		case "record_share_url":
			return common.NewDryRunAPI().
				GET("/open-apis/base/v3/record_share/:record_share_token/meta").
				Set("record_share_token", firstPathSegmentAfter(parsed.Path, "/record/"))
		default:
			return common.NewDryRunAPI().Set("url", raw).Set("resolution", "local")
		}
	},
	Execute: func(ctx context.Context, runtime *common.RuntimeContext) error {
		return executeBaseURLResolve(runtime)
	},
}
View Source
var BaseViewCreate = common.Shortcut{
	Service:     "base",
	Command:     "+view-create",
	Description: "Create one or more views",
	Risk:        "write",
	Scopes:      []string{"base:view:write_only"},
	AuthTypes:   authTypes(),
	Flags: []common.Flag{
		baseTokenFlag(true),
		tableRefFlag(true),
		{Name: "json", Desc: "view JSON object/array; type defaults to grid; type range: grid, kanban, gallery, calendar, gantt", Required: true},
	},
	Tips: []string{
		`Example: lark-cli base +view-create --base-token <base_token> --table-id <table_id> --json '{"name":"Main","type":"grid"}'`,
		`Minimal: --json '{"name":"Main"}' creates a grid view.`,
		"Do not pass form as a view type; form views are managed through form commands.",
		`Use +view-set-visible-fields after creation when the user needs a specific field order or visibility.`,
	},
	Validate: func(ctx context.Context, runtime *common.RuntimeContext) error {
		return validateViewCreate(runtime)
	},
	DryRun: dryRunViewCreate,
	Execute: func(ctx context.Context, runtime *common.RuntimeContext) error {
		return executeViewCreate(runtime)
	},
}
View Source
var BaseViewDelete = common.Shortcut{
	Service:     "base",
	Command:     "+view-delete",
	Description: "Delete a view by ID or name",
	Risk:        "high-risk-write",
	Scopes:      []string{"base:view:write_only"},
	AuthTypes:   authTypes(),
	Flags:       []common.Flag{baseTokenFlag(true), tableRefFlag(true), viewRefFlag(true)},
	Tips: []string{
		baseHighRiskYesTip,
		`Example: lark-cli base +view-delete --base-token <base_token> --table-id <table_id> --view-id "Old View" --yes`,
	},
	DryRun: dryRunViewDelete,
	Execute: func(ctx context.Context, runtime *common.RuntimeContext) error {
		return executeViewDelete(runtime)
	},
}
View Source
var BaseViewGet = common.Shortcut{
	Service:     "base",
	Command:     "+view-get",
	Description: "Get a view by ID or name",
	Risk:        "read",
	Scopes:      []string{"base:view:read"},
	AuthTypes:   authTypes(),
	Flags:       []common.Flag{baseTokenFlag(true), tableRefFlag(true), viewRefFlag(true)},
	DryRun:      dryRunViewGet,
	Execute: func(ctx context.Context, runtime *common.RuntimeContext) error {
		return executeViewGet(runtime)
	},
}
View Source
var BaseViewGetCard = common.Shortcut{
	Service:     "base",
	Command:     "+view-get-card",
	Description: "Get view card configuration",
	Risk:        "read",
	Scopes:      []string{"base:view:read"},
	AuthTypes:   authTypes(),
	Flags:       []common.Flag{baseTokenFlag(true), tableRefFlag(true), viewRefFlag(true)},
	DryRun:      dryRunViewGetCard,
	Execute: func(ctx context.Context, runtime *common.RuntimeContext) error {
		return executeViewGetProperty(runtime, "card", "card")
	},
}
View Source
var BaseViewGetFilter = common.Shortcut{
	Service:     "base",
	Command:     "+view-get-filter",
	Description: "Get view filter configuration",
	Risk:        "read",
	Scopes:      []string{"base:view:read"},
	AuthTypes:   authTypes(),
	Flags:       []common.Flag{baseTokenFlag(true), tableRefFlag(true), viewRefFlag(true)},
	DryRun:      dryRunViewGetFilter,
	Execute: func(ctx context.Context, runtime *common.RuntimeContext) error {
		return executeViewGetProperty(runtime, "filter", "filter")
	},
}
View Source
var BaseViewGetGroup = common.Shortcut{
	Service:     "base",
	Command:     "+view-get-group",
	Description: "Get view group configuration",
	Risk:        "read",
	Scopes:      []string{"base:view:read"},
	AuthTypes:   authTypes(),
	Flags:       []common.Flag{baseTokenFlag(true), tableRefFlag(true), viewRefFlag(true)},
	DryRun:      dryRunViewGetGroup,
	Execute: func(ctx context.Context, runtime *common.RuntimeContext) error {
		return executeViewGetProperty(runtime, "group", "group")
	},
}
View Source
var BaseViewGetSort = common.Shortcut{
	Service:     "base",
	Command:     "+view-get-sort",
	Description: "Get view sort configuration",
	Risk:        "read",
	Scopes:      []string{"base:view:read"},
	AuthTypes:   authTypes(),
	Flags:       []common.Flag{baseTokenFlag(true), tableRefFlag(true), viewRefFlag(true)},
	DryRun:      dryRunViewGetSort,
	Execute: func(ctx context.Context, runtime *common.RuntimeContext) error {
		return executeViewGetProperty(runtime, "sort", "sort")
	},
}
View Source
var BaseViewGetTimebar = common.Shortcut{
	Service:     "base",
	Command:     "+view-get-timebar",
	Description: "Get view timebar configuration",
	Risk:        "read",
	Scopes:      []string{"base:view:read"},
	AuthTypes:   authTypes(),
	Flags:       []common.Flag{baseTokenFlag(true), tableRefFlag(true), viewRefFlag(true)},
	DryRun:      dryRunViewGetTimebar,
	Execute: func(ctx context.Context, runtime *common.RuntimeContext) error {
		return executeViewGetProperty(runtime, "timebar", "timebar")
	},
}
View Source
var BaseViewGetVisibleFields = common.Shortcut{
	Service:     "base",
	Command:     "+view-get-visible-fields",
	Description: "Get view visible fields configuration",
	Risk:        "read",
	Scopes:      []string{"base:view:read"},
	AuthTypes:   authTypes(),
	Flags:       []common.Flag{baseTokenFlag(true), tableRefFlag(true), viewRefFlag(true)},
	DryRun:      dryRunViewGetVisibleFields,
	Execute: func(ctx context.Context, runtime *common.RuntimeContext) error {
		return executeViewGetProperty(runtime, "visible_fields", "visible_fields")
	},
}
View Source
var BaseViewList = common.Shortcut{
	Service:     "base",
	Command:     "+view-list",
	Description: "List views in a table",
	Risk:        "read",
	Scopes:      []string{"base:view:read"},
	AuthTypes:   authTypes(),
	Flags: []common.Flag{
		baseTokenFlag(true),
		tableRefFlag(true),
		{Name: "offset", Type: "int", Default: "0", Desc: "pagination offset"},
		{Name: "limit", Type: "int", Default: "100", Desc: "pagination size, range 1-200"},
		pageSizeLimitAliasFlag(),
	},
	Validate: func(ctx context.Context, runtime *common.RuntimeContext) error {
		if err := validateLimitPageSizeAlias(runtime); err != nil {
			return err
		}
		if _, err := common.ValidatePageSizeTyped(runtime, "limit", 100, 1, 200); err != nil {
			return err
		}
		if runtime.Changed("page-size") {
			if _, err := common.ValidatePageSizeTyped(runtime, "page-size", 100, 1, 200); err != nil {
				return err
			}
		}
		return nil
	},
	DryRun: dryRunViewList,
	Execute: func(ctx context.Context, runtime *common.RuntimeContext) error {
		return executeViewList(runtime)
	},
}
View Source
var BaseViewRename = common.Shortcut{
	Service:     "base",
	Command:     "+view-rename",
	Description: "Rename a view by ID or name",
	Risk:        "write",
	Scopes:      []string{"base:view:write_only"},
	AuthTypes:   authTypes(),
	Flags: []common.Flag{
		baseTokenFlag(true),
		tableRefFlag(true),
		viewRefFlag(true),
		{Name: "name", Desc: "new view name", Required: true},
	},
	DryRun: dryRunViewRename,
	Execute: func(ctx context.Context, runtime *common.RuntimeContext) error {
		return executeViewRename(runtime)
	},
}
View Source
var BaseViewSetCard = common.Shortcut{
	Service:     "base",
	Command:     "+view-set-card",
	Description: "Set view card configuration",
	Risk:        "write",
	Scopes:      []string{"base:view:write_only"},
	AuthTypes:   authTypes(),
	Flags: []common.Flag{
		baseTokenFlag(true),
		tableRefFlag(true),
		viewRefFlag(true),
		{Name: "json", Desc: `card JSON object, e.g. {"cover_field":"Cover"} or {"cover_field":null} to clear`, Required: true},
	},
	Tips: []string{
		"Supported view types: gallery, kanban.",
		"cover_field should be an attachment field id/name, or null to clear.",
		"Use +view-get-card first when updating an existing card view configuration.",
	},
	Validate: func(ctx context.Context, runtime *common.RuntimeContext) error {
		return validateViewJSONObject(runtime)
	},
	DryRun: dryRunViewSetCard,
	Execute: func(ctx context.Context, runtime *common.RuntimeContext) error {
		return executeViewSetJSONObject(runtime, "card", "card")
	},
}
View Source
var BaseViewSetFilter = common.Shortcut{
	Service:     "base",
	Command:     "+view-set-filter",
	Description: "Set view filter configuration",
	Risk:        "write",
	Scopes:      []string{"base:view:write_only"},
	AuthTypes:   authTypes(),
	Flags: []common.Flag{
		baseTokenFlag(true),
		tableRefFlag(true),
		viewRefFlag(true),
		{Name: "json", Desc: `filter JSON object, e.g. {"logic":"and","conditions":[["Status","==","Todo"]]}`, Required: true},
	},
	Tips: []string{
		"Agent hint: use the lark-base skill's view-set-filter guide for usage and limits.",
	},
	Validate: func(ctx context.Context, runtime *common.RuntimeContext) error {
		return validateViewJSONObject(runtime)
	},
	DryRun: dryRunViewSetFilter,
	Execute: func(ctx context.Context, runtime *common.RuntimeContext) error {
		return executeViewSetJSONObject(runtime, "filter", "filter")
	},
}
View Source
var BaseViewSetGroup = common.Shortcut{
	Service:     "base",
	Command:     "+view-set-group",
	Description: "Set view group configuration",
	Risk:        "write",
	Scopes:      []string{"base:view:write_only"},
	AuthTypes:   authTypes(),
	Flags: []common.Flag{
		baseTokenFlag(true),
		tableRefFlag(true),
		viewRefFlag(true),
		{Name: "json", Desc: `group JSON object with group_config array, e.g. {"group_config":[{"field":"Status","desc":false}]}; use {"group_config":[]} to clear`, Required: true},
	},
	Tips: []string{
		"Supported view types: grid, kanban, gantt.",
		"Use a JSON object, not a bare array; grouping fields must be supported by the current view.",
		"group_config supports max 3 group items.",
		"Use +view-get-group first when modifying an existing grouping configuration.",
	},
	Validate: func(ctx context.Context, runtime *common.RuntimeContext) error {
		return validateViewJSONObject(runtime)
	},
	DryRun: dryRunViewSetGroup,
	Execute: func(ctx context.Context, runtime *common.RuntimeContext) error {
		return executeViewSetWrapped(runtime, "group", "group_config", "group")
	},
}
View Source
var BaseViewSetSort = common.Shortcut{
	Service:     "base",
	Command:     "+view-set-sort",
	Description: "Set view sort configuration",
	Risk:        "write",
	Scopes:      []string{"base:view:write_only"},
	AuthTypes:   authTypes(),
	Flags: []common.Flag{
		baseTokenFlag(true),
		tableRefFlag(true),
		viewRefFlag(true),
		{Name: "json", Desc: `sort_config JSON object, e.g. {"sort_config":[{"field":"Priority","desc":true}]}; use {"sort_config":[]} to clear; max 10 items`, Required: true},
	},
	Tips: []string{
		"Supported view types: grid, kanban, gallery, gantt.",
		"Use a JSON object, not a bare array; sorting fields must be supported by the current view.",
		"sort_config supports max 10 sort items.",
		"Use +view-get-sort first when modifying an existing sort configuration.",
	},
	Validate: func(ctx context.Context, runtime *common.RuntimeContext) error {
		return validateViewJSONObject(runtime)
	},
	DryRun: dryRunViewSetSort,
	Execute: func(ctx context.Context, runtime *common.RuntimeContext) error {
		return executeViewSetWrapped(runtime, "sort", "sort_config", "sort")
	},
}
View Source
var BaseViewSetTimebar = common.Shortcut{
	Service:     "base",
	Command:     "+view-set-timebar",
	Description: "Set view timebar configuration",
	Risk:        "write",
	Scopes:      []string{"base:view:write_only"},
	AuthTypes:   authTypes(),
	Flags: []common.Flag{
		baseTokenFlag(true),
		tableRefFlag(true),
		viewRefFlag(true),
		{Name: "json", Desc: `timebar JSON object with start_time, end_time, title, e.g. {"start_time":"Start Date","end_time":"End Date","title":"Name"}`, Required: true},
	},
	Tips: []string{
		"Supported view types: calendar, gantt.",
		"start_time, end_time, and title are required; use date/time fields for start_time and end_time.",
		"Use +view-get-timebar first when modifying an existing timebar configuration.",
	},
	Validate: func(ctx context.Context, runtime *common.RuntimeContext) error {
		return validateViewJSONObject(runtime)
	},
	DryRun: dryRunViewSetTimebar,
	Execute: func(ctx context.Context, runtime *common.RuntimeContext) error {
		return executeViewSetJSONObject(runtime, "timebar", "timebar")
	},
}
View Source
var BaseViewSetVisibleFields = common.Shortcut{
	Service:     "base",
	Command:     "+view-set-visible-fields",
	Description: "Set view visible fields",
	Risk:        "write",
	Scopes:      []string{"base:view:write_only"},
	AuthTypes:   authTypes(),
	Flags: []common.Flag{
		baseTokenFlag(true),
		tableRefFlag(true),
		viewRefFlag(true),
		{Name: "json", Desc: `visible fields JSON object, e.g. {"visible_fields":["Name","Status"]}`, Required: true},
	},
	Tips: []string{
		"Supported view types: grid, kanban, gallery, calendar, gantt.",
		"Use a JSON object, not a bare array; primary field may be forced to the first position by the API.",
		"visible_fields controls both visibility and order; include every field that should remain visible.",
	},
	Validate: func(ctx context.Context, runtime *common.RuntimeContext) error {
		return validateViewJSONObject(runtime)
	},
	DryRun: dryRunViewSetVisibleFields,
	Execute: func(ctx context.Context, runtime *common.RuntimeContext) error {
		return executeViewSetVisibleFields(runtime)
	},
}
View Source
var BaseWorkflowCreate = common.Shortcut{
	Service:     "base",
	Command:     "+workflow-create",
	Description: "Create a new workflow in a base",
	Risk:        "write",
	Scopes:      []string{"base:workflow:create"},
	AuthTypes:   []string{"user", "bot"},
	Flags: []common.Flag{
		{Name: "base-token", Desc: "base token", Required: true},
		{Name: "json", Desc: "workflow body JSON; read lark-base-workflow-guide.md and lark-base-workflow-schema.md before constructing steps", Required: true},
	},
	Tips: []string{
		"lark-cli base +workflow-create --base-token <base_token> --json @workflow.json",
		"client_token is required and should be unique per create request.",
		"New workflows are created disabled; call +workflow-enable after creation when the user wants it active.",
		"Before constructing steps, use +table-list and +field-list to confirm real table and field names.",
		"Step ids must be unique, and every next/children link must reference an existing step id.",
		"Use lark-base-workflow-guide.md as the entry guide and lark-base-workflow-schema.md as the steps JSON SSOT; do not invent steps[].type/data/next/children from natural language.",
	},
	Validate: func(ctx context.Context, runtime *common.RuntimeContext) error {
		if strings.TrimSpace(runtime.Str("base-token")) == "" {
			return baseFlagErrorf("--base-token must not be blank")
		}
		pc := newParseCtx(runtime)
		raw, err := loadJSONInput(pc, runtime.Str("json"), "json")
		if err != nil {
			return err
		}
		if _, err := parseJSONObject(pc, raw, "json"); err != nil {
			return err
		}
		return nil
	},
	DryRun: func(ctx context.Context, runtime *common.RuntimeContext) *common.DryRunAPI {
		pc := newParseCtx(runtime)
		var body map[string]interface{}
		if raw, err := loadJSONInput(pc, runtime.Str("json"), "json"); err == nil {
			body, _ = parseJSONObject(pc, raw, "json")
		}
		return common.NewDryRunAPI().
			POST("/open-apis/base/v3/bases/:base_token/workflows").
			Body(body).
			Set("base_token", runtime.Str("base-token"))
	},
	Execute: func(ctx context.Context, runtime *common.RuntimeContext) error {
		pc := newParseCtx(runtime)
		raw, err := loadJSONInput(pc, runtime.Str("json"), "json")
		if err != nil {
			return err
		}
		body, err := parseJSONObject(pc, raw, "json")
		if err != nil {
			return err
		}
		data, err := baseV3Call(runtime, "POST",
			baseV3Path("bases", runtime.Str("base-token"), "workflows"),
			nil,
			body,
		)
		if err != nil {
			return err
		}
		runtime.Out(data, nil)
		return nil
	},
}
View Source
var BaseWorkflowDisable = common.Shortcut{
	Service:     "base",
	Command:     "+workflow-disable",
	Description: "Disable a workflow in a base",
	Risk:        "write",
	Scopes:      []string{"base:workflow:update"},
	AuthTypes:   []string{"user", "bot"},
	Flags: []common.Flag{
		{Name: "base-token", Desc: "base token", Required: true},
		{Name: "workflow-id", Desc: "workflow ID (wkf... prefix)", Required: true},
	},
	Tips: []string{
		"workflow-id must start with wkf; do not pass a tbl table ID from the same URL.",
		"Disable only changes workflow state; it does not delete the workflow or its steps.",
	},
	Validate: func(ctx context.Context, runtime *common.RuntimeContext) error {
		if strings.TrimSpace(runtime.Str("base-token")) == "" {
			return baseFlagErrorf("--base-token must not be blank")
		}
		if strings.TrimSpace(runtime.Str("workflow-id")) == "" {
			return baseFlagErrorf("--workflow-id must not be blank")
		}
		return nil
	},
	DryRun: func(ctx context.Context, runtime *common.RuntimeContext) *common.DryRunAPI {
		return common.NewDryRunAPI().
			PATCH("/open-apis/base/v3/bases/:base_token/workflows/:workflow_id/disable").
			Set("base_token", runtime.Str("base-token")).
			Set("workflow_id", runtime.Str("workflow-id"))
	},
	Execute: func(ctx context.Context, runtime *common.RuntimeContext) error {
		data, err := baseV3Call(runtime, "PATCH",
			baseV3Path("bases", runtime.Str("base-token"), "workflows", runtime.Str("workflow-id"), "disable"),
			nil,
			map[string]interface{}{},
		)
		if err != nil {
			return err
		}
		runtime.Out(data, nil)
		return nil
	},
}
View Source
var BaseWorkflowEnable = common.Shortcut{
	Service:     "base",
	Command:     "+workflow-enable",
	Description: "Enable a workflow in a base",
	Risk:        "write",
	Scopes:      []string{"base:workflow:update"},
	AuthTypes:   []string{"user", "bot"},
	Flags: []common.Flag{
		{Name: "base-token", Desc: "base token", Required: true},
		{Name: "workflow-id", Desc: "workflow ID (wkf... prefix)", Required: true},
	},
	Tips: []string{
		"workflow-id must start with wkf; do not pass a tbl table ID from the same URL.",
		"Enable only changes workflow state; it does not modify steps.",
		"New workflows are created disabled; enable after creation only when the user wants it active.",
	},
	Validate: func(ctx context.Context, runtime *common.RuntimeContext) error {
		if strings.TrimSpace(runtime.Str("base-token")) == "" {
			return baseFlagErrorf("--base-token must not be blank")
		}
		if strings.TrimSpace(runtime.Str("workflow-id")) == "" {
			return baseFlagErrorf("--workflow-id must not be blank")
		}
		return nil
	},
	DryRun: func(ctx context.Context, runtime *common.RuntimeContext) *common.DryRunAPI {
		return common.NewDryRunAPI().
			PATCH("/open-apis/base/v3/bases/:base_token/workflows/:workflow_id/enable").
			Set("base_token", runtime.Str("base-token")).
			Set("workflow_id", runtime.Str("workflow-id"))
	},
	Execute: func(ctx context.Context, runtime *common.RuntimeContext) error {
		data, err := baseV3Call(runtime, "PATCH",
			baseV3Path("bases", runtime.Str("base-token"), "workflows", runtime.Str("workflow-id"), "enable"),
			nil,
			map[string]interface{}{},
		)
		if err != nil {
			return err
		}
		runtime.Out(data, nil)
		return nil
	},
}
View Source
var BaseWorkflowGet = common.Shortcut{
	Service:     "base",
	Command:     "+workflow-get",
	Description: "Get a single workflow definition (including steps) from a base",
	Risk:        "read",
	Scopes:      []string{"base:workflow:read"},
	AuthTypes:   []string{"user", "bot"},
	Flags: []common.Flag{
		{Name: "base-token", Desc: "base token", Required: true},
		{Name: "workflow-id", Desc: "workflow ID (wkf... prefix)", Required: true},
		{Name: "user-id-type", Desc: "user ID type for creator/updater fields, default open_id", Enum: []string{"open_id", "union_id", "user_id"}},
	},
	Tips: []string{
		"workflow-id must start with wkf; use +workflow-list if the ID is unknown.",
		"steps may be an empty array; that is valid for an unconfigured workflow.",
		"Use +workflow-get before +workflow-update, then edit the returned definition and keep fields you do not intend to change.",
		"Read lark-base-workflow-schema.md when interpreting or reusing returned steps.",
	},
	Validate: func(ctx context.Context, runtime *common.RuntimeContext) error {
		if strings.TrimSpace(runtime.Str("base-token")) == "" {
			return baseFlagErrorf("--base-token must not be blank")
		}
		if strings.TrimSpace(runtime.Str("workflow-id")) == "" {
			return baseFlagErrorf("--workflow-id must not be blank")
		}
		return nil
	},
	DryRun: func(ctx context.Context, runtime *common.RuntimeContext) *common.DryRunAPI {
		api := common.NewDryRunAPI().
			GET("/open-apis/base/v3/bases/:base_token/workflows/:workflow_id").
			Set("base_token", runtime.Str("base-token")).
			Set("workflow_id", runtime.Str("workflow-id"))
		if t := runtime.Str("user-id-type"); t != "" {
			api = api.Params(map[string]interface{}{"user_id_type": t})
		}
		return api
	},
	Execute: func(ctx context.Context, runtime *common.RuntimeContext) error {
		var params map[string]interface{}
		if t := runtime.Str("user-id-type"); t != "" {
			params = map[string]interface{}{"user_id_type": t}
		}
		data, err := baseV3Call(runtime, "GET",
			baseV3Path("bases", runtime.Str("base-token"), "workflows", runtime.Str("workflow-id")),
			params,
			nil,
		)
		if err != nil {
			return err
		}
		runtime.Out(data, nil)
		return nil
	},
}
View Source
var BaseWorkflowList = common.Shortcut{
	Service:     "base",
	Command:     "+workflow-list",
	Description: "List all workflows in a base (auto-paginated)",
	Risk:        "read",
	Scopes:      []string{"base:workflow:read"},
	AuthTypes:   []string{"user", "bot"},
	Flags: []common.Flag{
		{Name: "base-token", Desc: "base token", Required: true},
		{Name: "status", Desc: "filter by status", Enum: []string{"enabled", "disabled"}},
		{Name: "page-size", Type: "int", Default: "100", Desc: "page size per request, range 1-100"},
	},
	Tips: []string{
		"Returns workflow_id values with wkf prefix; pass those IDs to +workflow-get/enable/disable/update.",
		"This shortcut auto-paginates and returns all matched workflows.",
	},
	Validate: func(ctx context.Context, runtime *common.RuntimeContext) error {
		if strings.TrimSpace(runtime.Str("base-token")) == "" {
			return baseFlagErrorf("--base-token must not be blank")
		}
		_, err := common.ValidatePageSizeTyped(runtime, "page-size", 100, 1, 100)
		return err
	},
	DryRun: func(ctx context.Context, runtime *common.RuntimeContext) *common.DryRunAPI {
		body := map[string]interface{}{
			"page_size": runtime.Int("page-size"),
		}
		if s := runtime.Str("status"); s != "" {
			body["status"] = s
		}
		return common.NewDryRunAPI().
			POST("/open-apis/base/v3/bases/:base_token/workflows/list").
			Body(body).
			Set("base_token", runtime.Str("base-token"))
	},
	Execute: func(ctx context.Context, runtime *common.RuntimeContext) error {
		var allItems []interface{}
		pageToken := ""
		for {
			body := map[string]interface{}{
				"page_size": runtime.Int("page-size"),
			}
			if pageToken != "" {
				body["page_token"] = pageToken
			}
			if s := runtime.Str("status"); s != "" {
				body["status"] = s
			}
			data, err := baseV3Call(runtime, "POST",
				baseV3Path("bases", runtime.Str("base-token"), "workflows", "list"),
				nil,
				body,
			)
			if err != nil {
				return err
			}
			items, _ := data["items"].([]interface{})
			allItems = append(allItems, items...)
			hasMore, _ := data["has_more"].(bool)
			if !hasMore {
				break
			}
			nextToken, _ := data["page_token"].(string)
			if nextToken == "" {
				break
			}
			pageToken = nextToken
		}
		runtime.Out(map[string]interface{}{
			"items": allItems,
			"total": len(allItems),
		}, nil)
		return nil
	},
}
View Source
var BaseWorkflowUpdate = common.Shortcut{
	Service:     "base",
	Command:     "+workflow-update",
	Description: "Replace a workflow's full definition (title and/or steps) in a base",
	Risk:        "write",
	Scopes:      []string{"base:workflow:update"},
	AuthTypes:   []string{"user", "bot"},
	Flags: []common.Flag{
		{Name: "base-token", Desc: "base token", Required: true},
		{Name: "workflow-id", Desc: "workflow ID (wkf... prefix)", Required: true},
		{Name: "json", Desc: "workflow body JSON; read lark-base-workflow-guide.md and lark-base-workflow-schema.md before replacing steps", Required: true},
	},
	Tips: []string{
		"lark-cli base +workflow-update --base-token <base_token> --workflow-id <workflow_id> --json @workflow.json",
		"PUT uses full replacement semantics; omitting steps clears the existing workflow steps.",
		"Use +workflow-get first, then edit the returned definition and keep title/status/steps fields you do not intend to change.",
		"workflow-id must start with wkf; do not pass a tbl table ID.",
		"Step ids must be unique, and every next/children link must reference an existing step id.",
		"Updating does not enable or disable a workflow; call +workflow-enable or +workflow-disable separately.",
		"Use lark-base-workflow-guide.md as the entry guide and lark-base-workflow-schema.md as the steps JSON SSOT; do not invent steps[].type/data/next/children from natural language.",
	},
	Validate: func(ctx context.Context, runtime *common.RuntimeContext) error {
		if strings.TrimSpace(runtime.Str("base-token")) == "" {
			return baseFlagErrorf("--base-token must not be blank")
		}
		if strings.TrimSpace(runtime.Str("workflow-id")) == "" {
			return baseFlagErrorf("--workflow-id must not be blank")
		}
		pc := newParseCtx(runtime)
		if _, err := parseJSONObject(pc, runtime.Str("json"), "json"); err != nil {
			return err
		}
		return nil
	},
	DryRun: func(ctx context.Context, runtime *common.RuntimeContext) *common.DryRunAPI {
		pc := newParseCtx(runtime)
		var body map[string]interface{}
		body, _ = parseJSONObject(pc, runtime.Str("json"), "json")
		return common.NewDryRunAPI().
			PUT("/open-apis/base/v3/bases/:base_token/workflows/:workflow_id").
			Body(body).
			Set("base_token", runtime.Str("base-token")).
			Set("workflow_id", runtime.Str("workflow-id"))
	},
	Execute: func(ctx context.Context, runtime *common.RuntimeContext) error {
		pc := newParseCtx(runtime)
		body, err := parseJSONObject(pc, runtime.Str("json"), "json")
		if err != nil {
			return err
		}
		data, err := baseV3Call(runtime, "PUT",
			baseV3Path("bases", runtime.Str("base-token"), "workflows", runtime.Str("workflow-id")),
			nil,
			body,
		)
		if err != nil {
			return err
		}
		runtime.Out(data, nil)
		return nil
	},
}

Functions

func Shortcuts

func Shortcuts() []common.Shortcut

Shortcuts returns all base shortcuts.

Types

This section is empty.

Source Files

Jump to

Keyboard shortcuts

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