wiki

package
v1.0.44 Latest Latest
Warning

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

Go to latest
Published: May 29, 2026 License: MIT Imports: 13 Imported by: 0

Documentation

Index

Constants

This section is empty.

Variables

View Source
var WikiDeleteSpace = common.Shortcut{
	Service:     "wiki",
	Command:     "+delete-space",
	Description: "Delete a wiki space, polling the async delete task when needed",
	Risk:        "high-risk-write",
	Scopes:      []string{"wiki:space:write_only", "wiki:space:read"},
	AuthTypes:   []string{"user", "bot"},
	Flags: []common.Flag{
		{Name: "space-id", Desc: "wiki space ID to delete", Required: true},
	},
	Tips: []string{
		"Deletion is irreversible; double-check --space-id before running.",
		"This is a high-risk-write command; pass --yes to confirm the deletion.",
		"If the API returns a long-running task, this command polls for a bounded window and then prints a follow-up drive +task_result command.",
	},
	Validate: func(ctx context.Context, runtime *common.RuntimeContext) error {
		return validateWikiDeleteSpaceSpec(readWikiDeleteSpaceSpec(runtime))
	},
	DryRun: func(ctx context.Context, runtime *common.RuntimeContext) *common.DryRunAPI {
		return buildWikiDeleteSpaceDryRun(readWikiDeleteSpaceSpec(runtime))
	},
	Execute: func(ctx context.Context, runtime *common.RuntimeContext) error {
		spec := readWikiDeleteSpaceSpec(runtime)
		fmt.Fprintf(runtime.IO().ErrOut, "Deleting wiki space %s...\n", spec.SpaceID)

		out, err := runWikiDeleteSpace(ctx, wikiDeleteSpaceAPI{runtime: runtime}, runtime, spec)
		if err != nil {
			return err
		}

		runtime.Out(out, nil)
		return nil
	},
}

WikiDeleteSpace deletes a wiki space. The DELETE endpoint may complete synchronously (empty task_id) or return a task_id that must be polled against /open-apis/wiki/v2/tasks/:task_id with task_type=delete_space.

View Source
var WikiMemberAdd = common.Shortcut{
	Service:     "wiki",
	Command:     "+member-add",
	Description: "Add a member to a wiki space",
	Risk:        "write",

	Scopes:    []string{"wiki:member:create"},
	AuthTypes: []string{"user", "bot"},
	Flags: []common.Flag{
		{Name: "space-id", Desc: "wiki space ID; use my_library for the personal document library (user only)", Required: true},
		{Name: "member-id", Desc: "member ID; interpretation is decided by --member-type", Required: true},
		{Name: "member-type", Desc: "ID type for --member-id", Required: true, Enum: wikiMemberTypes},
		{Name: "member-role", Desc: "role granted within the space", Required: true, Enum: wikiMemberRoles},
		{Name: "need-notification", Type: "bool", Desc: "send an in-app notification to the new member after the grant"},
	},
	Tips: []string{
		"Use --member-type=email with the user's mailbox if you do not know their open_id.",
		"--member-role=admin grants full space administration; pick --member-role=member for collaborator access.",
		"--space-id my_library is a per-user alias and is only valid with --as user.",
	},
	Validate: func(ctx context.Context, runtime *common.RuntimeContext) error {
		_, err := readWikiMemberAddSpec(runtime)
		return err
	},
	DryRun: func(ctx context.Context, runtime *common.RuntimeContext) *common.DryRunAPI {
		spec, err := readWikiMemberAddSpec(runtime)
		if err != nil {
			return common.NewDryRunAPI().Set("error", err.Error())
		}
		return buildWikiMemberAddDryRun(spec)
	},
	Execute: func(ctx context.Context, runtime *common.RuntimeContext) error {
		spec, err := readWikiMemberAddSpec(runtime)
		if err != nil {
			return err
		}

		spaceID, err := resolveWikiMemberSpaceID(runtime, spec.SpaceID)
		if err != nil {
			return err
		}

		fmt.Fprintf(runtime.IO().ErrOut, "Adding wiki space member %s (type=%s, role=%s) to space %s...\n",
			common.MaskToken(spec.MemberID), spec.MemberType, spec.MemberRole, common.MaskToken(spaceID))

		path := fmt.Sprintf("/open-apis/wiki/v2/spaces/%s/members", validate.EncodePathSegment(spaceID))
		data, err := runtime.CallAPI("POST", path, spec.QueryParams(), spec.RequestBody())
		if err != nil {
			return err
		}

		out := wikiMemberAddOutput(spaceID, common.GetMap(data, "member"))

		if common.GetString(out, "member_id") == "" {
			out["member_id"] = spec.MemberID
		}
		if common.GetString(out, "member_type") == "" {
			out["member_type"] = spec.MemberType
		}
		if common.GetString(out, "member_role") == "" {
			out["member_role"] = spec.MemberRole
		}
		fmt.Fprintf(runtime.IO().ErrOut, "Added wiki space member %s\n", common.MaskToken(common.GetString(out, "member_id")))
		runtime.Out(out, nil)
		return nil
	},
}

WikiMemberAdd wraps POST /open-apis/wiki/v2/spaces/{space_id}/members. The shortcut adds flag ergonomics over the raw API: explicit --member-type and --member-role enum hints, optional --need-notification, my_library resolution, and a flattened single-member output envelope.

View Source
var WikiMemberList = common.Shortcut{
	Service:     "wiki",
	Command:     "+member-list",
	Description: "List members of a wiki space",
	Risk:        "read",

	Scopes:    []string{"wiki:member:retrieve"},
	AuthTypes: []string{"user", "bot"},
	HasFormat: true,
	Flags: []common.Flag{
		{Name: "space-id", Desc: "wiki space ID; use my_library for the personal document library (user only)", Required: true},
		{Name: "page-size", Type: "int", Default: strconv.Itoa(wikiMemberListDefaultPageSize), Desc: fmt.Sprintf("page size, 1-%d", wikiMemberListMaxPageSize)},
		{Name: "page-token", Desc: "page token; implies single-page fetch (no auto-pagination)"},
		{Name: "page-all", Type: "bool", Desc: "automatically paginate through all pages (capped by --page-limit)"},
		{Name: "page-limit", Type: "int", Default: "10", Desc: "max pages to fetch with --page-all (default 10, 0 = unlimited)"},
	},
	Tips: []string{
		"Default fetches a single page; pass --page-all to walk every page.",
		"--space-id my_library is a per-user alias and is only valid with --as user.",
	},
	Validate: func(ctx context.Context, runtime *common.RuntimeContext) error {
		if err := validateWikiMemberSpaceID(runtime, strings.TrimSpace(runtime.Str("space-id"))); err != nil {
			return err
		}
		return validateWikiListPagination(runtime, wikiMemberListMaxPageSize)
	},
	DryRun: func(ctx context.Context, runtime *common.RuntimeContext) *common.DryRunAPI {
		spaceID := strings.TrimSpace(runtime.Str("space-id"))
		params := map[string]interface{}{"page_size": runtime.Int("page-size")}
		if pt := strings.TrimSpace(runtime.Str("page-token")); pt != "" {
			params["page_token"] = pt
		}
		dry := common.NewDryRunAPI()
		if wikiListShouldAutoPaginate(runtime) {
			dry.Desc("Auto-paginates through all pages (capped by --page-limit when > 0)")
		}
		if spaceID == wikiMyLibrarySpaceID {
			return dry.
				Desc("2-step orchestration: resolve my_library -> list members").
				GET("/open-apis/wiki/v2/spaces/my_library").
				Desc("[1] Resolve my_library space ID").
				GET(fmt.Sprintf("/open-apis/wiki/v2/spaces/%s/members", "<resolved_space_id>")).
				Desc("[2] List wiki space members").
				Params(params)
		}
		return dry.
			GET(fmt.Sprintf("/open-apis/wiki/v2/spaces/%s/members", validate.EncodePathSegment(spaceID))).
			Params(params)
	},
	Execute: func(ctx context.Context, runtime *common.RuntimeContext) error {
		warnIfConflictingPagingFlags(runtime)

		spaceID, err := resolveWikiMemberSpaceID(runtime, strings.TrimSpace(runtime.Str("space-id")))
		if err != nil {
			return err
		}

		members, hasMore, nextToken, err := fetchWikiMembers(runtime, spaceID)
		if err != nil {
			return err
		}
		fmt.Fprintf(runtime.IO().ErrOut, "Found %d wiki space member(s)\n", len(members))

		outData := map[string]interface{}{
			"space_id":   spaceID,
			"members":    members,
			"has_more":   hasMore,
			"page_token": nextToken,
		}
		runtime.OutFormat(outData, &output.Meta{Count: len(members)}, func(w io.Writer) {
			renderWikiMembersPretty(w, spaceID, members, hasMore, nextToken)
		})
		return nil
	},
}

WikiMemberList lists the members of a wiki space. Pagination follows the same conventions as +space-list / +node-list (single page by default, --page-all to walk every page, --page-token for explicit cursor resume).

View Source
var WikiMemberRemove = common.Shortcut{
	Service:     "wiki",
	Command:     "+member-remove",
	Description: "Remove a member from a wiki space",
	Risk:        "write",

	Scopes:    []string{"wiki:member:update"},
	AuthTypes: []string{"user", "bot"},
	Flags: []common.Flag{
		{Name: "space-id", Desc: "wiki space ID; use my_library for the personal document library (user only)", Required: true},
		{Name: "member-id", Desc: "member ID; interpretation is decided by --member-type", Required: true},
		{Name: "member-type", Desc: "ID type for --member-id (must match the original grant)", Required: true, Enum: wikiMemberTypes},
		{Name: "member-role", Desc: "role being revoked (must match the original grant)", Required: true, Enum: wikiMemberRoles},
	},
	Tips: []string{
		"--member-type and --member-role must match the original grant; revoking a non-existent (member_id, type, role) tuple is a no-op error from the API.",
		"To switch a member from admin to member or vice versa, remove the old role first, then call +member-add with the new one.",
		"--space-id my_library is a per-user alias and is only valid with --as user.",
	},
	Validate: func(ctx context.Context, runtime *common.RuntimeContext) error {
		_, err := readWikiMemberRemoveSpec(runtime)
		return err
	},
	DryRun: func(ctx context.Context, runtime *common.RuntimeContext) *common.DryRunAPI {
		spec, err := readWikiMemberRemoveSpec(runtime)
		if err != nil {
			return common.NewDryRunAPI().Set("error", err.Error())
		}
		return buildWikiMemberRemoveDryRun(spec)
	},
	Execute: func(ctx context.Context, runtime *common.RuntimeContext) error {
		spec, err := readWikiMemberRemoveSpec(runtime)
		if err != nil {
			return err
		}

		spaceID, err := resolveWikiMemberSpaceID(runtime, spec.SpaceID)
		if err != nil {
			return err
		}

		fmt.Fprintf(runtime.IO().ErrOut, "Removing wiki space member %s (type=%s, role=%s) from space %s...\n",
			common.MaskToken(spec.MemberID), spec.MemberType, spec.MemberRole, common.MaskToken(spaceID))

		path := fmt.Sprintf(
			"/open-apis/wiki/v2/spaces/%s/members/%s",
			validate.EncodePathSegment(spaceID),
			validate.EncodePathSegment(spec.MemberID),
		)
		data, err := runtime.CallAPI("DELETE", path, nil, spec.RequestBody())
		if err != nil {
			return err
		}

		out := map[string]interface{}{"space_id": spaceID}
		for k, v := range wikiMemberRecord(common.GetMap(data, "member")) {
			out[k] = v
		}

		if common.GetString(out, "member_id") == "" {
			out["member_id"] = spec.MemberID
		}
		if common.GetString(out, "member_type") == "" {
			out["member_type"] = spec.MemberType
		}
		if common.GetString(out, "member_role") == "" {
			out["member_role"] = spec.MemberRole
		}
		fmt.Fprintf(runtime.IO().ErrOut, "Removed wiki space member %s\n", common.MaskToken(common.GetString(out, "member_id")))
		runtime.Out(out, nil)
		return nil
	},
}

WikiMemberRemove wraps DELETE /open-apis/wiki/v2/spaces/{space_id}/members/{member_id}. Unlike most DELETEs, this API requires a body specifying member_type and member_role, since the path :member_id is ambiguous without both. The shortcut surfaces both as flags and flattens the returned member object.

View Source
var WikiMove = common.Shortcut{
	Service:     "wiki",
	Command:     "+move",
	Description: "Move a wiki node, or move a Drive document into Wiki",
	Risk:        "write",
	Scopes:      []string{"wiki:node:move", "wiki:node:read", "wiki:space:read"},
	AuthTypes:   []string{"user", "bot"},
	Flags: []common.Flag{
		{Name: "node-token", Desc: "wiki node token to move inside Wiki"},
		{Name: "source-space-id", Desc: "source wiki space ID for --node-token; if omitted, it is resolved from the node token"},
		{Name: "target-space-id", Desc: "target wiki space ID; required for docs-to-wiki, optional for node move when --target-parent-token is set"},
		{Name: "target-parent-token", Desc: "target parent wiki node token; if omitted for docs-to-wiki, the document is moved to the target space root"},
		{Name: "obj-type", Desc: "Drive document type for docs-to-wiki mode", Enum: wikiMoveObjectTypes},
		{Name: "obj-token", Desc: "Drive document token for docs-to-wiki mode"},
		{Name: "apply", Type: "bool", Desc: "submit a move request when the caller lacks permission to move the document immediately"},
	},
	Tips: []string{
		"Use --node-token to move an existing wiki node inside or across wiki spaces.",
		"Use --obj-type and --obj-token to move a Drive document into Wiki.",
		"If docs-to-wiki returns a long-running task, this command polls for a bounded window and then prints a follow-up drive +task_result command.",
	},
	Validate: func(ctx context.Context, runtime *common.RuntimeContext) error {
		spec := readWikiMoveSpec(runtime)

		if runtime.As().IsBot() && spec.TargetSpaceID == wikiMyLibrarySpaceID {
			return output.ErrValidation("--target-space-id my_library is a per-user personal library alias and cannot be used with --as bot; resolve it to a real space_id first via `lark-cli wiki spaces get --params '{\"space_id\":\"my_library\"}' --as user`")
		}
		return validateWikiMoveSpec(spec)
	},
	DryRun: func(ctx context.Context, runtime *common.RuntimeContext) *common.DryRunAPI {
		return buildWikiMoveDryRun(readWikiMoveSpec(runtime))
	},
	Execute: func(ctx context.Context, runtime *common.RuntimeContext) error {
		spec := readWikiMoveSpec(runtime)
		fmt.Fprintf(runtime.IO().ErrOut, "Running wiki move (%s)...\n", spec.Mode())

		out, err := runWikiMove(ctx, wikiMoveAPI{runtime: runtime}, runtime, spec)
		if err != nil {
			return err
		}

		runtime.Out(out, nil)
		return nil
	},
}

WikiMove moves an existing wiki node inside Wiki or migrates a Drive document into Wiki with bounded polling for async task completion.

View Source
var WikiNodeCopy = common.Shortcut{
	Service:     "wiki",
	Command:     "+node-copy",
	Description: "Copy a wiki node to a target space or parent node",
	Risk:        "high-risk-write",
	Scopes:      []string{"wiki:node:copy"},
	AuthTypes:   []string{"user", "bot"},
	HasFormat:   true,
	Flags: []common.Flag{
		{Name: "space-id", Desc: "source wiki space ID", Required: true},
		{Name: "node-token", Desc: "source node token to copy", Required: true},
		{Name: "target-space-id", Desc: "target wiki space ID; required if --target-parent-node-token is not set"},
		{Name: "target-parent-node-token", Desc: "target parent node token; required if --target-space-id is not set"},
		{Name: "title", Desc: "new title for the copied node; leave empty to keep the original title"},
	},
	Tips: []string{
		"At least one of --target-space-id or --target-parent-node-token must be provided.",
		"Omit --title to keep the original node title in the copy.",
	},
	Validate: func(ctx context.Context, runtime *common.RuntimeContext) error {
		if err := validateOptionalResourceName(strings.TrimSpace(runtime.Str("space-id")), "--space-id"); err != nil {
			return err
		}
		if err := validateOptionalResourceName(strings.TrimSpace(runtime.Str("node-token")), "--node-token"); err != nil {
			return err
		}
		targetSpaceID := strings.TrimSpace(runtime.Str("target-space-id"))
		targetParent := strings.TrimSpace(runtime.Str("target-parent-node-token"))
		if targetSpaceID == "" && targetParent == "" {
			return output.ErrValidation("at least one of --target-space-id or --target-parent-node-token is required")
		}
		if targetSpaceID != "" && targetParent != "" {
			return output.ErrValidation("--target-space-id and --target-parent-node-token are mutually exclusive; provide only one")
		}
		if err := validateOptionalResourceName(targetSpaceID, "--target-space-id"); err != nil {
			return err
		}
		return validateOptionalResourceName(targetParent, "--target-parent-node-token")
	},
	DryRun: func(ctx context.Context, runtime *common.RuntimeContext) *common.DryRunAPI {
		spaceID := strings.TrimSpace(runtime.Str("space-id"))
		nodeToken := strings.TrimSpace(runtime.Str("node-token"))
		return common.NewDryRunAPI().
			POST(fmt.Sprintf("/open-apis/wiki/v2/spaces/%s/nodes/%s/copy",
				validate.EncodePathSegment(spaceID),
				validate.EncodePathSegment(nodeToken))).
			Body(buildNodeCopyBody(runtime)).
			Set("space_id", spaceID).
			Set("node_token", nodeToken)
	},
	Execute: func(ctx context.Context, runtime *common.RuntimeContext) error {
		spaceID := strings.TrimSpace(runtime.Str("space-id"))
		nodeToken := strings.TrimSpace(runtime.Str("node-token"))

		fmt.Fprintf(runtime.IO().ErrOut, "Copying wiki node %s from space %s\n",
			common.MaskToken(nodeToken), common.MaskToken(spaceID))

		data, err := runtime.CallAPI("POST",
			fmt.Sprintf("/open-apis/wiki/v2/spaces/%s/nodes/%s/copy",
				validate.EncodePathSegment(spaceID),
				validate.EncodePathSegment(nodeToken)),
			nil, buildNodeCopyBody(runtime))
		if err != nil {
			return err
		}

		node, err := parseWikiNodeRecord(common.GetMap(data, "node"))
		if err != nil {
			return err
		}

		fmt.Fprintf(runtime.IO().ErrOut, "Copied to node %s in space %s\n",
			common.MaskToken(node.NodeToken), common.MaskToken(node.SpaceID))
		out := wikiNodeCopyOutput(node)
		if u := wikiNodeURL(runtime.Config.Brand, node); u != "" {
			out["url"] = u
		}
		runtime.OutFormat(out, nil, func(w io.Writer) {
			renderWikiNodeCopyPretty(w, out)
		})
		return nil
	},
}

WikiNodeCopy copies a wiki node into a target space or under a target parent node.

View Source
var WikiNodeCreate = common.Shortcut{
	Service:     "wiki",
	Command:     "+node-create",
	Description: "Create a wiki node with automatic space resolution",
	Risk:        "write",
	Scopes:      []string{"wiki:node:create", "wiki:node:read", "wiki:space:read"},
	AuthTypes:   []string{"user", "bot"},
	Flags: []common.Flag{
		{Name: "space-id", Desc: "target wiki space ID; use my_library for the personal document library"},
		{Name: "parent-node-token", Desc: "parent wiki node token; if set, the new node is created under that parent"},
		{Name: "title", Desc: "node title"},
		{Name: "node-type", Default: wikiNodeTypeOrigin, Desc: "node type", Enum: []string{wikiNodeTypeOrigin, wikiNodeTypeShortcut}},
		{Name: "obj-type", Default: "docx", Desc: "target object type", Enum: wikiObjectTypes},
		{Name: "origin-node-token", Desc: "source node token when --node-type=shortcut"},
	},
	Tips: []string{
		"If --space-id and --parent-node-token are both omitted, user identity falls back to my_library.",
		"Use --node-type shortcut --origin-node-token <token> to create a shortcut node.",
	},
	Validate: func(ctx context.Context, runtime *common.RuntimeContext) error {
		return validateWikiNodeCreateSpec(readWikiNodeCreateSpec(runtime), runtime.As())
	},
	DryRun: func(ctx context.Context, runtime *common.RuntimeContext) *common.DryRunAPI {
		dry := buildWikiNodeCreateDryRun(readWikiNodeCreateSpec(runtime))
		if runtime.IsBot() {
			dry.Desc("After wiki node creation succeeds in bot mode, the CLI will also try to grant the current CLI user full_access (可管理权限) on the new wiki node.")
		}
		return dry
	},
	Execute: func(ctx context.Context, runtime *common.RuntimeContext) error {
		spec := readWikiNodeCreateSpec(runtime)

		fmt.Fprintf(runtime.IO().ErrOut, "Creating wiki node...\n")
		execution, err := runWikiNodeCreate(ctx, wikiNodeCreateAPI{runtime: runtime}, runtime.As(), spec, runtime.IO().ErrOut)
		if err != nil {
			return err
		}

		fmt.Fprintf(runtime.IO().ErrOut, "Created wiki node in space %s via %s.\n", execution.ResolvedSpace.SpaceID, execution.ResolvedSpace.ResolvedBy)
		runtime.Out(augmentWikiNodeCreateOutput(runtime, execution), nil)
		return nil
	},
}

WikiNodeCreate wraps wiki node creation with shortcut-specific ergonomics: it can infer the target space from the parent node or the caller's personal document library instead of forcing users to pass a numeric space ID first.

View Source
var WikiNodeDelete = common.Shortcut{
	Service:     "wiki",
	Command:     "+node-delete",
	Description: "Delete a wiki node, polling the async delete task when needed",
	Risk:        "high-risk-write",

	Scopes:    []string{"wiki:node:create"},
	AuthTypes: []string{"user", "bot"},
	Flags: []common.Flag{
		{Name: "node-token", Desc: "wiki node_token, cloud-doc obj_token, or a Lark URL embedding one of them", Required: true},

		{Name: "obj-type", Desc: "token kind; no default — pass explicitly when --node-token is a raw token (URL inputs auto-infer)", Enum: wikiNodeDeleteObjTypes},
		{Name: "space-id", Desc: "wiki space ID; auto-resolved via get_node when omitted"},
		{Name: "include-children", Type: "bool", Default: "true", Desc: "cascade delete the subtree (default); pass --include-children=false to lift direct children up to the parent"},
	},
	Tips: []string{
		"Deletion is irreversible; double-check --node-token and --obj-type before running.",
		"This is a high-risk-write command; pass --yes to confirm the deletion.",
		"--node-token accepts a raw token (wikcnXXX, docxXXX, ...) or a Lark URL like https://feishu.cn/wiki/<token> or https://feishu.cn/docx/<token>; URL paths also imply --obj-type.",
		"Run +node-get first to confirm space_id / obj_type when in doubt.",
		"Auto-resolving space_id (when --space-id is omitted) also calls get_node, which needs the wiki:node:retrieve scope; pass --space-id to skip that lookup if your token only carries wiki:node:create.",
		"Async deletes return a task_id; this command polls for a bounded window and then prints a follow-up drive +task_result command.",
	},
	Validate: func(ctx context.Context, runtime *common.RuntimeContext) error {
		_, err := readWikiNodeDeleteSpec(runtime)
		return err
	},
	DryRun: func(ctx context.Context, runtime *common.RuntimeContext) *common.DryRunAPI {
		spec, err := readWikiNodeDeleteSpec(runtime)
		if err != nil {
			return common.NewDryRunAPI().Set("error", err.Error())
		}
		return buildWikiNodeDeleteDryRun(spec)
	},
	Execute: func(ctx context.Context, runtime *common.RuntimeContext) error {
		spec, err := readWikiNodeDeleteSpec(runtime)
		if err != nil {
			return err
		}

		out, err := runWikiNodeDelete(ctx, wikiNodeDeleteAPI{runtime: runtime}, runtime, spec)
		if err != nil {
			return err
		}
		runtime.Out(out, nil)
		return nil
	},
}

WikiNodeDelete deletes a wiki node (or pulls a cloud doc out of Wiki). The API mirrors +delete-space — synchronous on small deletes, async with a task_id for cascade deletes — so this shortcut shares the async-polling helper. Space ID is optional: when omitted, +node-delete first looks up the node via get_node to resolve the space ID so callers do not have to chain commands.

View Source
var WikiNodeGet = common.Shortcut{
	Service:     "wiki",
	Command:     "+node-get",
	Description: "Get wiki node details by node_token, obj_token, or Lark URL",
	Risk:        "read",
	Scopes:      []string{"wiki:node:retrieve"},
	AuthTypes:   []string{"user", "bot"},
	HasFormat:   true,
	Flags: []common.Flag{

		{Name: "node-token", Desc: "wiki node_token, obj_token, or a Lark URL embedding one of them"},
		{Name: "token", Desc: "DEPRECATED: use --node-token", Hidden: true},
		{Name: "obj-type", Desc: "obj_type when --node-token is an obj_token; auto-inferred from URL path when omitted", Enum: wikiNodeGetObjTypeEnum},
		{Name: "space-id", Desc: "optional: assert the resolved node lives in this space"},
	},
	Tips: []string{
		"--node-token accepts a raw token (wikcnXXX, docxXXX, ...) or a Lark URL like https://feishu.cn/wiki/<token> or https://feishu.cn/docx/<token>.",
		"For raw obj_tokens (not starting with wik), pass --obj-type so the API knows how to resolve them; URL inputs infer it from the path.",
		"Pair with +move / +node-copy / +delete-space to confirm space_id, obj_type, and parent before mutating.",
		"--token is the deprecated original name and still works for backward compatibility; new scripts should use --node-token.",
	},
	PostMount: func(cmd *cobra.Command) {

		_ = cmd.Flags().MarkDeprecated("token", "use --node-token instead")
	},
	Validate: func(ctx context.Context, runtime *common.RuntimeContext) error {
		_, err := readWikiNodeGetSpec(runtime)
		return err
	},
	DryRun: func(ctx context.Context, runtime *common.RuntimeContext) *common.DryRunAPI {
		spec, err := readWikiNodeGetSpec(runtime)
		if err != nil {
			return common.NewDryRunAPI().Set("error", err.Error())
		}
		return buildWikiNodeGetDryRun(spec)
	},
	Execute: func(ctx context.Context, runtime *common.RuntimeContext) error {
		spec, err := readWikiNodeGetSpec(runtime)
		if err != nil {
			return err
		}

		fmt.Fprintf(runtime.IO().ErrOut, "Fetching wiki node %s...\n", common.MaskToken(spec.Token))

		data, err := runtime.CallAPI("GET", "/open-apis/wiki/v2/spaces/get_node", spec.RequestParams(), nil)
		if err != nil {
			return err
		}
		raw := common.GetMap(data, "node")
		node, err := parseWikiNodeRecord(raw)
		if err != nil {
			return err
		}

		if spec.SpaceID != "" && node.SpaceID != "" && spec.SpaceID != node.SpaceID {
			return output.ErrValidation(
				"--space-id %q does not match the resolved node space %q (node_token=%s)",
				spec.SpaceID, node.SpaceID, node.NodeToken,
			)
		}
		if spec.SpaceID != "" && node.SpaceID == "" {

			fmt.Fprintf(runtime.IO().ErrOut,
				"Warning: --space-id %q could not be verified; the resolved node carries no space_id.\n",
				spec.SpaceID)
		}

		out := wikiNodeGetOutput(node, raw)
		runtime.OutFormat(out, nil, func(w io.Writer) {
			renderWikiNodeGetPretty(w, out)
		})
		return nil
	},
}

WikiNodeGet wraps wiki.spaces.get_node so callers can resolve a node by node_token, obj_token, or a Lark URL without hand-rolling a `wiki spaces get_node --params ...` invocation. The shortcut prints a formatted view of the node (title / obj_type / obj_token / parent / creator / updated_at) and is intended as the "what am I about to touch?" step before +move / +node-copy / +delete-space.

View Source
var WikiNodeList = common.Shortcut{
	Service:     "wiki",
	Command:     "+node-list",
	Description: "List wiki nodes in a space or under a parent node",
	Risk:        "read",

	Scopes:    []string{"wiki:node:retrieve"},
	AuthTypes: []string{"user", "bot"},
	HasFormat: true,
	Flags: []common.Flag{
		{Name: "space-id", Desc: "wiki space ID; use my_library for the personal document library, or +space-list to discover other space IDs", Required: true},
		{Name: "parent-node-token", Desc: "parent node token; if omitted, lists the root-level nodes of the space"},
		{Name: "page-size", Type: "int", Default: strconv.Itoa(wikiNodeListDefaultPageSize), Desc: fmt.Sprintf("page size, 1-%d", wikiNodeListMaxPageSize)},
		{Name: "page-token", Desc: "page token; implies single-page fetch (no auto-pagination)"},
		{Name: "page-all", Type: "bool", Desc: "automatically paginate through all pages (capped by --page-limit)"},
		{Name: "page-limit", Type: "int", Default: "10", Desc: "max pages to fetch with --page-all (default 10, 0 = unlimited)"},
	},
	Tips: []string{
		"Default fetches a single page; pass --page-all to walk every page (large knowledge bases can be huge — keep an eye on --page-limit).",
		"Use --parent-node-token to drill into a sub-directory.",
		"Run +space-list first to discover your space IDs, including the personal document library.",
		"--space-id my_library is a per-user alias and is only valid with --as user.",
	},
	Validate: func(ctx context.Context, runtime *common.RuntimeContext) error {
		spaceID := strings.TrimSpace(runtime.Str("space-id"))

		if runtime.As().IsBot() && spaceID == wikiMyLibrarySpaceID {
			return output.ErrValidation("bot identity does not support --space-id my_library; use an explicit --space-id")
		}
		if err := validateOptionalResourceName(spaceID, "--space-id"); err != nil {
			return err
		}
		if err := validateOptionalResourceName(strings.TrimSpace(runtime.Str("parent-node-token")), "--parent-node-token"); err != nil {
			return err
		}
		return validateWikiListPagination(runtime, wikiNodeListMaxPageSize)
	},
	DryRun: func(ctx context.Context, runtime *common.RuntimeContext) *common.DryRunAPI {
		spaceID := strings.TrimSpace(runtime.Str("space-id"))
		params := map[string]interface{}{"page_size": runtime.Int("page-size")}
		if pt := strings.TrimSpace(runtime.Str("parent-node-token")); pt != "" {
			params["parent_node_token"] = pt
		}
		if pt := strings.TrimSpace(runtime.Str("page-token")); pt != "" {
			params["page_token"] = pt
		}
		d := common.NewDryRunAPI()
		if wikiListShouldAutoPaginate(runtime) {
			d.Desc("Auto-paginates through all pages (capped by --page-limit when > 0)")
		}

		if spaceID == wikiMyLibrarySpaceID {
			return d.
				Desc("2-step orchestration: resolve my_library -> list nodes").
				GET("/open-apis/wiki/v2/spaces/my_library").
				Desc("[1] Resolve my_library space ID").
				GET(fmt.Sprintf("/open-apis/wiki/v2/spaces/%s/nodes", "<resolved_space_id>")).
				Desc("[2] List nodes").
				Params(params).
				Set("space_id", "<resolved_space_id>")
		}
		return d.
			GET(fmt.Sprintf("/open-apis/wiki/v2/spaces/%s/nodes", validate.EncodePathSegment(spaceID))).
			Params(params).
			Set("space_id", spaceID)
	},
	Execute: func(ctx context.Context, runtime *common.RuntimeContext) error {
		warnIfConflictingPagingFlags(runtime)
		spaceID := strings.TrimSpace(runtime.Str("space-id"))

		if spaceID == wikiMyLibrarySpaceID {
			resolved, err := resolveMyLibrarySpaceID(runtime)
			if err != nil {
				return err
			}
			fmt.Fprintf(runtime.IO().ErrOut, "Resolved my_library to space %s\n", common.MaskToken(resolved))
			spaceID = resolved
		}

		nodes, hasMore, nextToken, err := fetchWikiNodes(runtime, spaceID)
		if err != nil {
			return err
		}
		fmt.Fprintf(runtime.IO().ErrOut, "Found %d node(s)\n", len(nodes))
		outData := map[string]interface{}{
			"nodes":      nodes,
			"has_more":   hasMore,
			"page_token": nextToken,
		}
		runtime.OutFormat(outData, &output.Meta{Count: len(nodes)}, func(w io.Writer) {
			renderWikiNodesPretty(w, nodes, hasMore, nextToken)
		})
		return nil
	},
}

WikiNodeList lists child nodes in a wiki space or under a parent node.

View Source
var WikiSpaceCreate = common.Shortcut{
	Service:     "wiki",
	Command:     "+space-create",
	Description: "Create a wiki space",
	Risk:        "write",

	Scopes:    []string{"wiki:space:write_only"},
	AuthTypes: []string{"user"},
	Flags: []common.Flag{
		{Name: "name", Desc: "wiki space name", Required: true},
		{Name: "description", Desc: "wiki space description"},
	},
	Tips: []string{
		"Only --as user is supported; the create API does not accept a tenant/bot token.",
		"The underlying spaces.create API is flagged danger in the schema browser; a space is recoverable via `wiki +delete-space` if created by mistake.",
		"--name is required: an unnamed space is almost always an accident and is hard to find later.",
	},
	Validate: func(ctx context.Context, runtime *common.RuntimeContext) error {
		_, err := readWikiSpaceCreateSpec(runtime)
		return err
	},
	DryRun: func(ctx context.Context, runtime *common.RuntimeContext) *common.DryRunAPI {
		spec, err := readWikiSpaceCreateSpec(runtime)
		if err != nil {
			return common.NewDryRunAPI().Set("error", err.Error())
		}
		return common.NewDryRunAPI().
			POST(wikiSpacesAPIPath).
			Body(spec.RequestBody())
	},
	Execute: func(ctx context.Context, runtime *common.RuntimeContext) error {
		spec, err := readWikiSpaceCreateSpec(runtime)
		if err != nil {
			return err
		}

		fmt.Fprintf(runtime.IO().ErrOut, "Creating wiki space %q...\n", spec.Name)

		data, err := runtime.CallAPI("POST", wikiSpacesAPIPath, nil, spec.RequestBody())
		if err != nil {
			return err
		}

		raw := common.GetMap(data, "space")
		if raw == nil {
			return output.Errorf(output.ExitAPI, "api_error", "wiki space create returned no space")
		}

		out := wikiSpaceCreateOutput(raw)
		fmt.Fprintf(runtime.IO().ErrOut, "Created wiki space %s\n", common.MaskToken(common.GetString(out, "space_id")))
		runtime.Out(out, nil)
		return nil
	},
}

WikiSpaceCreate wraps wiki.spaces.create. The raw API only takes two optional string fields, so the shortcut's value is flag ergonomics (no hand-written --params JSON), output flattening (data.space.* lifted to the top level), and a dry-run preview.

The API only accepts a user access token (no tenant/bot), so AuthTypes is user-only — the framework's CheckIdentity rejects --as bot for us.

View Source
var WikiSpaceList = common.Shortcut{
	Service:     "wiki",
	Command:     "+space-list",
	Description: "List wiki spaces accessible to the caller",
	Risk:        "read",

	Scopes:    []string{"wiki:space:retrieve"},
	AuthTypes: []string{"user", "bot"},
	HasFormat: true,
	Flags: []common.Flag{
		{Name: "page-size", Type: "int", Default: strconv.Itoa(wikiSpaceListDefaultPageSize), Desc: fmt.Sprintf("page size, 1-%d", wikiSpaceListMaxPageSize)},
		{Name: "page-token", Desc: "page token; implies single-page fetch (no auto-pagination)"},
		{Name: "page-all", Type: "bool", Desc: "automatically paginate through all pages (capped by --page-limit)"},
		{Name: "page-limit", Type: "int", Default: "10", Desc: "max pages to fetch with --page-all (default 10, 0 = unlimited)"},
	},
	Tips: []string{
		"Default fetches a single page (matches other list shortcuts in this CLI); pass --page-all to pull every page.",
		"The underlying API never returns the my_library personal library; resolve it via `wiki spaces get --params '{\"space_id\":\"my_library\"}'`.",
	},
	Validate: func(ctx context.Context, runtime *common.RuntimeContext) error {
		return validateWikiListPagination(runtime, wikiSpaceListMaxPageSize)
	},
	DryRun: func(ctx context.Context, runtime *common.RuntimeContext) *common.DryRunAPI {
		params := map[string]interface{}{"page_size": runtime.Int("page-size")}
		if pt := strings.TrimSpace(runtime.Str("page-token")); pt != "" {
			params["page_token"] = pt
		}
		dry := common.NewDryRunAPI()

		if wikiListShouldAutoPaginate(runtime) {
			dry.Desc("Auto-paginates through all pages (capped by --page-limit when > 0)")
		}
		return dry.GET(wikiSpacesAPIPath).Params(params)
	},
	Execute: func(ctx context.Context, runtime *common.RuntimeContext) error {
		warnIfConflictingPagingFlags(runtime)
		spaces, hasMore, nextToken, err := fetchWikiSpaces(runtime)
		if err != nil {
			return err
		}
		fmt.Fprintf(runtime.IO().ErrOut, "Found %d wiki space(s)\n", len(spaces))
		outData := map[string]interface{}{
			"spaces":     spaces,
			"has_more":   hasMore,
			"page_token": nextToken,
		}
		runtime.OutFormat(outData, &output.Meta{Count: len(spaces)}, func(w io.Writer) {
			renderWikiSpacesPretty(w, spaces, hasMore, nextToken)
		})
		return nil
	},
}

WikiSpaceList lists all wiki spaces the caller has access to.

Functions

func Shortcuts

func Shortcuts() []common.Shortcut

Shortcuts returns all wiki shortcuts.

Types

This section is empty.

Jump to

Keyboard shortcuts

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