mirror of
https://github.com/larksuite/cli.git
synced 2026-07-07 00:55:53 +08:00
Compare commits
3 Commits
chore/spli
...
v1.0.30
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a18504b1f9 | ||
|
|
5e0ac02f08 | ||
|
|
b0c9a4d74e |
17
CHANGELOG.md
17
CHANGELOG.md
@@ -2,6 +2,22 @@
|
||||
|
||||
All notable changes to this project will be documented in this file.
|
||||
|
||||
## [v1.0.30] - 2026-05-13
|
||||
|
||||
### Features
|
||||
|
||||
- **im**: Add `--chat-mode topic` to `+chat-create` (#790)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- **auth**: Support comma-separated `--scope` in `auth login` (#764)
|
||||
- **auth**: Clarify URL handling in auth messages and docs (#856)
|
||||
- **bind**: Accept `~/` paths in OpenClaw secret references (#839)
|
||||
|
||||
### Tests
|
||||
|
||||
- **update**: Isolate stamp writes from real `~/.lark-cli/skills.stamp` (#858)
|
||||
|
||||
## [v1.0.29] - 2026-05-12
|
||||
|
||||
### Features
|
||||
@@ -676,6 +692,7 @@ Bundled AI agent skills for intelligent assistance:
|
||||
- Bilingual documentation (English & Chinese).
|
||||
- CI/CD pipelines: linting, testing, coverage reporting, and automated releases.
|
||||
|
||||
[v1.0.30]: https://github.com/larksuite/cli/releases/tag/v1.0.30
|
||||
[v1.0.29]: https://github.com/larksuite/cli/releases/tag/v1.0.29
|
||||
[v1.0.28]: https://github.com/larksuite/cli/releases/tag/v1.0.28
|
||||
[v1.0.27]: https://github.com/larksuite/cli/releases/tag/v1.0.27
|
||||
|
||||
@@ -6,14 +6,14 @@
|
||||
|
||||
[中文版](./README.zh.md) | [English](./README.md)
|
||||
|
||||
The official [Lark/Feishu](https://www.larksuite.com/) CLI tool, maintained by the [larksuite](https://github.com/larksuite) team — built for humans and AI Agents. Covers core business domains including Messenger, Docs, Base, Sheets, Slides, Calendar, Mail, Tasks, Meetings, Markdown, and more, with 200+ commands and 25 AI Agent [Skills](./skills/).
|
||||
The official [Lark/Feishu](https://www.larksuite.com/) CLI tool, maintained by the [larksuite](https://github.com/larksuite) team — built for humans and AI Agents. Covers core business domains including Messenger, Docs, Base, Sheets, Slides, Calendar, Mail, Tasks, Meetings, Markdown, and more, with 200+ commands and 24 AI Agent [Skills](./skills/).
|
||||
|
||||
[Install](#installation--quick-start) · [AI Agent Skills](#agent-skills) · [Auth](#authentication) · [Commands](#three-layer-command-system) · [Advanced](#advanced-usage) · [Security](#security--risk-warnings-read-before-use) · [Contributing](#contributing)
|
||||
|
||||
## Why lark-cli?
|
||||
|
||||
- **Agent-Native Design** — 25 structured [Skills](./skills/) out of the box, compatible with popular AI tools — Agents can operate Lark with zero extra setup
|
||||
- **Wide Coverage** — 17 business domains, 200+ curated commands, 25 AI Agent [Skills](./skills/)
|
||||
- **Agent-Native Design** — 24 structured [Skills](./skills/) out of the box, compatible with popular AI tools — Agents can operate Lark with zero extra setup
|
||||
- **Wide Coverage** — 17 business domains, 200+ curated commands, 24 AI Agent [Skills](./skills/)
|
||||
- **AI-Friendly & Optimized** — Every command is tested with real Agents, featuring concise parameters, smart defaults, and structured output to maximize Agent call success rates
|
||||
- **Open Source, Zero Barriers** — MIT license, ready to use, just `npm install`
|
||||
- **Up and Running in 3 Minutes** — One-click app creation, interactive login, from install to first API call in just 3 steps
|
||||
@@ -142,8 +142,7 @@ lark-cli auth status
|
||||
| `lark-drive` | Upload, download files, manage permissions & comments |
|
||||
| `lark-markdown` | Create, fetch, and overwrite Drive-native Markdown files |
|
||||
| `lark-sheets` | Create, read, write, append, find, export spreadsheets |
|
||||
| `lark-slides-creator` | Create polished presentations with planning, design, asset, template, and validation workflows |
|
||||
| `lark-slides` | Low-level Slides XML/API read/write operations |
|
||||
| `lark-slides` | Create and manage presentations, read presentation content, and add or remove slides |
|
||||
| `lark-base` | Tables, fields, records, views, dashboards, data aggregation & analytics |
|
||||
| `lark-task` | Tasks, task lists, subtasks, reminders, member assignment |
|
||||
| `lark-mail` | Browse, search, read emails, send, reply, forward, draft management, watch new mail |
|
||||
|
||||
@@ -62,7 +62,7 @@ browser. Run it in the background and retrieve the verification URL from its out
|
||||
}
|
||||
cmdutil.SetSupportedIdentities(cmd, []string{"user"})
|
||||
|
||||
cmd.Flags().StringVar(&opts.Scope, "scope", "", "scopes to request (space-separated)")
|
||||
cmd.Flags().StringVar(&opts.Scope, "scope", "", "scopes to request (space- or comma-separated)")
|
||||
cmd.Flags().BoolVar(&opts.Recommend, "recommend", false, "request only recommended (auto-approve) scopes")
|
||||
available := sortedKnownDomains()
|
||||
cmd.Flags().StringSliceVar(&opts.Domains, "domain", nil,
|
||||
@@ -185,7 +185,11 @@ func authLoginRun(opts *LoginOptions) error {
|
||||
}
|
||||
}
|
||||
|
||||
finalScope := opts.Scope
|
||||
// Normalize --scope so users can pass either OAuth-standard space-separated
|
||||
// values or the more natural comma-separated list. RFC 6749 §3.3 mandates
|
||||
// space-delimited scopes in the wire request, so the device authorization
|
||||
// endpoint rejects raw "a,b" strings as a single malformed scope.
|
||||
finalScope := normalizeScopeInput(opts.Scope)
|
||||
|
||||
// Resolve scopes from domain/permission filters
|
||||
if len(selectedDomains) > 0 || opts.Recommend {
|
||||
@@ -532,6 +536,40 @@ func shortcutSupportsIdentity(sc common.Shortcut, identity string) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
// normalizeScopeInput accepts a user-supplied --scope value that may use
|
||||
// commas, spaces, tabs, or newlines (or any mix) as separators and returns the
|
||||
// canonical OAuth 2.0 wire form: a single space-joined string with empties
|
||||
// trimmed and duplicates removed (first occurrence wins; order preserved).
|
||||
//
|
||||
// Examples:
|
||||
//
|
||||
// "vc:note:read,vc:meeting.meetingevent:read" -> "vc:note:read vc:meeting.meetingevent:read"
|
||||
// "a, b , c" -> "a b c"
|
||||
// "a b a" -> "a b"
|
||||
// "" -> ""
|
||||
func normalizeScopeInput(raw string) string {
|
||||
if raw == "" {
|
||||
return ""
|
||||
}
|
||||
// Treat both commas and any whitespace as separators.
|
||||
fields := strings.FieldsFunc(raw, func(r rune) bool {
|
||||
return r == ',' || r == ' ' || r == '\t' || r == '\n' || r == '\r'
|
||||
})
|
||||
if len(fields) == 0 {
|
||||
return ""
|
||||
}
|
||||
seen := make(map[string]struct{}, len(fields))
|
||||
out := make([]string, 0, len(fields))
|
||||
for _, f := range fields {
|
||||
if _, ok := seen[f]; ok {
|
||||
continue
|
||||
}
|
||||
seen[f] = struct{}{}
|
||||
out = append(out, f)
|
||||
}
|
||||
return strings.Join(out, " ")
|
||||
}
|
||||
|
||||
// suggestDomain finds the best "did you mean" match for an unknown domain.
|
||||
func suggestDomain(input string, known map[string]bool) string {
|
||||
// Check common cases: prefix match or input is a substring
|
||||
|
||||
@@ -70,6 +70,32 @@ func TestSuggestDomain_ExactMatch(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestNormalizeScopeInput(t *testing.T) {
|
||||
cases := []struct {
|
||||
name string
|
||||
in string
|
||||
want string
|
||||
}{
|
||||
{"empty", "", ""},
|
||||
{"single", "vc:note:read", "vc:note:read"},
|
||||
{"comma", "vc:note:read,vc:meeting.meetingevent:read", "vc:note:read vc:meeting.meetingevent:read"},
|
||||
{"space", "vc:note:read vc:meeting.meetingevent:read", "vc:note:read vc:meeting.meetingevent:read"},
|
||||
{"comma_and_spaces", "vc:note:read, vc:meeting.meetingevent:read", "vc:note:read vc:meeting.meetingevent:read"},
|
||||
{"mixed_separators", "a, b\tc\nd e", "a b c d e"},
|
||||
{"trim_and_dedup", " a , b , a ", "a b"},
|
||||
{"trailing_separators", "a,b,,", "a b"},
|
||||
{"only_separators", " , , ", ""},
|
||||
{"tab_separated", "im:message:send\toffline_access", "im:message:send offline_access"},
|
||||
}
|
||||
for _, tc := range cases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
if got := normalizeScopeInput(tc.in); got != tc.want {
|
||||
t.Errorf("normalizeScopeInput(%q) = %q, want %q", tc.in, got, tc.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestShortcutSupportsIdentity_DefaultUser(t *testing.T) {
|
||||
// Empty AuthTypes defaults to ["user"]
|
||||
sc := common.Shortcut{AuthTypes: nil}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@larksuite/cli",
|
||||
"version": "1.0.29",
|
||||
"version": "1.0.30",
|
||||
"description": "The official CLI for Lark/Feishu open platform",
|
||||
"bin": {
|
||||
"lark-cli": "scripts/run.js"
|
||||
|
||||
@@ -15,6 +15,7 @@ import (
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
// mustMarshalDryRun marshals v to a JSON string, calling t.Fatalf on error.
|
||||
func mustMarshalDryRun(t *testing.T, v interface{}) string {
|
||||
t.Helper()
|
||||
|
||||
@@ -25,6 +26,9 @@ func mustMarshalDryRun(t *testing.T, v interface{}) string {
|
||||
return string(b)
|
||||
}
|
||||
|
||||
// newTestRuntimeContext builds a *common.RuntimeContext backed by a cobra
|
||||
// command whose flags are populated from the provided string and bool maps,
|
||||
// for unit-testing shortcut bodies, validators, and dry-run shapes.
|
||||
func newTestRuntimeContext(t *testing.T, stringFlags map[string]string, boolFlags map[string]bool) *common.RuntimeContext {
|
||||
t.Helper()
|
||||
|
||||
@@ -55,6 +59,9 @@ func newTestRuntimeContext(t *testing.T, stringFlags map[string]string, boolFlag
|
||||
return &common.RuntimeContext{Cmd: cmd}
|
||||
}
|
||||
|
||||
// newMessagesSearchTestRuntimeContext is the messages-search variant of
|
||||
// newTestRuntimeContext: registers the search-specific --page-size flag
|
||||
// before applying caller-provided values.
|
||||
func newMessagesSearchTestRuntimeContext(t *testing.T, stringFlags map[string]string, boolFlags map[string]bool) *common.RuntimeContext {
|
||||
t.Helper()
|
||||
|
||||
@@ -86,6 +93,8 @@ func newMessagesSearchTestRuntimeContext(t *testing.T, stringFlags map[string]st
|
||||
return &common.RuntimeContext{Cmd: cmd}
|
||||
}
|
||||
|
||||
// TestBuildCreateChatBody verifies the request body assembled when every
|
||||
// flag is populated, including the default chat_mode="group".
|
||||
func TestBuildCreateChatBody(t *testing.T) {
|
||||
runtime := newTestRuntimeContext(t, map[string]string{
|
||||
"type": "public",
|
||||
@@ -94,11 +103,13 @@ func TestBuildCreateChatBody(t *testing.T) {
|
||||
"users": "ou_1, ou_2",
|
||||
"bots": "cli_1, cli_2",
|
||||
"owner": "ou_owner",
|
||||
"chat-mode": "group",
|
||||
}, nil)
|
||||
|
||||
got := buildCreateChatBody(runtime)
|
||||
want := map[string]interface{}{
|
||||
"chat_type": "public",
|
||||
"chat_mode": "group",
|
||||
"name": "Team Chat",
|
||||
"description": "daily sync",
|
||||
"user_id_list": []string{
|
||||
@@ -116,6 +127,43 @@ func TestBuildCreateChatBody(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
// TestBuildCreateChatBody_TopicMode verifies that --chat-mode topic produces
|
||||
// chat_mode="topic" in the request body, the topic-chat creation path.
|
||||
func TestBuildCreateChatBody_TopicMode(t *testing.T) {
|
||||
runtime := newTestRuntimeContext(t, map[string]string{
|
||||
"type": "public",
|
||||
"name": "Topic Group",
|
||||
"chat-mode": "topic",
|
||||
}, nil)
|
||||
|
||||
got := buildCreateChatBody(runtime)
|
||||
want := map[string]interface{}{
|
||||
"chat_type": "public",
|
||||
"chat_mode": "topic",
|
||||
"name": "Topic Group",
|
||||
}
|
||||
if !reflect.DeepEqual(got, want) {
|
||||
t.Fatalf("buildCreateChatBody() = %#v, want %#v", got, want)
|
||||
}
|
||||
}
|
||||
|
||||
// TestBuildCreateChatBody_EmptyChatModeFallsBack pins the defensive fallback:
|
||||
// explicit `--chat-mode ""` slips past validateEnumFlags (which skips empty
|
||||
// values), but buildCreateChatBody must still emit chat_mode="group" rather
|
||||
// than an empty string with unspecified server semantics.
|
||||
func TestBuildCreateChatBody_EmptyChatModeFallsBack(t *testing.T) {
|
||||
runtime := newTestRuntimeContext(t, map[string]string{
|
||||
"type": "public",
|
||||
"name": "Fallback Test",
|
||||
"chat-mode": "",
|
||||
}, nil)
|
||||
|
||||
got := buildCreateChatBody(runtime)
|
||||
if got["chat_mode"] != "group" {
|
||||
t.Fatalf("buildCreateChatBody() chat_mode = %#v, want \"group\"", got["chat_mode"])
|
||||
}
|
||||
}
|
||||
|
||||
// TestSplitMembers verifies the delegation wrapper; core logic is tested in TestSplitCSV. [#17]
|
||||
func TestSplitMembers(t *testing.T) {
|
||||
got := common.SplitCSV(" ou_1, ,ou_2 ,, ou_3 ")
|
||||
@@ -591,10 +639,12 @@ func TestMessagesSearchPaginationConfig(t *testing.T) {
|
||||
})
|
||||
}
|
||||
|
||||
// TestShortcutDryRunShapes verifies that each shortcut's DryRun function
|
||||
// produces the expected API path, query parameters, and request body.
|
||||
func TestShortcutDryRunShapes(t *testing.T) {
|
||||
t.Run("ImChatCreate dry run includes params and body", func(t *testing.T) {
|
||||
cmd := &cobra.Command{Use: "test"}
|
||||
for _, name := range []string{"type", "name", "users", "owner"} {
|
||||
for _, name := range []string{"type", "name", "users", "owner", "chat-mode"} {
|
||||
cmd.Flags().String(name, "", "")
|
||||
}
|
||||
cmd.Flags().Bool("set-bot-manager", false, "")
|
||||
@@ -604,9 +654,10 @@ func TestShortcutDryRunShapes(t *testing.T) {
|
||||
_ = cmd.Flags().Set("users", "ou_1,ou_2")
|
||||
_ = cmd.Flags().Set("owner", "ou_owner")
|
||||
_ = cmd.Flags().Set("set-bot-manager", "true")
|
||||
_ = cmd.Flags().Set("chat-mode", "group")
|
||||
runtime := common.TestNewRuntimeContextWithIdentity(cmd, nil, "bot")
|
||||
got := mustMarshalDryRun(t, ImChatCreate.DryRun(context.Background(), runtime))
|
||||
if !strings.Contains(got, `"/open-apis/im/v1/chats"`) || !strings.Contains(got, `"set_bot_manager":true`) || !strings.Contains(got, `"chat_type":"public"`) {
|
||||
if !strings.Contains(got, `"/open-apis/im/v1/chats"`) || !strings.Contains(got, `"set_bot_manager":true`) || !strings.Contains(got, `"chat_type":"public"`) || !strings.Contains(got, `"chat_mode":"group"`) {
|
||||
t.Fatalf("ImChatCreate.DryRun() = %s", got)
|
||||
}
|
||||
})
|
||||
|
||||
@@ -16,10 +16,14 @@ import (
|
||||
larkcore "github.com/larksuite/oapi-sdk-go/v3/core"
|
||||
)
|
||||
|
||||
// ImChatCreate is the +chat-create shortcut: creates a group chat or topic
|
||||
// chat via POST /open-apis/im/v1/chats. Supports user and bot identities;
|
||||
// --chat-mode selects group (default) or topic; --type selects private
|
||||
// (default) or public; --users/--bots invite members at creation.
|
||||
var ImChatCreate = common.Shortcut{
|
||||
Service: "im",
|
||||
Command: "+chat-create",
|
||||
Description: "Create a group chat; user/bot; creates private/public chats, invites users/bots, optionally sets bot manager",
|
||||
Description: "Create a group chat or topic chat; user/bot; --chat-mode group|topic; private/public; invites users/bots; optionally sets bot manager",
|
||||
Risk: "write",
|
||||
UserScopes: []string{"im:chat:create_by_user"},
|
||||
BotScopes: []string{"im:chat:create"},
|
||||
@@ -32,6 +36,7 @@ var ImChatCreate = common.Shortcut{
|
||||
{Name: "bots", Desc: "comma-separated bot app IDs (cli_xxx) to invite, max 5"},
|
||||
{Name: "owner", Desc: "owner open_id (ou_xxx); defaults to bot (--as bot) or authorized user (--as user)"},
|
||||
{Name: "type", Default: "private", Desc: "chat type", Enum: []string{"private", "public"}},
|
||||
{Name: "chat-mode", Default: "group", Desc: "group mode (\"topic\" creates a topic chat; differs from a normal group in topic-message mode)", Enum: []string{"group", "topic"}},
|
||||
{Name: "set-bot-manager", Type: "bool", Desc: "set the bot that creates this chat as manager (bot identity only)"},
|
||||
},
|
||||
DryRun: func(ctx context.Context, runtime *common.RuntimeContext) *common.DryRunAPI {
|
||||
@@ -141,9 +146,18 @@ var ImChatCreate = common.Shortcut{
|
||||
},
|
||||
}
|
||||
|
||||
// buildCreateChatBody assembles the POST /open-apis/im/v1/chats request
|
||||
// body. chat_mode is always emitted; an empty value (which can slip past
|
||||
// validateEnumFlags, since that helper skips empty strings) is pinned to
|
||||
// "group" so the wire never carries an unspecified chat_mode value.
|
||||
func buildCreateChatBody(runtime *common.RuntimeContext) map[string]interface{} {
|
||||
chatMode := runtime.Str("chat-mode")
|
||||
if chatMode == "" {
|
||||
chatMode = "group"
|
||||
}
|
||||
body := map[string]interface{}{
|
||||
"chat_type": runtime.Str("type"),
|
||||
"chat_mode": chatMode,
|
||||
}
|
||||
if name := runtime.Str("name"); name != "" {
|
||||
body["name"] = name
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
---
|
||||
name: lark-im
|
||||
version: 1.0.0
|
||||
description: "飞书即时通讯:收发消息和管理群聊。发送和回复消息、搜索聊天记录、管理群聊成员、上传下载图片和文件(支持大文件分片下载)、管理表情回复。当用户需要发消息、查看或搜索聊天记录、下载聊天中的文件、查看群成员、管理标记数据时使用。"
|
||||
description: "飞书即时通讯:收发消息和管理群聊。发送和回复消息、搜索聊天记录、管理群聊成员、上传下载图片和文件(支持大文件分片下载)、管理表情回复。当用户需要发消息、查看或搜索聊天记录、下载聊天中的文件、查看群成员、搜索群、创建群聊或话题群、管理标记数据时使用。"
|
||||
metadata:
|
||||
requires:
|
||||
bins: ["lark-cli"]
|
||||
@@ -68,7 +68,7 @@ Shortcut 是对常用操作的高级封装(`lark-cli im +<verb> [flags]`)。
|
||||
|
||||
| Shortcut | 说明 |
|
||||
|----------|------|
|
||||
| [`+chat-create`](references/lark-im-chat-create.md) | Create a group chat; user/bot; creates private/public chats, invites users/bots, optionally sets bot manager |
|
||||
| [`+chat-create`](references/lark-im-chat-create.md) | Create a group chat or topic chat; user/bot; --chat-mode group|topic; private/public; invites users/bots; optionally sets bot manager |
|
||||
| [`+chat-messages-list`](references/lark-im-chat-messages-list.md) | List messages in a chat or P2P conversation; user/bot; accepts --chat-id or --user-id, resolves P2P chat_id, supports time range/sort/pagination |
|
||||
| [`+chat-search`](references/lark-im-chat-search.md) | Search visible group chats by `--query` keyword and/or `--member-ids`; user/bot; e.g. look up chat_id by group name; supports type filters, sorting, and pagination |
|
||||
| [`+chat-update`](references/lark-im-chat-update.md) | Update group chat name or description; user/bot; updates a chat's name or description |
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
> **Prerequisite:** Read [`../lark-shared/SKILL.md`](../../lark-shared/SKILL.md) first to understand authentication, global parameters, and safety rules.
|
||||
|
||||
Create a group chat. Supports both user identity (`--as user`) and bot identity (`--as bot`). You can specify the group name, description, members (users/bots), owner, and chat type (private/public).
|
||||
Create a group chat. Supports both user identity (`--as user`) and bot identity (`--as bot`). You can specify the group name, description, members (users/bots), owner, chat type (private/public), and group mode. Set `--chat-mode topic` to create a topic chat.
|
||||
|
||||
This skill maps to the shortcut: `lark-cli im +chat-create` (internally calls `POST /open-apis/im/v1/chats`).
|
||||
|
||||
@@ -18,6 +18,9 @@ lark-cli im +chat-create --name "My Group"
|
||||
# Create a public group (name is required and must be at least 2 characters)
|
||||
lark-cli im +chat-create --name "Public Group" --type public
|
||||
|
||||
# Create a topic chat
|
||||
lark-cli im +chat-create --name "Topic Group" --chat-mode topic
|
||||
|
||||
# Specify the group owner
|
||||
lark-cli im +chat-create --name "My Group" --owner ou_xxx
|
||||
|
||||
@@ -55,12 +58,15 @@ lark-cli im +chat-create --name "My Group" --dry-run
|
||||
| `--users <ids>` | No | Up to 50, format `ou_xxx` | Comma-separated user open_ids |
|
||||
| `--bots <ids>` | No | Up to 5, format `cli_xxx` | Comma-separated bot app IDs |
|
||||
| `--owner <open_id>` | No | Format `ou_xxx` | Owner open_id (defaults to the bot when using `--as bot`, or the authorized user when using `--as user`) |
|
||||
| `--type <type>` | No | `private` (default) or `public` | Group type |
|
||||
| `--type <type>` | No | `private` (default) or `public` | Group type. Default to `private`; pass `public` only when the user explicitly asks for a discoverable/public group. |
|
||||
| `--chat-mode <mode>` | No | `group` (default) or `topic` | Group mode; `topic` creates a topic chat (not the same as `group_message_type=thread`). When the user asks for a topic chat, pass `topic` explicitly — do not rely on the default. |
|
||||
| `--set-bot-manager` | No | - | Set the creating bot as a group manager (only effective with `--as bot`) |
|
||||
| `--format json` | No | - | Output as JSON |
|
||||
| `--as <identity>` | No | `bot` or `user` | Identity type |
|
||||
| `--dry-run` | No | - | Preview the request without executing it |
|
||||
|
||||
> **`--chat-mode topic` vs "normal group with topic-message mode"**: `--chat-mode topic` here creates a 话题群 — the entire group is a topic chat. This is different from "normal group (`chat_mode=group`) + topic-message mode (`group_message_type=thread`)". This CLI exposes only `chat_mode`; `group_message_type` is intentionally not surfaced.
|
||||
|
||||
## AI Usage Guidance
|
||||
|
||||
### When using `--as bot`
|
||||
|
||||
@@ -1,107 +0,0 @@
|
||||
---
|
||||
name: lark-slides-creator
|
||||
version: 1.0.0
|
||||
description: "飞书幻灯片创作工作流:从自然语言需求创建、重构、美化完整 PPT,覆盖规划、模板选择、视觉风格、素材规划和创建后验证。"
|
||||
metadata:
|
||||
requires:
|
||||
bins: ["lark-cli"]
|
||||
cliHelp: "lark-cli slides --help"
|
||||
---
|
||||
|
||||
# slides creator workflow
|
||||
|
||||
> 执行 XML/API 前必须读取 ../lark-slides/SKILL.md 和对应 reference。
|
||||
|
||||
This skill is the natural-language entry point for creating polished presentations. It owns planning, design, template, asset, and quality-validation workflows. It delegates all XML/API execution to [`../lark-slides/SKILL.md`](../lark-slides/SKILL.md).
|
||||
|
||||
## When To Use
|
||||
|
||||
Use this skill when the user asks for:
|
||||
|
||||
- A new complete presentation from a topic, notes, outline, document, meeting, or rough prompt.
|
||||
- Beautification, restructuring, major rewrite, or formal-report polishing.
|
||||
- Template selection or a deck based on a theme, scene, industry, or visual style.
|
||||
- Visual direction, palette, typography, layout system, or executive-ready presentation quality.
|
||||
- Asset planning, image search/download/upload planning, or deciding where visuals belong.
|
||||
- Creation-time and post-creation validation for content completeness and visual quality.
|
||||
|
||||
For a narrow raw XML/API operation, use `lark-slides` directly.
|
||||
|
||||
## Required Execution Dependency
|
||||
|
||||
Before running any `lark-cli slides` command or writing final XML:
|
||||
|
||||
1. Read [`../lark-slides/SKILL.md`](../lark-slides/SKILL.md).
|
||||
2. Read [`../lark-slides/references/xml-schema-quick-ref.md`](../lark-slides/references/xml-schema-quick-ref.md).
|
||||
3. Read the relevant execution reference, such as `lark-slides-create.md`, `lark-slides-media-upload.md`, `lark-slides-replace-slide.md`, or an `xml_presentation.*` API reference.
|
||||
|
||||
Use the execution skill's lint tool from here when XML is available:
|
||||
|
||||
```bash
|
||||
python3 skills/lark-slides-creator/scripts/layout_lint.py --input /tmp/presentation.xml
|
||||
```
|
||||
|
||||
## Workflow
|
||||
|
||||
1. Understand the deck goal.
|
||||
Capture topic, audience, page count, source material, language, formality, delivery setting, and any brand/style constraints. If the user gives enough information, proceed with explicit assumptions instead of blocking on questions.
|
||||
|
||||
2. Choose template or custom direction.
|
||||
If the request mentions templates, style, theme, or a common deck scenario, search templates first:
|
||||
|
||||
```bash
|
||||
python3 skills/lark-slides-creator/scripts/template_tool.py search --query "<用户需求原文>" --limit 3
|
||||
```
|
||||
|
||||
Offer 2-3 concise candidates when user choice matters. If one template is clearly best for a lightweight request, state the default and continue unless the user asked to choose.
|
||||
|
||||
3. Plan the deck.
|
||||
Build a page-by-page outline with title, role, key message, and intended layout for each slide. For formal reports, make the argument flow explicit: context, evidence, analysis, recommendation, next steps.
|
||||
|
||||
4. Design the visual system.
|
||||
Define palette, typography hierarchy, spacing, page rhythm, chart/table treatment, and recurring elements. Keep slides visual and low-density; do not produce document-like pages.
|
||||
|
||||
5. Plan assets.
|
||||
Decide which pages need screenshots, photos, diagrams, icons, or charts. External images must become local files first, then execution uses `+media-upload` or `@./path` placeholders as described in `lark-slides`.
|
||||
|
||||
6. Generate XML and execute through `lark-slides`.
|
||||
Use template summaries or extracted page slices when helpful, but rewrite all placeholder copy into the user's real content. For complex decks, prefer the two-step create flow from `lark-slides`.
|
||||
|
||||
7. Validate after creation.
|
||||
Read the created presentation XML with `xml_presentations get`, confirm page count and expected content, run lint when possible, then fix issues with `+replace-slide` or raw slide APIs.
|
||||
|
||||
## Template Workflow
|
||||
|
||||
Template assets live in this skill:
|
||||
|
||||
- [`references/template-catalog.md`](references/template-catalog.md)
|
||||
- [`references/template-index.json`](references/template-index.json)
|
||||
- [`assets/templates/`](assets/templates/)
|
||||
- [`scripts/template_tool.py`](scripts/template_tool.py)
|
||||
|
||||
Machine-first commands:
|
||||
|
||||
```bash
|
||||
python3 skills/lark-slides-creator/scripts/template_tool.py search --query "工作汇报" --tone light --limit 3
|
||||
python3 skills/lark-slides-creator/scripts/template_tool.py summarize --template office--work_report --label 内容
|
||||
python3 skills/lark-slides-creator/scripts/template_tool.py extract --template office--work_report --label 封面 --out /tmp/work-report-cover.xml
|
||||
```
|
||||
|
||||
Rules:
|
||||
|
||||
- Search using the user's original wording.
|
||||
- Show only 2-3 candidate templates unless the user asks for the full catalog.
|
||||
- Summarize a target page type before extracting XML.
|
||||
- Do not read entire template XML files by default.
|
||||
- Reuse theme, spacing, and structure; do not copy placeholder text.
|
||||
|
||||
## References
|
||||
|
||||
| Reference | Purpose |
|
||||
| --- | --- |
|
||||
| [planning-layer.md](references/planning-layer.md) | Deck planning and outline workflow. |
|
||||
| [visual-planning.md](references/visual-planning.md) | Visual style and layout design guidance. |
|
||||
| [asset-planning.md](references/asset-planning.md) | Asset selection, local-file, and upload planning. |
|
||||
| [template-catalog.md](references/template-catalog.md) | Template matching catalog. |
|
||||
| [slide-templates.md](references/slide-templates.md) | Copyable slide XML patterns for creation. |
|
||||
| [validation-checklist.md](references/validation-checklist.md) | Creation quality and post-create validation checklist. |
|
||||
@@ -1,21 +0,0 @@
|
||||
# Asset Planning
|
||||
|
||||
Use this when a deck needs screenshots, photos, diagrams, logos, icons, or chart data.
|
||||
|
||||
## Asset Plan
|
||||
|
||||
For each asset, record:
|
||||
|
||||
- Slide number and purpose.
|
||||
- Asset type: screenshot, product image, chart, diagram, logo, icon, photo.
|
||||
- Source: provided file, generated file, downloaded file, or chart from data.
|
||||
- Local path under the current working directory.
|
||||
- Intended placement and dimensions.
|
||||
|
||||
## Rules
|
||||
|
||||
- Slides XML cannot use HTTP(S) image URLs directly.
|
||||
- For a new deck using `+create --slides`, local image placeholders can use `src="@./path.png"`.
|
||||
- For existing decks or raw slide APIs, upload first with `slides +media-upload`, then use the returned `file_token`.
|
||||
- Keep source files inside the current working directory or a safe project subdirectory.
|
||||
- Check image dimensions and file size before upload; slides media upload limit is 20 MB.
|
||||
@@ -1,32 +0,0 @@
|
||||
# Slides Planning Layer
|
||||
|
||||
Use this before writing XML for a full presentation or a major rewrite.
|
||||
|
||||
## Inputs
|
||||
|
||||
- Goal: what decision, update, teaching outcome, or story the deck must support.
|
||||
- Audience: executives, customers, internal team, interview panel, students, or general readers.
|
||||
- Constraints: page count, language, source material, deadline, brand rules, required sections.
|
||||
- Success criteria: what the user should be able to inspect after creation.
|
||||
|
||||
## Output Outline
|
||||
|
||||
Use this compact structure:
|
||||
|
||||
```text
|
||||
Title: <deck title>
|
||||
Audience: <audience>
|
||||
Style: <visual direction or selected template>
|
||||
Slides:
|
||||
1. <slide title> - <message> - <layout intent>
|
||||
2. ...
|
||||
```
|
||||
|
||||
For formal reports, prefer this flow: cover, context, key findings, supporting evidence, implications, recommendations, next steps, closing.
|
||||
|
||||
## Rules
|
||||
|
||||
- Each slide gets one primary message.
|
||||
- Avoid document-like density; split overloaded pages.
|
||||
- Make charts or tables serve a stated point.
|
||||
- Confirm template choice when multiple good candidates would lead to materially different decks.
|
||||
@@ -1,25 +0,0 @@
|
||||
# Slides Creation Validation Checklist
|
||||
|
||||
Use this after generating XML and again after creating or editing the deck.
|
||||
|
||||
## Before API Execution
|
||||
|
||||
- XML is well-formed.
|
||||
- User text is escaped: `&`, `<`, and `>` are safe.
|
||||
- Each slide has one clear message.
|
||||
- Text boxes are sized for expected content.
|
||||
- Images use `@./local-path` only where `+create --slides` supports it; otherwise they use `file_token`.
|
||||
- Run execution-layer lint when XML is in a file:
|
||||
|
||||
```bash
|
||||
python3 skills/lark-slides-creator/scripts/layout_lint.py --input /tmp/presentation.xml
|
||||
```
|
||||
|
||||
## After Creation
|
||||
|
||||
- Record `xml_presentation_id`.
|
||||
- Read the full deck with `xml_presentations get`.
|
||||
- Confirm expected page count and page order.
|
||||
- Confirm key titles, body text, metrics, and image elements exist.
|
||||
- Check for blank pages, missing text, truncated shell arguments, unresolved `@` paths, and wrong image `src`.
|
||||
- Fix localized issues with `+replace-slide`; only delete/recreate a page when the whole structure is wrong.
|
||||
@@ -1,22 +0,0 @@
|
||||
# Visual Planning
|
||||
|
||||
Use this to define the deck's visual system before generating slide XML.
|
||||
|
||||
## Decisions
|
||||
|
||||
- Palette: background, primary accent, secondary accent, text, muted text, border.
|
||||
- Typography: title, section heading, body, caption, metric number.
|
||||
- Layout rhythm: margins, grid, recurring title position, footer treatment.
|
||||
- Components: cards, callouts, timelines, charts, tables, quote blocks, section dividers.
|
||||
|
||||
## Guidance
|
||||
|
||||
- Business reports should be quiet, readable, and scannable.
|
||||
- Product or technology decks can use stronger contrast, but keep hierarchy clear.
|
||||
- Use repeated structure across related slides.
|
||||
- Keep text inside predictable bounds; leave enough whitespace for rendering variance.
|
||||
- Do not rely on external image URLs in XML. Images must become `file_token` values through the execution workflow.
|
||||
|
||||
## XML Note
|
||||
|
||||
Before writing XML, read `../lark-slides/references/xml-schema-quick-ref.md`. Gradient fills must use `rgba()` stops with percentages.
|
||||
@@ -1,163 +1,525 @@
|
||||
---
|
||||
name: lark-slides
|
||||
version: 1.0.0
|
||||
description: "飞书幻灯片执行层:通过 Slides XML/API 读取、创建、删除、替换幻灯片页面,处理 URL/wiki token、媒体上传、XML schema、格式校验与排障。"
|
||||
description: "飞书幻灯片:创建和编辑幻灯片,接口通过 XML 协议通信。创建演示文稿、读取幻灯片内容、管理幻灯片页面(创建、删除、读取、局部替换)。当用户需要创建或编辑幻灯片、读取或修改单个页面时使用。"
|
||||
metadata:
|
||||
requires:
|
||||
bins: ["lark-cli"]
|
||||
cliHelp: "lark-cli slides --help"
|
||||
---
|
||||
|
||||
# slides execution layer
|
||||
# slides (v1)
|
||||
|
||||
> 创建完整 PPT、设计、美化、模板、素材、正式汇报场景请使用 lark-slides-creator。本 skill 只负责 XML/API 执行层。
|
||||
**CRITICAL — 开始前 MUST 先用 Read 工具读取 [`../lark-shared/SKILL.md`](../lark-shared/SKILL.md),其中包含认证、权限处理**
|
||||
|
||||
**CRITICAL — 开始前 MUST 先用 Read 工具读取 [`../lark-shared/SKILL.md`](../lark-shared/SKILL.md),其中包含认证、权限处理。**
|
||||
**CRITICAL — 生成任何 XML 之前,MUST 先用 Read 工具读取 [xml-schema-quick-ref.md](references/xml-schema-quick-ref.md),禁止凭记忆猜测 XML 结构。**
|
||||
|
||||
**CRITICAL — 生成或修改任何 XML 之前,MUST 先读取 [xml-schema-quick-ref.md](references/xml-schema-quick-ref.md)。不要凭记忆猜测 XML 结构。**
|
||||
**CRITICAL — 如果用户提到“模板”“套用模板”“参考某种主题/风格/版式”,或用户需求明显落在已有场景模板内(如工作汇报、产品介绍、商业计划书、培训、晋升汇报等),MUST 先用 [`scripts/template_tool.py`](scripts/template_tool.py) 的 `search` 做模板检索;默认给出 2-3 个最匹配模板候选供用户选择。锁定模板后用 `summarize` 获取主题和布局摘要;只有需要布局骨架时才用 `extract` 裁切目标页型 XML。不要直接读取完整模板 XML。**
|
||||
|
||||
**CRITICAL — `references/slides_xml_schema_definition.xml` 是 Slides XML 协议的唯一权威来源;Markdown reference 只是摘要。若两者或 `lark-cli schema` 输出不一致,以 schema 和 CLI 为准。**
|
||||
> [!NOTE]
|
||||
> `scripts/template_tool.py` 需要 Python 3。`references/template-index.json` 是脚本缓存/轻量路由索引,不是默认给 agent 阅读的文档;`assets/templates/*.xml` 是机器资源,只应通过脚本摘要或裁切,不要全文读取。
|
||||
|
||||
## Scope
|
||||
**CRITICAL — 使用模板生成或改写页面时,MUST 先 `summarize` 目标页型;只有需要具体布局骨架时才 `extract`。生成本地 XML 后,如可运行 Python,MUST 先用 [`scripts/layout_lint.py`](scripts/layout_lint.py) 检查 XML well-formed、重叠/越界/文本高度风险,再创建或追加页面。它不是完整 XSD schema 校验。**
|
||||
|
||||
Use this skill for low-level execution tasks:
|
||||
**编辑已有幻灯片页面**:优先用 [`+replace-slide`](references/lark-slides-replace-slide.md)(块级替换/插入,不动页序);选择 action 和完整读-改-写流程见 [`lark-slides-edit-workflows.md`](references/lark-slides-edit-workflows.md)。
|
||||
|
||||
- Create an empty presentation or add raw slide XML.
|
||||
- Read presentation or slide XML.
|
||||
- Delete slides.
|
||||
- Replace or insert existing slide blocks.
|
||||
- Upload local media and use returned `file_token` in XML.
|
||||
- Resolve `/slides/` URL tokens and `/wiki/` tokens.
|
||||
- Check XML format, schema rules, and common API errors.
|
||||
## 身份选择
|
||||
|
||||
Do not use this skill as the primary entry for planning, visual design, template selection, asset planning, or full-deck creation. Route those requests to `lark-slides-creator`, then return here only for XML/API execution.
|
||||
飞书幻灯片通常是用户自己的内容资源。**默认应优先显式使用 `--as user`(用户身份)执行 slides 相关操作**,始终显式指定身份。
|
||||
|
||||
## Identity
|
||||
|
||||
Slides are usually user-owned content. Default to explicit `--as user` for slides commands.
|
||||
- **`--as user`(推荐)**:以当前登录用户身份创建、读取、管理演示文稿。执行前先完成用户授权:
|
||||
|
||||
```bash
|
||||
lark-cli auth login --domain slides
|
||||
```
|
||||
|
||||
Use `--as bot` only when the user explicitly asks for app/bot identity or the workflow intentionally creates bot-owned resources. If access fails, first check that the command did not accidentally use the wrong identity.
|
||||
- **`--as bot`**:仅在用户明确要求以应用身份操作,或需要让 bot 持有/创建资源时使用。使用 bot 身份时,要额外确认 bot 是否真的有目标演示文稿的访问权限。
|
||||
|
||||
## URL And Wiki Tokens
|
||||
**执行规则**:
|
||||
|
||||
| URL | Token | Handling |
|
||||
| --- | --- | --- |
|
||||
| `/slides/<token>` | `xml_presentation_id` | Use the path token directly. |
|
||||
| `/wiki/<token>` | `wiki_token` | Resolve first with `wiki.spaces.get_node`; use `node.obj_token` only when `node.obj_type` is `slides`. |
|
||||
1. 创建、读取、增删 slide、按用户给出的链接继续编辑已有 PPT,默认都先用 `--as user`。
|
||||
2. 如果出现权限不足,先检查当前是否误用了 bot 身份;不要默认回退到 bot。
|
||||
3. 只有在用户明确要求"用应用身份 / bot 身份操作",或当前工作流就是 bot 创建资源后再做协作授权时,才切换到 `--as bot`。
|
||||
|
||||
## 快速开始
|
||||
|
||||
一条命令创建包含页面内容的 PPT(推荐):
|
||||
|
||||
```bash
|
||||
lark-cli wiki spaces get_node --as user --params '{"token":"wiki_token"}'
|
||||
lark-cli slides xml_presentations get --as user --params '{"xml_presentation_id":"obj_token"}'
|
||||
```
|
||||
|
||||
`+replace-slide` and `+media-upload` can parse slides/wiki URLs. Raw API commands still require the real `xml_presentation_id`.
|
||||
|
||||
## Shortcuts
|
||||
|
||||
| Shortcut | Reference | Purpose |
|
||||
| --- | --- | --- |
|
||||
| `slides +create` | [lark-slides-create.md](references/lark-slides-create.md) | Create a presentation; optionally add pages with `--slides`; supports local image placeholders in `+create --slides`. |
|
||||
| `slides +media-upload` | [lark-slides-media-upload.md](references/lark-slides-media-upload.md) | Upload a local image to a presentation and return a `file_token`. |
|
||||
| `slides +replace-slide` | [lark-slides-replace-slide.md](references/lark-slides-replace-slide.md) | Replace or insert blocks on an existing slide without changing page order. |
|
||||
|
||||
Prefer shortcuts when they cover the operation, especially `+replace-slide` for existing-slide edits.
|
||||
|
||||
## API Commands
|
||||
|
||||
Always inspect schema before raw API calls:
|
||||
|
||||
```bash
|
||||
lark-cli schema slides.<resource>.<method>
|
||||
lark-cli slides <resource> <method> --as user --params '{}' --data '{}'
|
||||
```
|
||||
|
||||
Core resources:
|
||||
|
||||
| Resource | Method | Purpose |
|
||||
| --- | --- | --- |
|
||||
| `xml_presentations` | `get` | Read full presentation XML and metadata. |
|
||||
| `xml_presentation.slide` | `create` | Add one slide XML page. |
|
||||
| `xml_presentation.slide` | `delete` | Delete a slide; a presentation must keep at least one page. |
|
||||
| `xml_presentation.slide` | `get` | Read one slide XML. |
|
||||
| `xml_presentation.slide` | `replace` | Low-level block replace/insert API; prefer `+replace-slide` unless you need raw control. |
|
||||
|
||||
## Creation Paths
|
||||
|
||||
For simple XML, `+create --slides` is concise:
|
||||
|
||||
```bash
|
||||
lark-cli slides +create --as user --title "Demo" --slides '[
|
||||
"<slide xmlns=\"http://www.larkoffice.com/sml/2.0\"><style><fill><fillColor color=\"rgb(248,250,252)\"/></fill></style><data><shape type=\"text\" topLeftX=\"80\" topLeftY=\"80\" width=\"800\" height=\"100\"><content textType=\"title\"><p>Title</p></content></shape></data></slide>"
|
||||
lark-cli slides +create --title "演示文稿标题" --slides '[
|
||||
"<slide xmlns=\"http://www.larkoffice.com/sml/2.0\"><style><fill><fillColor color=\"rgb(245,245,245)\"/></fill></style><data><shape type=\"text\" topLeftX=\"80\" topLeftY=\"80\" width=\"800\" height=\"100\"><content textType=\"title\"><p>页面标题</p></content></shape><shape type=\"text\" topLeftX=\"80\" topLeftY=\"200\" width=\"800\" height=\"200\"><content textType=\"body\"><p>正文内容</p><ul><li><p>要点一</p></li><li><p>要点二</p></li></ul></content></shape></data></slide>"
|
||||
]'
|
||||
```
|
||||
|
||||
For complex XML, long text, many special characters, Chinese paragraphs, images, or many pages, create an empty presentation first and add slides one by one. `+create --slides` is not atomic; if a later slide fails, earlier slides may already exist. Record `xml_presentation_id` and read the deck before continuing.
|
||||
也可以分两步(先创建空白 PPT,再逐页添加),详见 [+create 参考文档](references/lark-slides-create.md)。
|
||||
|
||||
> [!WARNING]
|
||||
> `--slides '[...]'` 适合简单页面批量创建,但并不等同于“10 页以内都安全”。如果 slide XML 含中文、大段文本、复杂布局、嵌套引号或较多特殊字符,shell 传参时可能出现转义或截断问题,导致内容丢失、页面空白或布局异常。遇到复杂页面时,优先改用“两步创建法”。
|
||||
|
||||
> [!IMPORTANT]
|
||||
> `slides +create --slides` 底层是“先创建空白 PPT,再逐页调用 `xml_presentation.slide.create`”。这不是原子操作;中途某一页失败时,前面已创建成功的页面会保留。skill 必须把这种“部分成功”风险提前告诉用户,并在失败后先记录 `xml_presentation_id`,回读确认当前状态,再决定是否在现有 PPT 上继续修复或追加。
|
||||
|
||||
> 以上是最小可用示例。更丰富的页面效果(渐变背景、卡片、图表、表格等),参考下方 Workflow 和 XML 模板。
|
||||
|
||||
## 执行前必做
|
||||
|
||||
> **重要**:`references/slides_xml_schema_definition.xml` 是此 skill 唯一正确的 XML 协议来源;其他 md 仅是对它和 CLI schema 的摘要。
|
||||
|
||||
### 必读(每次创建前)
|
||||
|
||||
| 文档 | 说明 |
|
||||
|------|------|
|
||||
| [xml-schema-quick-ref.md](references/xml-schema-quick-ref.md) | **XML 元素和属性速查,必读** |
|
||||
|
||||
### 选读(需要时查阅)
|
||||
|
||||
| 场景 | 文档 |
|
||||
|------|------|
|
||||
| 需要了解详细 XML 结构 | [xml-format-guide.md](references/xml-format-guide.md) |
|
||||
| 需要快速筛模板、做低成本路由 | [`scripts/template_tool.py search`](scripts/template_tool.py) |
|
||||
| 需要匹配 PPT 模板/主题风格 | [template-catalog.md](references/template-catalog.md) |
|
||||
| 需要按页型抽摘要或裁切 XML 片段 | [`scripts/template_tool.py`](scripts/template_tool.py) |
|
||||
| 需要做本地布局风险检查 | [`scripts/layout_lint.py`](scripts/layout_lint.py) |
|
||||
| 需要 CLI 调用示例 | [examples.md](references/examples.md) |
|
||||
| 需要参考真实 PPT 的 XML | [slides_demo.xml](references/slides_demo.xml) |
|
||||
| 需要用 table/chart 等复杂元素 | [slides_xml_schema_definition.xml](references/slides_xml_schema_definition.xml)(完整 Schema) |
|
||||
| 需要编辑已有 PPT 的单个页面 | [lark-slides-edit-workflows.md](references/lark-slides-edit-workflows.md) |
|
||||
| 需要了解某个命令的详细参数 | 对应命令的 reference 文档(见下方参考文档章节) |
|
||||
|
||||
## Workflow
|
||||
|
||||
> **这是演示文稿,不是文档。** 每页 slide 是独立的视觉画面,信息密度要低,排版要留白。
|
||||
|
||||
### 创建方式选择
|
||||
|
||||
| 场景 | 推荐方式 |
|
||||
|------|----------|
|
||||
| 简单 XML(1-3 页、结构简单、几乎无复杂中文和特殊字符) | `slides +create --slides '[...]'` 一步创建 |
|
||||
| 复杂 XML(多页、含中文、大段文本、复杂布局、嵌套引号、特殊字符较多) | **两步创建**:先 `slides +create` 创建空白 PPT,再用 `xml_presentation.slide create` 逐页添加 |
|
||||
| 已有 PPT 继续追加或插入页面 | 使用 `xml_presentation.slide create`,必要时配合 `before_slide_id` |
|
||||
|
||||
> [!WARNING]
|
||||
> `--slides '[...]'` 的风险点主要在 shell 参数传递,而不是单纯页数。即使只有 1 页,只要 XML 足够复杂,也建议使用两步创建法。
|
||||
|
||||
### 模板与脚本优先流程
|
||||
|
||||
```bash
|
||||
lark-cli slides +create --as user --title "Demo"
|
||||
# 1. 搜索候选:把用户原始需求整句放进 --query,不要只放手动提炼的短词
|
||||
python3 skills/lark-slides/scripts/template_tool.py search --query "<用户需求原文>" --limit 3
|
||||
|
||||
lark-cli slides xml_presentation.slide create --as user \
|
||||
--params '{"xml_presentation_id":"YOUR_ID"}' \
|
||||
--data "$(jq -n --arg content '<slide xmlns="http://www.larkoffice.com/sml/2.0"><data/></slide>' '{slide:{content:$content}}')"
|
||||
# 2. 锁定模板后先看页型摘要
|
||||
python3 skills/lark-slides/scripts/template_tool.py summarize --template <template-id> --label <封面|目录|分节|内容|结尾>
|
||||
|
||||
# 3. 只有需要复用布局骨架时才裁切 XML
|
||||
python3 skills/lark-slides/scripts/template_tool.py extract --template <template-id> --label <页型> --out /tmp/template-slice.xml
|
||||
|
||||
# 4. 生成待创建 XML 后先做布局风险检查
|
||||
python3 skills/lark-slides/scripts/layout_lint.py --input /tmp/presentation.xml
|
||||
```
|
||||
|
||||
To insert before an existing page, put `before_slide_id` in `--data`, not in `--params`.
|
||||
执行规则:
|
||||
|
||||
## Media Upload
|
||||
1. `search --query` 使用用户原始描述;如用户明确风格,再额外加 `--tone light|dark|colorful` 或 `--formality formal|casual|creative`。
|
||||
2. 候选展示只给 2-3 个,包含模板名、适用场景、风格/色调、推荐理由;不要把完整目录贴给用户。
|
||||
3. 锁定模板后,复用 `<theme>`、配色、页面流、布局骨架;所有占位文案都必须改写为用户真实内容。
|
||||
4. `layout_lint.py` 有 error 时先修 XML,不要提交创建;只有 warning 时,检查是否是可接受的装饰/背景误报。
|
||||
|
||||
Slides XML image `src` must be a Lark `file_token`; do not use external HTTP(S) URLs.
|
||||
```text
|
||||
Step 1: 需求澄清 & 读取知识
|
||||
- 澄清用户需求:主题、受众、页数、风格偏好
|
||||
- 如果需求明显落在已有模板场景内,主动提示用户“可以直接基于现成模板生成”,并给出 2-3 个最匹配模板候选(模板名 + 适用场景 + 风格/色调 + 简短推荐理由)
|
||||
- 默认不要把完整模板目录直接贴给用户;除非用户明确要求看更多,否则只展示 2-3 个候选
|
||||
- 候选优先选场景强相关模板;只有没有明显场景模板时,才用 `light_general.xml` / `dark_general.xml` 这类通用模板兜底
|
||||
- 如果用户没有明确风格,根据主题推荐(见下方风格判断表)
|
||||
- 如果用户要求“模板/主题/风格参考”,或主题属于常见模板场景:
|
||||
· 优先运行 `python3 skills/lark-slides/scripts/template_tool.py search --query "<用户需求原文>" --limit 3` 做低成本模板匹配
|
||||
· 需要人类可读说明时,再读 template-catalog.md 组织候选文案
|
||||
· 锁定模板后,优先运行 `template_tool.py summarize` 看 `<theme>` / 页型摘要;需要具体布局时,再用 `template_tool.py extract`
|
||||
· 复用模板的 theme、配色、页面流、布局骨架,不要照搬占位文案
|
||||
· `references/template-index.json` 只是脚本缓存/轻量路由索引,`assets/templates/*.xml` 是机器资源;除非用户明确要求审计原始模板,否则不要直接读取
|
||||
- 读取 XML Schema 参考:
|
||||
· xml-schema-quick-ref.md — 元素和属性速查
|
||||
· xml-format-guide.md — 详细结构与示例
|
||||
· slides_demo.xml — 真实 XML 示例
|
||||
|
||||
- New deck with `+create --slides`: `src="@./local.png"` is allowed and the shortcut uploads it.
|
||||
- Existing deck or raw `slide.create`: run `slides +media-upload` first, then write `src="<file_token>"`.
|
||||
- Existing slide edit: upload first, then use `+replace-slide` with `block_insert` or `block_replace`.
|
||||
Step 2: 生成大纲 → 用户确认 → 创建
|
||||
- 生成大纲前,先确认用户是否采用推荐模板;轻量任务且候选中有明显最佳匹配时,可在大纲里声明“默认基于 <template-id> 改写”并继续,但正式创建前必须给用户改选机会
|
||||
- 生成结构化大纲(每页标题 + 要点 + 布局描述),交给用户确认
|
||||
- 如果已选模板,大纲和页面布局要明确标注“基于哪个模板/哪些模板改写”
|
||||
- 如果用户明确不要模板,直接按自定义风格继续,不要重复推动模板选择
|
||||
- 先判断创建方式:
|
||||
· 简单 XML:可用 `slides +create --slides '[...]'` 一步创建
|
||||
· 复杂 XML:优先先 `slides +create` 创建空白 PPT,再用 `xml_presentation.slide.create` 逐页添加
|
||||
· 超过 10 页:默认使用两步创建,避免单次输入过长
|
||||
- 含本地图片:
|
||||
· 新建带图 PPT —— 在 slide XML 里写 <img src="@./pic.png" .../>,
|
||||
+create 会自动上传并替换为 file_token(详见 lark-slides-create.md)
|
||||
· 给已有 PPT 加带图新页 —— 先 `slides +media-upload --file ./pic.png --presentation $PID`
|
||||
拿到 file_token,再用它写进 slide XML 调 xml_presentation.slide.create
|
||||
· 给已有页加图 —— 两步:① `slides +media-upload` 拿 file_token
|
||||
② `slides +replace-slide --parts '[{"action":"block_insert","insertion":"<img src=\"<file_token>\" .../>"}]'`
|
||||
不动其他元素,不要再整页重建(完整示例见 lark-slides-edit-workflows.md 的 block_insert 章节)
|
||||
· 路径必须是 CWD 内的相对路径(如 ./pic.png 或 ./assets/x.png);
|
||||
绝对路径会被 CLI 拒绝,先 cd 到素材所在目录再执行
|
||||
- 每页 slide 需要完整的 XML:背景、文本、图形、配色
|
||||
- 复杂元素(table、chart)需参考 XSD 原文
|
||||
- 创建前必须做 XML 自检:
|
||||
· 检查特殊字符是否按 XML 规则转义:文本节点和属性值里的裸 `& -> &`;文本里的 `< -> <`、`> -> >`。例如 `Q&A -> Q&A`,URL 属性 `a=1&b=2 -> a=1&b=2`
|
||||
· 属性值里的双引号必须转义或改为外层安全包装,避免 shell 和 JSON 双重截断
|
||||
· 确认所有标签闭合,且 `<slide>` 直接子元素只包含 `<style>`、`<data>`、`<note>`
|
||||
· 如果内容里同时出现中文、大段文本、复杂布局、较多特殊字符,默认不要走 `--slides '[...]'`,直接改用两步创建法
|
||||
· 如果 XML 已落到本地文件且可运行 Python,先执行 `layout_lint.py --input <file>`;它会先检查 XML well-formed 再检查布局风险,但不等价于完整 XSD schema 校验;有 error 先修复再创建
|
||||
- 如果使用模板生成页面,先复用模板骨架再填内容,不要直接复制模板中的长段占位文本
|
||||
|
||||
Local paths must be safe paths under the current working directory. The upload limit is 20 MB.
|
||||
Step 3: 审查 & 交付
|
||||
- 创建完成后,必须用 xml_presentations.get 读取全文 XML 做创建后验证,确认:
|
||||
· 页数是否正确?
|
||||
· 每页 `<data>` 是否包含预期的 `<shape>` / `<img>` / 其他元素?
|
||||
· 文本内容是否完整,是否有被截断、丢失、空白区域?
|
||||
· 关键布局坐标和尺寸是否合理,是否出现明显重叠?
|
||||
· 配色是否统一?字号层级是否合理?
|
||||
- 如果本地有 Python 3,运行
|
||||
`python3 skills/lark-slides/scripts/layout_lint.py --input presentation.xml`
|
||||
做重叠、越界、页脚碰撞、文本高度风险检查;有 error 先修复再交付
|
||||
- 如果创建过程中失败:
|
||||
· 先保留并记录 `xml_presentation_id`,不要假设失败代表什么都没创建
|
||||
· 先判断是否已有部分页面写入,再决定是否在现有 PPT 上修复后继续追加
|
||||
· 优先排查当前失败页:先看该页 XML,再检查是否存在未转义 `&`、错误引号、标签未闭合、shell 传参截断
|
||||
- 局部问题 → 用 `+replace-slide` 块级修正;整页结构要改 → `slide.delete` 旧页 + `slide.create` 新页
|
||||
- 没问题 → 交付:告知用户演示文稿 ID 和访问方式
|
||||
```
|
||||
|
||||
## XML Rules
|
||||
### 创建后验证
|
||||
|
||||
- `<slide>` direct children are only `<style>`, `<data>`, and `<note>`.
|
||||
- Text belongs inside `<content><p>...</p></content>`.
|
||||
- Escape raw text before writing XML: `&` becomes `&`, text `<` becomes `<`, and text `>` becomes `>`.
|
||||
- Gradient fills require `rgba()` stops with percentages, for example `linear-gradient(135deg,rgba(15,23,42,1) 0%,rgba(56,97,140,1) 100%)`.
|
||||
- For `xml_presentation.slide.replace`, `block_replace` needs the target block id and text shapes need `<content/>`; `+replace-slide` injects the required wrapper details.
|
||||
创建成功不等于内容正确。创建完 PPT 后,**必须**读取全文 XML 校验结果:
|
||||
|
||||
## Validation
|
||||
```bash
|
||||
lark-cli slides xml_presentations get --as user \
|
||||
--params '{"xml_presentation_id":"YOUR_ID"}'
|
||||
```
|
||||
|
||||
This execution skill validates at the XML/API layer. Before execution, check XML well-formedness, escaping, request body shape, and `lark-cli schema` output. Visual layout quality checks belong to creator workflows, not this execution layer.
|
||||
重点检查:
|
||||
|
||||
## Troubleshooting
|
||||
- [ ] 页数是否与预期一致
|
||||
- [ ] 每页 `<data>` 中是否包含所有预期元素
|
||||
- [ ] 文本内容是否完整,没有被 shell 截断或转义损坏
|
||||
- [ ] 白底内容区、卡片区、图文区等关键布局是否实际生成
|
||||
- [ ] 坐标、宽高是否合理,是否出现堆叠或越界
|
||||
|
||||
| Symptom | Likely Cause | Next Action |
|
||||
| --- | --- | --- |
|
||||
| `400` XML or wrapper error | Bad XML or wrong `--data` shape | Check escaping, tag closure, and `lark-cli schema`. |
|
||||
| `403` permission denied | Wrong identity or missing scope | Confirm `--as user` vs `--as bot`; re-run auth for slides scope. |
|
||||
| `404` presentation/slide not found | Wrong token or unresolved wiki URL | Resolve wiki token or re-read current presentation. |
|
||||
| `1061002` media params error | Raw upload API used incorrectly | Use `slides +media-upload`; slides parent type is `slide_file`. |
|
||||
| `1061004` forbidden | Current identity cannot edit target deck | Use the owner identity or share the deck with the bot/user. |
|
||||
| `3350001` catch-all validation failure | XML not well-formed, bad replace wrapper, missing `<content/>`, or unescaped text | Run lint, inspect failed page XML, and prefer `+replace-slide` for block edits. |
|
||||
| `3350002` stale revision | `revision_id` is newer than current | Use `-1` or re-read the presentation and retry. |
|
||||
| Created deck has blank/missing pages | Shell/JSON argument truncation or escaping issue | Read back XML, then continue with two-step `slide.create`. |
|
||||
| Image does not show | `src` is URL or unresolved `@path` | Upload and replace with a `file_token`. |
|
||||
发现问题时:
|
||||
|
||||
## References
|
||||
1. 不要假设“创建成功就代表渲染正确”
|
||||
2. 先读取问题页的 XML,确认是生成问题还是传参损坏
|
||||
3. 删除问题页后重新添加;复杂页面优先改用两步创建法
|
||||
|
||||
| Reference | Purpose |
|
||||
| --- | --- |
|
||||
| [xml-schema-quick-ref.md](references/xml-schema-quick-ref.md) | Required XML element and attribute quick reference. |
|
||||
| [xml-format-guide.md](references/xml-format-guide.md) | Detailed XML structure and examples. |
|
||||
| [slides_xml_schema_definition.xml](references/slides_xml_schema_definition.xml) | Full XML schema definition. |
|
||||
| [lark-slides-create.md](references/lark-slides-create.md) | `+create` shortcut. |
|
||||
| [lark-slides-media-upload.md](references/lark-slides-media-upload.md) | `+media-upload` shortcut. |
|
||||
| [lark-slides-replace-slide.md](references/lark-slides-replace-slide.md) | `+replace-slide` shortcut. |
|
||||
| [lark-slides-edit-workflows.md](references/lark-slides-edit-workflows.md) | Existing-slide read/modify/write workflows. |
|
||||
| [lark-slides-xml-presentations-get.md](references/lark-slides-xml-presentations-get.md) | Raw presentation read API. |
|
||||
| [lark-slides-xml-presentation-slide-create.md](references/lark-slides-xml-presentation-slide-create.md) | Raw slide create API. |
|
||||
| [lark-slides-xml-presentation-slide-delete.md](references/lark-slides-xml-presentation-slide-delete.md) | Raw slide delete API. |
|
||||
| [lark-slides-xml-presentation-slide-get.md](references/lark-slides-xml-presentation-slide-get.md) | Raw slide get API. |
|
||||
| [lark-slides-xml-presentation-slide-replace.md](references/lark-slides-xml-presentation-slide-replace.md) | Raw slide replace API. |
|
||||
| [examples.md](references/examples.md) | CLI examples. |
|
||||
| [slides_demo.xml](references/slides_demo.xml) | Example presentation XML. |
|
||||
### 最小验收清单
|
||||
|
||||
创建完成后,默认按下面顺序验收,不要省略:
|
||||
|
||||
1. 记录 `xml_presentation_id`
|
||||
2. 确认返回的 `slides_added` 或实际页数是否符合预期
|
||||
3. 立即执行 `xml_presentations get`
|
||||
4. 检查标题、关键页面、关键文本是否存在
|
||||
5. 检查是否有明显空白页、内容缺失、页序错误
|
||||
6. 再决定是否向用户交付 URL 和后续编辑建议
|
||||
|
||||
推荐最小闭环:
|
||||
|
||||
```bash
|
||||
# 创建
|
||||
lark-cli slides +create --as user --title "Demo" --slides '[...]'
|
||||
|
||||
# 立即回读
|
||||
lark-cli slides xml_presentations get --as user \
|
||||
--params '{"xml_presentation_id":"YOUR_ID"}'
|
||||
```
|
||||
|
||||
## XML 自检与排障
|
||||
|
||||
在真正创建前,至少做下面 4 项检查:
|
||||
|
||||
- [ ] 特殊字符已转义:正文和标题里的 `&`、`<`、`>` 不能裸写;属性值里的裸 `&` 也必须写成 `&`
|
||||
- [ ] 属性引号安全:XML 属性、shell 引号、JSON 字符串包装之间没有互相打断
|
||||
- [ ] 结构合法:`<slide>` 下只放 `<style>`、`<data>`、`<note>`,文本都在 `<content>` 内
|
||||
- [ ] 路径正确:`<img src="@...">` 只在 `+create --slides` 的支持链路中使用
|
||||
|
||||
高频失败信号和处理顺序:
|
||||
|
||||
1. `invalid param` / 某一页创建失败
|
||||
2. 先检查失败页是否含未转义 `&` / `<` / `>`:`Q&A -> Q&A`,属性 URL `a=1&b=2 -> a=1&b=2`
|
||||
3. 再检查标签闭合、属性引号、`<content>` 结构
|
||||
4. 如果是 `--slides '[...]'`,怀疑 shell 截断时直接切两步创建法
|
||||
5. 创建后无论成功失败,都优先记录 `xml_presentation_id` 并回读确认是否已有部分页面写入
|
||||
|
||||
### jq 命令模板(编辑已有 PPT 时使用)
|
||||
|
||||
新建 PPT 推荐用 `+create --slides`。以下 jq 模板适用于向已有演示文稿追加页面的场景,可以避免手动转义双引号:
|
||||
|
||||
```bash
|
||||
# 追加到末尾
|
||||
lark-cli slides xml_presentation.slide create \
|
||||
--as user \
|
||||
--params '{"xml_presentation_id":"YOUR_ID"}' \
|
||||
--data "$(jq -n --arg content '<slide xmlns="http://www.larkoffice.com/sml/2.0">
|
||||
<style><fill><fillColor color="BACKGROUND_COLOR"/></fill></style>
|
||||
<data>
|
||||
在这里放置 shape、line、table、chart 等元素
|
||||
</data>
|
||||
</slide>' '{slide:{content:$content}}')"
|
||||
|
||||
# 插到指定页之前:before_slide_id 必须在 --data body 里,与 slide 同级
|
||||
# ⚠️ 不要把 before_slide_id 写进 --params —— CLI 会当未知 query 参数静默下发,服务端忽略,新页跑到末尾
|
||||
lark-cli slides xml_presentation.slide create \
|
||||
--as user \
|
||||
--params '{"xml_presentation_id":"YOUR_ID"}' \
|
||||
--data "$(jq -n --arg content '<slide ...>...</slide>' --arg before 'TARGET_SLIDE_ID' \
|
||||
'{slide:{content:$content}, before_slide_id:$before}')"
|
||||
```
|
||||
|
||||
### 风格快速判断表
|
||||
|
||||
> **注意**:渐变色必须使用 `rgba()` 格式并带百分比停靠点,如 `linear-gradient(135deg,rgba(15,23,42,1) 0%,rgba(56,97,140,1) 100%)`。使用 `rgb()` 或省略停靠点会导致服务端回退为白色。
|
||||
|
||||
| 场景/主题 | 推荐风格 | 背景 | 主色 | 文字色 |
|
||||
|----------|---------|------|------|-------|
|
||||
| 科技/AI/产品 | 深色科技风 | 深蓝渐变 `linear-gradient(135deg,rgba(15,23,42,1) 0%,rgba(56,97,140,1) 100%)` | 蓝色系 `rgb(59,130,246)` | 白色 |
|
||||
| 商务汇报/季度总结 | 浅色商务风 | 浅灰 `rgb(248,250,252)` | 深蓝 `rgb(30,60,114)` | 深灰 `rgb(30,41,59)` |
|
||||
| 教育/培训 | 清新明亮风 | 白色 `rgb(255,255,255)` | 绿色系 `rgb(34,197,94)` | 深灰 `rgb(51,65,85)` |
|
||||
| 创意/设计 | 渐变活力风 | 紫粉渐变 `linear-gradient(135deg,rgba(88,28,135,1) 0%,rgba(190,24,93,1) 100%)` | 粉紫色系 | 白色 |
|
||||
| 周报/日常汇报 | 简约专业风 | 浅灰 `rgb(248,250,252)` + 顶部彩色渐变条 | 蓝色 `rgb(59,130,246)` | 深色 `rgb(15,23,42)` |
|
||||
| 用户未指定 | 默认简约专业风 | 同上 | 同上 | 同上 |
|
||||
|
||||
### 页面布局建议
|
||||
|
||||
| 页面类型 | 布局要点 |
|
||||
|---------|---------|
|
||||
| 封面页 | 居中大标题 + 副标题 + 底部信息,背景用渐变或深色 |
|
||||
| 数据概览页 | 指标卡片横排(rect 背景 + 大号数字 + 小号说明),下方列表或图表 |
|
||||
| 内容页 | 左侧竖线装饰 + 标题,下方分栏或列表 |
|
||||
| 对比/表格页 | table 元素或并列卡片,表头深色背景白字 |
|
||||
| 图表页 | chart 元素(column/line/pie),配合文字说明 |
|
||||
| 结尾页 | 居中感谢语 + 装饰线,风格与封面呼应 |
|
||||
|
||||
### 大纲模板
|
||||
|
||||
生成大纲时使用以下格式,交给用户确认:
|
||||
|
||||
```text
|
||||
[PPT 标题] — [定位描述],面向 [目标受众]
|
||||
|
||||
模板:[未使用模板 / <category>/<template>.xml(推荐原因)]
|
||||
|
||||
页面结构(N 页):
|
||||
1. 封面页:[标题文案]
|
||||
2. [页面主题]:[要点1]、[要点2]、[要点3]
|
||||
3. [页面主题]:[要点描述]
|
||||
...
|
||||
N. 结尾页:[结尾文案]
|
||||
|
||||
风格:[配色方案],[排版风格]
|
||||
```
|
||||
|
||||
### 常用 Slide XML 模板
|
||||
|
||||
可直接复制使用的模板(封面页、内容页、数据卡片页、结尾页):[slide-templates.md](references/slide-templates.md)
|
||||
|
||||
---
|
||||
|
||||
## 核心概念
|
||||
|
||||
### URL 格式与 Token
|
||||
|
||||
| URL 格式 | 示例 | Token 类型 | 处理方式 |
|
||||
|----------|------|-----------|----------|
|
||||
| `/slides/` | `https://example.larkoffice.com/slides/xxxxxxxxxxxxx` | `xml_presentation_id` | URL 路径中的 token 直接作为 `xml_presentation_id` 使用 |
|
||||
| `/wiki/` | `https://example.larkoffice.com/wiki/wikcnxxxxxxxxx` | `wiki_token` | ⚠️ **不能直接使用**,需要先查询获取真实的 `obj_token` |
|
||||
|
||||
> `+replace-slide` 和 `+media-upload` shortcut 会自动解析以上两种 URL;直接调用原生 API 时仍需手动解析 wiki 链接。
|
||||
|
||||
### Wiki 链接特殊处理(关键!)
|
||||
|
||||
知识库链接(`/wiki/TOKEN`)背后可能是云文档、电子表格、幻灯片等不同类型的文档。**不能直接假设 URL 中的 token 就是 `xml_presentation_id`**,必须先查询实际类型和真实 token。
|
||||
|
||||
#### 处理流程
|
||||
|
||||
1. **使用 `wiki.spaces.get_node` 查询节点信息**
|
||||
```bash
|
||||
lark-cli wiki spaces get_node --as user --params '{"token":"wiki_token"}'
|
||||
```
|
||||
|
||||
2. **从返回结果中提取关键信息**
|
||||
- `node.obj_type`:文档类型,幻灯片对应 `slides`
|
||||
- `node.obj_token`:**真实的演示文稿 token**(用于后续操作)
|
||||
- `node.title`:文档标题
|
||||
|
||||
3. **确认 `obj_type` 为 `slides` 后,使用 `obj_token` 作为 `xml_presentation_id`**
|
||||
|
||||
#### 查询示例
|
||||
|
||||
```bash
|
||||
# 查询 wiki 节点
|
||||
lark-cli wiki spaces get_node --as user --params '{"token":"wikcnxxxxxxxxx"}'
|
||||
```
|
||||
|
||||
返回结果示例:
|
||||
```json
|
||||
{
|
||||
"node": {
|
||||
"obj_type": "slides",
|
||||
"obj_token": "xxxxxxxxxxxx",
|
||||
"title": "2026 产品年度总结",
|
||||
"node_type": "origin",
|
||||
"space_id": "1234567890"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
```bash
|
||||
# 用 obj_token 读取幻灯片内容
|
||||
lark-cli slides xml_presentations get --as user --params '{"xml_presentation_id":"xxxxxxxxxxxx"}'
|
||||
```
|
||||
|
||||
### 资源关系
|
||||
|
||||
```text
|
||||
Wiki Space (知识空间)
|
||||
└── Wiki Node (知识库节点, obj_type: slides)
|
||||
└── obj_token → xml_presentation_id
|
||||
|
||||
Slides (演示文稿)
|
||||
├── xml_presentation_id (演示文稿唯一标识)
|
||||
├── revision_id (版本号)
|
||||
└── Slide (幻灯片页面)
|
||||
└── slide_id (页面唯一标识)
|
||||
```
|
||||
|
||||
## Shortcuts(推荐优先使用)
|
||||
|
||||
Shortcut 是对常用操作的高级封装(`lark-cli slides +<verb> [flags]`)。有 Shortcut 的操作优先使用。
|
||||
|
||||
| Shortcut | 说明 |
|
||||
|----------|------|
|
||||
| [`+create`](references/lark-slides-create.md) | 创建 PPT(可选 `--slides` 一步添加页面,支持 `<img src="@./local.png">` 占位符自动上传),bot 模式自动授权 |
|
||||
| [`+media-upload`](references/lark-slides-media-upload.md) | 上传本地图片到指定演示文稿,返回 `file_token`(用作 `<img src="...">`),最大 20 MB |
|
||||
| [`+replace-slide`](references/lark-slides-replace-slide.md) | 对已有幻灯片页面进行块级替换/插入(`block_replace` / `block_insert`),自动注入 id 和 `<content/>`,不改变页序 |
|
||||
|
||||
## API Resources
|
||||
|
||||
```bash
|
||||
lark-cli schema slides.<resource>.<method> # 调用 API 前必须先查看参数结构
|
||||
lark-cli slides <resource> <method> [flags] # 调用 API
|
||||
```
|
||||
|
||||
> **重要**:使用原生 API 时,必须先运行 `schema` 查看 `--data` / `--params` 参数结构,不要猜测字段格式。
|
||||
|
||||
### xml_presentations
|
||||
|
||||
- `get` — 读取演示文稿全文信息,XML 格式返回
|
||||
|
||||
### xml_presentation.slide
|
||||
|
||||
- `create` — 在指定 XML 演示文稿下创建页面
|
||||
- `delete` — 在指定 XML 演示文稿下删除页面
|
||||
- `get` — 获取指定 XML 演示文稿的单个页面 XML 内容
|
||||
- `replace` — 对指定 XML 演示文稿页面进行元素级别的局部替换
|
||||
|
||||
## 核心规则
|
||||
|
||||
1. **先定模板/风格并出大纲再动手**:如果需求可匹配模板,先给用户 2-3 个模板候选;模板或自定义风格确定后,再生成大纲交给用户确认,避免返工
|
||||
2. **创建流程**:简单短 XML(1-3 页、结构简单、特殊字符少)可用 `slides +create --slides '[...]'` 一步创建;复杂内容、含图片/中文大段文本/嵌套引号/较多特殊字符,或超过 10 页时,默认先 `slides +create` 创建空白 PPT,再用 `xml_presentation.slide.create` 逐页添加
|
||||
3. **`<slide>` 直接子元素只有 `<style>`、`<data>`、`<note>`**:文本和图形必须放在 `<data>` 内
|
||||
4. **文本通过 `<content>` 表达**:必须用 `<content><p>...</p></content>`,不能把文字直接写在 shape 内
|
||||
5. **保存关键 ID**:后续操作需要 `xml_presentation_id`、`slide_id`、`revision_id`
|
||||
6. **删除谨慎**:删除操作不可逆,且至少保留一页幻灯片
|
||||
7. **编辑已有页面优先块级替换**:修改单个 shape/img 用 `+replace-slide`(`block_replace` / `block_insert`),不要整页重建;只有需要替换整页结构时才用 `slide.delete` + `slide.create`
|
||||
8. **`<img src>` 只能用上传到飞书 drive 的 `file_token`,禁止使用 http(s) 外链 URL**:飞书 slides 渲染端不会代理外链图片,外链 src 在 PPT 里通常不显示或显示破图。流程必须是「先把图存到本地 → 用 `slides +media-upload` 上传或 `+create --slides` 的 `@./path` 占位符自动上传 → 拿 `file_token` 写进 `<img src>`」。如果用户给了网图链接,先 `curl`/下载到 CWD 内再走上传流程,不要直接把外链 URL 塞进 `src`。**图片最大 20 MB**(slides upload API 不支持分片上传)。
|
||||
|
||||
## 权限表
|
||||
|
||||
| 方法 | 所需 scope |
|
||||
|------|-----------|
|
||||
| `slides +create` | `slides:presentation:create`, `slides:presentation:write_only`(含 `@` 占位符时还需 `docs:document.media:upload`) |
|
||||
| `slides +media-upload` | `docs:document.media:upload`(wiki URL 解析还需 `wiki:node:read`) |
|
||||
| `slides +replace-slide` | `slides:presentation:update`(wiki URL 解析还需 `wiki:node:read`) |
|
||||
| `xml_presentations.get` | `slides:presentation:read` |
|
||||
| `xml_presentation.slide.create` | `slides:presentation:update` 或 `slides:presentation:write_only` |
|
||||
| `xml_presentation.slide.delete` | `slides:presentation:update` 或 `slides:presentation:write_only` |
|
||||
| `xml_presentation.slide.get` | `slides:presentation:read` |
|
||||
| `xml_presentation.slide.replace` | `slides:presentation:update` |
|
||||
|
||||
## 常见错误速查
|
||||
|
||||
| 错误码 | 含义 | 解决方案 |
|
||||
|--------|------|----------|
|
||||
| 400 | XML 格式错误 | 检查 XML 语法,确保标签闭合 |
|
||||
| 400 | 请求包装错误 | 检查 `--data` 是否按 schema 传入 `xml_presentation.content` 或 `slide.content` |
|
||||
| 创建成功但页面空白/内容缺失/布局错乱 | 常见于 `--slides '[...]'` 的 shell 转义或长参数传递问题 | 改用两步创建:先 `slides +create`,再用 `jq -n` 包装 `xml_presentation.slide.create` 逐页添加,并在创建后立即读取 XML 验证 |
|
||||
| 404 | 演示文稿不存在 | 检查 `xml_presentation_id` 是否正确 |
|
||||
| 404 | 幻灯片不存在 | 检查 `slide_id` 是否正确 |
|
||||
| 403 | 权限不足 | 检查是否拥有对应的 scope |
|
||||
| 400 | 无法删除唯一幻灯片 | 演示文稿至少保留一页幻灯片 |
|
||||
| 1061002 | params error(媒体上传时) | 用 `slides +media-upload`,不要手拼原生 `medias/upload_all`;slides 唯一可用 `parent_type` 是 `slide_file` |
|
||||
| 1061004 | forbidden:当前身份对演示文稿无编辑权限 | 确认 user/bot 对目标 PPT 有编辑权限;bot 常见于 PPT 非该 bot 创建,需先授权或用 `+create --as bot` 新建 |
|
||||
| 3350001 | XML 非 well-formed、XML 结构不符合服务端要求,或 `xml_presentation.slide.replace` 失败(catch-all) | 优先检查未转义 `&` / `<` / `>`:`Q&A -> Q&A`,属性 URL `a=1&b=2 -> a=1&b=2`;运行 `layout_lint.py --input <file>` 定位行列和上下文;再检查 replace 场景的 `block_id` / `<content/>` / 坐标 |
|
||||
| 3350002 | `revision_id` 大于当前版本 | 用 `-1` 取当前版本,或重新读 `xml_presentations.get` 取最新 `revision_id` |
|
||||
| validation: unsafe file path | `--file` 给了绝对路径或上层路径 | `--file` 必须是 CWD 内相对路径;先 `cd` 到素材目录再执行 |
|
||||
|
||||
## 创建前自查
|
||||
|
||||
逐页生成 XML 前,快速检查:
|
||||
|
||||
- [ ] 每页背景色/渐变是否设置?风格是否与整体一致?
|
||||
- [ ] 标题用大字号(28-48),正文用小字号(13-16),层级分明?
|
||||
- [ ] 同类元素配色一致?(如所有指标卡片同色系、所有正文同色)
|
||||
- [ ] 装饰元素(分割线、色块、竖线)颜色是否与主色协调?
|
||||
- [ ] 文本框尺寸是否足够容纳内容?(宽度 × 高度)
|
||||
- [ ] shape 的 `type` 是否正确?(文本框用 `text`,装饰用 `rect`)
|
||||
- [ ] XML 标签是否全部正确闭合?特殊字符(`&`、`<`、`>`)是否转义?
|
||||
|
||||
## 症状 → 修复表
|
||||
|
||||
| 看到的问题 | 改什么 |
|
||||
|-----------|--------|
|
||||
| 文字被截断/看不全 | 增大 shape 的 `width` 或 `height` |
|
||||
| 元素重叠 | 调整 `topLeftX`/`topLeftY`,拉开间距 |
|
||||
| 页面大面积空白 | 缩小元素间距,或增加内容填充 |
|
||||
| 文字和背景色太接近 | 深色背景用浅色文字,浅色背景用深色文字 |
|
||||
| 表格列宽不合理 | 调整 `colgroup` 中 `col` 的 `width` 值 |
|
||||
| 图表没有显示 | 检查 `chartPlotArea` 和 `chartData` 是否都包含,`dim1`/`dim2` 数据数量是否匹配 |
|
||||
| 图片被裁掉一部分 | `<img>` 的 `width`/`height` 是裁剪后尺寸,比例和原图不一致时会自动裁剪;要整图显示就让 `width:height` 对齐原图比例 |
|
||||
| 只想改某页的单个元素(文字/图片/形状) | 用 `+replace-slide` 块级替换,不要整页重建 |
|
||||
| 想给已有页加一张图(不动原有元素) | ① `+media-upload` 拿 `file_token` ② `+replace-slide` 用 `block_insert` 插入 `<img src="<file_token>" .../>`;不要再用 "整页 create + delete" 的老流程 |
|
||||
| 新插入的 `<img>` 挡住/重叠原有元素 | `slide.get` 读原页,对照已有块的 `topLeftX/Y/width/height` 挑空白位置;空间不够就在同一批 `--parts` 里先 `block_replace` 缩小/挪动现有块再 `block_insert` 图片 |
|
||||
| 渐变背景变成白色 | 渐变必须用 `rgba()` 格式 + 百分比停靠点,如 `linear-gradient(135deg,rgba(30,60,114,1) 0%,rgba(59,130,246,1) 100%)`;用 `rgb()` 或省略停靠点会被回退为白色 |
|
||||
| 渐变方向不对 | 调整 `linear-gradient` 的角度(`90deg` 水平、`180deg` 垂直、`135deg` 对角线) |
|
||||
| 整体风格不统一 | 封面页和结尾页用同一背景,内容页保持一致的配色和字号体系 |
|
||||
| API 返回 400 | 检查 XML 语法:标签闭合、属性引号、特殊字符转义 |
|
||||
| API 返回 3350001 | `block_replace` 根元素缺 `id=<block_id>` 或 `<shape>` 缺 `<content/>`,详见 replace-slide 文档 |
|
||||
| 图片不显示 / `<img src>` 仍是 `@path` | `@` 占位符**只在 `+create --slides` 中替换**;直接调 `xml_presentation.slide.create` 必须先用 `+media-upload` 拿 `file_token` 写进 src |
|
||||
| 上传图片报 1061002 params error | `parent_type` 必须是 `slide_file`(slides 唯一接受值);不要手拼,用 `slides +media-upload` |
|
||||
|
||||
## 参考文档
|
||||
|
||||
| 文档 | 说明 |
|
||||
|------|------|
|
||||
| [lark-slides-create.md](references/lark-slides-create.md) | **+create Shortcut:创建 PPT(支持 `--slides` 一步添加页面,含 `@` 占位符自动上传图片)** |
|
||||
| [lark-slides-media-upload.md](references/lark-slides-media-upload.md) | **+media-upload Shortcut:上传本地图片,返回 `file_token`** |
|
||||
| [lark-slides-replace-slide.md](references/lark-slides-replace-slide.md) | **+replace-slide Shortcut:块级替换/插入,含合法根元素速查与 3350001 排错** |
|
||||
| [lark-slides-edit-workflows.md](references/lark-slides-edit-workflows.md) | 编辑已有页面的读-改-写流程与 action 决策树 |
|
||||
| [template-index.json](references/template-index.json) | **脚本缓存/轻量路由索引:由 `template_tool.py search` 使用,不是默认阅读入口** |
|
||||
| [template-catalog.md](references/template-catalog.md) | **按场景/色调匹配现成 PPT 模板,并定位到页型范围** |
|
||||
| [`scripts/template_tool.py`](scripts/template_tool.py) | **可选 Python 辅助脚本:`search` / `summarize` / `extract`,支持 `--layout-tag` 与 `extract --with-summary`** |
|
||||
| [`scripts/layout_lint.py`](scripts/layout_lint.py) | **本地预检脚本:先检查 XML well-formed,再检测重叠、越界、页脚碰撞、文本高度风险;不是完整 XSD schema 校验** |
|
||||
| [xml-schema-quick-ref.md](references/xml-schema-quick-ref.md) | **XML Schema 精简速查(必读)** |
|
||||
| [slide-templates.md](references/slide-templates.md) | 可复制的 Slide XML 模板 |
|
||||
| [xml-format-guide.md](references/xml-format-guide.md) | XML 详细结构与示例 |
|
||||
| [examples.md](references/examples.md) | CLI 调用示例 |
|
||||
| [slides_demo.xml](references/slides_demo.xml) | 真实 PPT 的完整 XML |
|
||||
| [slides_xml_schema_definition.xml](references/slides_xml_schema_definition.xml) | **完整 Schema 定义**(唯一协议依据) |
|
||||
| [lark-slides-xml-presentations-get.md](references/lark-slides-xml-presentations-get.md) | 读取 PPT 命令详情 |
|
||||
| [lark-slides-xml-presentation-slide-create.md](references/lark-slides-xml-presentation-slide-create.md) | 添加幻灯片命令详情 |
|
||||
| [lark-slides-xml-presentation-slide-delete.md](references/lark-slides-xml-presentation-slide-delete.md) | 删除幻灯片命令详情 |
|
||||
| [lark-slides-xml-presentation-slide-get.md](references/lark-slides-xml-presentation-slide-get.md) | 读取单个幻灯片命令详情 |
|
||||
| [lark-slides-xml-presentation-slide-replace.md](references/lark-slides-xml-presentation-slide-replace.md) | 原生 slide.replace API 命令详情 |
|
||||
|
||||
> **注意**:如果 md 内容与 `slides_xml_schema_definition.xml` 或 `lark-cli schema slides.<resource>.<method>` 输出不一致,以后两者为准。
|
||||
|
||||
@@ -178,7 +178,7 @@ lark-cli slides xml_presentation.slide create --as user \
|
||||
| 400 | XML 格式错误 | 检查 `slide.content` 是否是完整 `<slide>` 元素 |
|
||||
| 400 | 请求体结构错误 | 检查是否按 `slide.content` 和 `before_slide_id` 包装 |
|
||||
| 403 | 权限不足 | 检查是否拥有 `slides:presentation:update` 或 `slides:presentation:write_only` scope |
|
||||
| 3350001 | XML 非 well-formed 或服务端参数校验失败 | 优先检查未转义字符:文本 `Q&A -> Q&A`,文本 `<` / `>` 写成 `<` / `>`,属性 URL `a=1&b=2 -> a=1&b=2`;再检查标签闭合、命名空间、`<content>` 结构和请求体包装 |
|
||||
| 3350001 | XML 非 well-formed 或服务端参数校验失败 | 优先检查未转义字符:文本 `Q&A -> Q&A`,文本 `<` / `>` 写成 `<` / `>`,属性 URL `a=1&b=2 -> a=1&b=2`;创建前运行 `python3 skills/lark-slides/scripts/layout_lint.py --input <file>` 获取行列和上下文 |
|
||||
|
||||
## 注意事项
|
||||
|
||||
@@ -188,7 +188,7 @@ lark-cli slides xml_presentation.slide create --as user \
|
||||
4. **fill / border 写法**: 颜色填充使用 `<fill><fillColor color="..."/></fill>`,边框常用 `<border color="..." width="2"/>`
|
||||
5. **插入位置**: 通过 `before_slide_id` 指定插入目标,而不是用 `position`
|
||||
6. **JSON 转义**: 如果直接内联 XML,需要正确转义双引号
|
||||
7. **执行层预检**: 检查 XML well-formed、特殊字符转义、`slide.content` 包装和 `before_slide_id` 位置;布局质量检查不属于本执行层
|
||||
7. **本地预检**: 创建前运行 `layout_lint.py --input <file>`;它检查 XML well-formed 和布局风险,不等价于完整 XSD schema 校验
|
||||
8. **建议**: 先使用 `xml_presentations.get` 获取现有结构,再添加新页面
|
||||
|
||||
## 批量添加建议
|
||||
|
||||
@@ -10,26 +10,26 @@
|
||||
|
||||
## 使用方法
|
||||
|
||||
1. 先运行 `python3 skills/lark-slides-creator/scripts/template_tool.py search --query "<主题>" --limit 3`,根据用户描述的 **场景、风格、色调** 做初筛
|
||||
1. 先运行 `python3 skills/lark-slides/scripts/template_tool.py search --query "<主题>" --limit 3`,根据用户描述的 **场景、风格、色调** 做初筛
|
||||
2. 整理出 **2-3 个**最匹配的用户可选模板候选;优先选场景强相关模板,没有明显场景模板时再用标 ⭐ 的通用模板兜底
|
||||
3. 用户选定后,再锁定 **1-2 个**最匹配的模板作为实际参考
|
||||
4. 先看模板下方的 **页型索引**,锁定你真正需要的页型:封面 / 目录 / 分节 / 内容 / 结尾
|
||||
5. 优先运行 `template_tool.py summarize` 查看 `<theme>` / 页型摘要;只有需要具体布局骨架时,再运行 `template_tool.py extract`
|
||||
6. 从模板中提取并复用:`<theme>` 配色、页面流、shape 排列布局、装饰元素风格
|
||||
7. 将用户的实际内容填充到模板的结构框架中,**不要照搬模板的占位文字**
|
||||
8. 创建前运行 `python3 skills/lark-slides-creator/scripts/layout_lint.py --input <file>`;它检查 XML well-formed 和布局风险,不等价于完整 XSD schema 校验
|
||||
8. 创建前运行 `layout_lint.py --input <file>`;它检查 XML well-formed 和布局风险,不等价于完整 XSD schema 校验
|
||||
|
||||
### 脚本快捷命令
|
||||
|
||||
```bash
|
||||
# 先找候选模板
|
||||
python3 skills/lark-slides-creator/scripts/template_tool.py search --query "工作汇报" --tone light --limit 3
|
||||
python3 skills/lark-slides/scripts/template_tool.py search --query "工作汇报" --tone light --limit 3
|
||||
|
||||
# 看指定页型的紧凑摘要
|
||||
python3 skills/lark-slides-creator/scripts/template_tool.py summarize --template office--work_report --label 内容
|
||||
python3 skills/lark-slides/scripts/template_tool.py summarize --template office--work_report --label 内容
|
||||
|
||||
# 只裁切目标页型,避免把整份 XML 拉进上下文
|
||||
python3 skills/lark-slides-creator/scripts/template_tool.py extract --template office--work_report --label 封面 --out /tmp/work-report-cover.xml
|
||||
python3 skills/lark-slides/scripts/template_tool.py extract --template office--work_report --label 封面 --out /tmp/work-report-cover.xml
|
||||
```
|
||||
|
||||
如果脚本路径不可用,按这个顺序手动降级:
|
||||
Reference in New Issue
Block a user