Compare commits

..

1 Commits

Author SHA1 Message Date
zhangjun.1
58466a3d94 feat: replace summary and todo 2026-06-03 10:52:42 +08:00
16 changed files with 675 additions and 633 deletions

View File

@@ -0,0 +1,73 @@
// Copyright (c) 2026 Lark Technologies Pte. Ltd.
// SPDX-License-Identifier: MIT
package minutes
import (
"context"
"fmt"
"net/http"
"strings"
"github.com/larksuite/cli/internal/output"
"github.com/larksuite/cli/internal/validate"
"github.com/larksuite/cli/shortcuts/common"
)
const minutesSummaryMarkdownTip = "Summary accepts any text; unsupported Markdown is saved but may display as literal raw text in Minutes. For best rendering, prefer plain text, line breaks, headings (#, ##, ###), bold (**text**), and lists (-, *, or 1.)."
// MinutesSummary replaces the AI summary of a minute.
var MinutesSummary = common.Shortcut{
Service: "minutes",
Command: "+summary",
Description: "Replace the AI summary of a minute",
Risk: "write",
Scopes: []string{"minutes:minutes:update"},
AuthTypes: []string{"user"},
HasFormat: true,
Flags: []common.Flag{
{Name: "minute-token", Desc: "minute token", Required: true},
{Name: "summary", Desc: "replacement summary text (Markdown subset renders best in Minutes)", Required: true, Input: []string{common.File, common.Stdin}},
},
Tips: []string{
minutesSummaryMarkdownTip,
"Use `lark-cli vc +notes --minute-tokens <token>` to read the current summary before replacing it.",
},
Validate: func(ctx context.Context, runtime *common.RuntimeContext) error {
minuteToken := runtime.Str("minute-token")
if minuteToken == "" {
return output.ErrValidation("--minute-token is required")
}
if err := validate.ResourceName(minuteToken, "--minute-token"); err != nil {
return output.ErrValidation("%s", err)
}
summary := strings.TrimSpace(runtime.Str("summary"))
if summary == "" {
return output.ErrValidation("--summary is required")
}
return nil
},
DryRun: func(ctx context.Context, runtime *common.RuntimeContext) *common.DryRunAPI {
return common.NewDryRunAPI().
PUT(fmt.Sprintf("/open-apis/minutes/v1/minutes/%s/summary", validate.EncodePathSegment(runtime.Str("minute-token")))).
Body(map[string]interface{}{"summary": "<summary markdown>"})
},
Execute: func(ctx context.Context, runtime *common.RuntimeContext) error {
minuteToken := runtime.Str("minute-token")
summary := strings.TrimSpace(runtime.Str("summary"))
path := fmt.Sprintf("/open-apis/minutes/v1/minutes/%s/summary", validate.EncodePathSegment(minuteToken))
body := map[string]interface{}{
"summary": summary,
}
if _, err := runtime.CallAPI(http.MethodPut, path, nil, body); err != nil {
return err
}
runtime.OutFormat(map[string]interface{}{
"minute_token": minuteToken,
"updated": true,
}, nil, nil)
return nil
},
}

View File

@@ -0,0 +1,221 @@
// Copyright (c) 2026 Lark Technologies Pte. Ltd.
// SPDX-License-Identifier: MIT
package minutes
import (
"encoding/json"
"strings"
"testing"
"github.com/larksuite/cli/internal/cmdutil"
"github.com/larksuite/cli/internal/httpmock"
)
func todoStub(token string) *httpmock.Stub {
return &httpmock.Stub{
Method: "PUT",
URL: "/open-apis/minutes/v1/minutes/" + token + "/todo",
Body: map[string]interface{}{"code": 0, "msg": "ok", "data": map[string]interface{}{}},
}
}
func firstTodoItem(t *testing.T, raw []byte) map[string]any {
t.Helper()
if len(raw) == 0 {
t.Fatal("request body was not captured")
}
var body map[string]any
if err := json.Unmarshal(raw, &body); err != nil {
t.Fatalf("failed to parse captured body: %v", err)
}
items, _ := body["todo_items"].([]any)
if len(items) != 1 {
t.Fatalf("todo_items: want 1 item, got %d (%v)", len(items), body["todo_items"])
}
item, _ := items[0].(map[string]any)
return item
}
func TestMinutesSummary_DryRun(t *testing.T) {
f, stdout, _, _ := cmdutil.TestFactory(t, defaultConfig())
warmTokenCache(t)
err := mountAndRun(t, MinutesSummary, []string{
"+summary",
"--minute-token", "obcn123456789",
"--summary", "**Weekly sync**\n- follow up",
"--dry-run",
"--as", "user",
}, f, stdout)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
out := stdout.String()
if !strings.Contains(out, "PUT") || !strings.Contains(out, "/open-apis/minutes/v1/minutes/obcn123456789/summary") {
t.Fatalf("dry-run output = %q", out)
}
}
func TestMinutesTodo_DryRun(t *testing.T) {
f, stdout, _, _ := cmdutil.TestFactory(t, defaultConfig())
warmTokenCache(t)
err := mountAndRun(t, MinutesTodo, []string{
"+todo",
"--minute-token", "obcn123456789",
"--todo", "- finish deck",
"--is-done",
"--dry-run",
"--as", "user",
}, f, stdout)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
out := stdout.String()
if !strings.Contains(out, "PUT") || !strings.Contains(out, "/open-apis/minutes/v1/minutes/obcn123456789/todo") {
t.Fatalf("dry-run output = %q", out)
}
if !strings.Contains(out, "todo_items") {
t.Fatalf("dry-run output should contain todo_items, got %q", out)
}
}
func TestMinutesTodo_RequiresIsDone(t *testing.T) {
f, _, stderr, _ := cmdutil.TestFactory(t, defaultConfig())
warmTokenCache(t)
err := mountAndRun(t, MinutesTodo, []string{
"+todo",
"--minute-token", "obcn123456789",
"--todo", "finish deck",
"--as", "user",
}, f, stderr)
if err == nil {
t.Fatal("expected validation error for missing --is-done")
}
if !strings.Contains(err.Error(), "is-done") && !strings.Contains(err.Error(), "todo-list") {
t.Fatalf("error = %q, want message mentioning is-done or todo-list", err.Error())
}
}
func TestMinutesTodo_Add_RequestBody(t *testing.T) {
f, stdout, _, reg := cmdutil.TestFactory(t, defaultConfig())
warmTokenCache(t)
stub := todoStub("obcn123456789")
reg.Register(stub)
err := mountAndRun(t, MinutesTodo, []string{
"+todo", "--minute-token", "obcn123456789",
"--todo", "finish deck", "--is-done=false", "--as", "user",
}, f, stdout)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
item := firstTodoItem(t, stub.CapturedBody)
if item["content"] != "finish deck" {
t.Errorf("content = %v, want finish deck", item["content"])
}
if item["is_done"] != false {
t.Errorf("is_done = %v, want false", item["is_done"])
}
if _, ok := item["todo_id"]; ok {
t.Errorf("add should not send todo_id, got %v", item["todo_id"])
}
if !strings.Contains(stdout.String(), "add") {
t.Errorf("output should report add operation, got %q", stdout.String())
}
}
func TestMinutesTodo_Update_RequestBody(t *testing.T) {
f, stdout, _, reg := cmdutil.TestFactory(t, defaultConfig())
warmTokenCache(t)
stub := todoStub("obcn123456789")
reg.Register(stub)
err := mountAndRun(t, MinutesTodo, []string{
"+todo", "--minute-token", "obcn123456789",
"--todo-id", "99", "--todo", "updated deck", "--is-done", "--as", "user",
}, f, stdout)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
item := firstTodoItem(t, stub.CapturedBody)
if item["todo_id"] != "99" {
t.Errorf("todo_id = %v, want 99", item["todo_id"])
}
if item["content"] != "updated deck" {
t.Errorf("content = %v, want updated deck", item["content"])
}
if item["is_done"] != true {
t.Errorf("is_done = %v, want true", item["is_done"])
}
}
func TestMinutesTodo_Delete_RequestBody(t *testing.T) {
f, stdout, _, reg := cmdutil.TestFactory(t, defaultConfig())
warmTokenCache(t)
stub := todoStub("obcn123456789")
reg.Register(stub)
err := mountAndRun(t, MinutesTodo, []string{
"+todo", "--minute-token", "obcn123456789",
"--todo-id", "88", "--as", "user",
}, f, stdout)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
item := firstTodoItem(t, stub.CapturedBody)
if item["todo_id"] != "88" {
t.Errorf("todo_id = %v, want 88", item["todo_id"])
}
if _, ok := item["content"]; ok {
t.Errorf("delete should not send content, got %v", item["content"])
}
if _, ok := item["is_done"]; ok {
t.Errorf("delete should not send is_done, got %v", item["is_done"])
}
// the todo id must never be surfaced to the user in the command output
if strings.Contains(stdout.String(), "88") {
t.Errorf("output must not expose the todo id, got %q", stdout.String())
}
}
func TestMinutesTodo_DeleteRejectsIsDone(t *testing.T) {
f, _, _, _ := cmdutil.TestFactory(t, defaultConfig())
warmTokenCache(t)
err := mountAndRun(t, MinutesTodo, []string{
"+todo", "--minute-token", "obcn123456789",
"--todo-id", "88", "--is-done", "--as", "user",
}, f, nil)
if err == nil {
t.Fatal("expected validation error when --is-done is used to delete")
}
}
func TestMinutesTodo_RequiresAnyInput(t *testing.T) {
f, _, _, _ := cmdutil.TestFactory(t, defaultConfig())
warmTokenCache(t)
err := mountAndRun(t, MinutesTodo, []string{
"+todo", "--minute-token", "obcn123456789", "--as", "user",
}, f, nil)
if err == nil {
t.Fatal("expected validation error when neither --todo nor --todo-id is provided")
}
}
func TestMinutesSummaryAndTodo_HelpMetadata(t *testing.T) {
for _, tip := range MinutesSummary.Tips {
if strings.Contains(tip, "raw text") {
return
}
}
t.Fatal("MinutesSummary tips should mention unsupported markdown display behavior")
}

View File

@@ -0,0 +1,123 @@
// Copyright (c) 2026 Lark Technologies Pte. Ltd.
// SPDX-License-Identifier: MIT
package minutes
import (
"context"
"fmt"
"net/http"
"strings"
"github.com/larksuite/cli/internal/output"
"github.com/larksuite/cli/internal/validate"
"github.com/larksuite/cli/shortcuts/common"
)
// minuteTodoOp describes the resolved single-todo operation derived from flags.
type minuteTodoOp struct {
operation string // add | update | delete
item map[string]interface{} // the single todo_items entry sent to the API
}
// MinutesTodo adds, updates, or deletes a single todo item on a minute.
var MinutesTodo = common.Shortcut{
Service: "minutes",
Command: "+todo",
Description: "Add, update, or delete a single todo item on a minute",
Risk: "write",
Scopes: []string{"minutes:minutes:update"},
AuthTypes: []string{"user"},
HasFormat: true,
Flags: []common.Flag{
{Name: "minute-token", Desc: "minute token (required)", Required: true},
{Name: "todo", Desc: "todo plain-text content; required (with --is-done) to add or update", Input: []string{common.File, common.Stdin}},
{Name: "is-done", Type: "bool", Desc: "completion flag; required together with --todo"},
{Name: "todo-id", Desc: "id of an existing todo; provide to update (with --todo) or delete (without --todo); omit to add"},
},
Tips: []string{
"Add a todo: `--todo \"...\" --is-done=false`.",
"Update a todo: `--todo-id <id> --todo \"...\" --is-done`.",
"Delete a todo: `--todo-id <id>` (omit --todo).",
"`content` is plain text only; markdown formatting is not supported.",
"Use `lark-cli vc +notes --minute-tokens <token>` to read current todos before writing.",
},
Validate: func(ctx context.Context, runtime *common.RuntimeContext) error {
minuteToken := runtime.Str("minute-token")
if minuteToken == "" {
return output.ErrValidation("--minute-token is required")
}
if err := validate.ResourceName(minuteToken, "--minute-token"); err != nil {
return output.ErrValidation("%s", err)
}
if _, err := resolveMinuteTodoOp(runtime); err != nil {
return err
}
return nil
},
DryRun: func(ctx context.Context, runtime *common.RuntimeContext) *common.DryRunAPI {
return common.NewDryRunAPI().
PUT(fmt.Sprintf("/open-apis/minutes/v1/minutes/%s/todo", validate.EncodePathSegment(runtime.Str("minute-token")))).
Body(map[string]interface{}{
"todo_items": "<todo_items array>",
})
},
Execute: func(ctx context.Context, runtime *common.RuntimeContext) error {
minuteToken := runtime.Str("minute-token")
op, err := resolveMinuteTodoOp(runtime)
if err != nil {
return err
}
path := fmt.Sprintf("/open-apis/minutes/v1/minutes/%s/todo", validate.EncodePathSegment(minuteToken))
body := map[string]interface{}{
"todo_items": []interface{}{op.item},
}
if _, err := runtime.CallAPI(http.MethodPut, path, nil, body); err != nil {
return err
}
// Intentionally omit the todo id from the output: users never see it.
runtime.OutFormat(map[string]interface{}{
"minute_token": minuteToken,
"operation": op.operation,
"updated": true,
}, nil, nil)
return nil
},
}
func resolveMinuteTodoOp(runtime *common.RuntimeContext) (*minuteTodoOp, error) {
todo := strings.TrimSpace(runtime.Str("todo"))
todoID := strings.TrimSpace(runtime.Str("todo-id"))
hasTodo := todo != ""
hasTodoID := todoID != ""
hasIsDone := runtime.Changed("is-done")
item := map[string]interface{}{}
if hasTodoID {
item["todo_id"] = todoID
}
switch {
case hasTodo:
// add or update: content and is_done must appear together
if !hasIsDone {
return nil, output.ErrValidation("--todo requires --is-done")
}
item["content"] = todo
item["is_done"] = runtime.Bool("is-done")
if hasTodoID {
return &minuteTodoOp{operation: "update", item: item}, nil
}
return &minuteTodoOp{operation: "add", item: item}, nil
case hasTodoID:
// delete: only the id is needed; content/is_done are not allowed
if hasIsDone {
return nil, output.ErrValidation("--is-done cannot be used to delete a todo (omit it, and provide only --todo-id)")
}
return &minuteTodoOp{operation: "delete", item: item}, nil
default:
return nil, output.ErrValidation("provide --todo (with --is-done) to add/update, or --todo-id alone to delete")
}
}

View File

@@ -11,5 +11,7 @@ func Shortcuts() []common.Shortcut {
MinutesSearch,
MinutesDownload,
MinutesUpload,
MinutesSummary,
MinutesTodo,
}
}

View File

@@ -1,7 +1,7 @@
---
name: lark-drive
version: 1.0.0
description: "飞书云空间(云盘/云存储):管理云空间(云盘/云存储)中的文件和文件夹。上传和下载文件、创建文件夹、复制/移动/删除文件、查看文件元数据、管理文档评论、管理文档权限、订阅用户评论变更事件、修改文件标题docx、sheet、bitable、file、folder、wiki也负责把本地 Word/Markdown/Excel/CSV/PPTX 以及 Base 快照(.base导入为飞书在线云文档docx、sheet、bitable、slides。当用户需要上传或下载文件、整理云空间云盘/云存储)目录、查看文件详情、管理评论、管理文档权限、修改文件标题、订阅用户评论变更事件,或要把本地文件导入成新版文档、电子表格、多维表格/Base/幻灯片时使用。涉及云空间/知识库/文档库的知识资产盘点、整理、治理,也从本 skill 进入。\"云空间\"、\"云盘\"和\"云存储\"是同一概念,用户说\"云盘\"、\"云存储\"、\"网盘\"、\"我的空间\"时均路由到本 skill。"
description: "飞书云空间(云盘/云存储):管理云空间(云盘/云存储)中的文件和文件夹。上传和下载文件、创建文件夹、复制/移动/删除文件、查看文件元数据、管理文档评论、管理文档权限、订阅用户评论变更事件、修改文件标题docx、sheet、bitable、file、folder、wiki也负责把本地 Word/Markdown/Excel/CSV/PPTX 以及 Base 快照(.base导入为飞书在线云文档docx、sheet、bitable、slides。当用户需要上传或下载文件、整理云空间云盘/云存储)目录、查看文件详情、管理评论、管理文档权限、修改文件标题、订阅用户评论变更事件,或要把本地文件导入成新版文档、电子表格、多维表格/Base/幻灯片 时使用。\"云空间\"、\"云盘\"和\"云存储\"是同一概念,用户说\"云盘\"、\"云存储\"、\"网盘\"、\"我的空间\"时均路由到本 skill。"
metadata:
requires:
bins: ["lark-cli"]
@@ -18,8 +18,7 @@ metadata:
## 快速决策
- 用户要**搜文档 / 知识库 / 电子表格 / 多维表格 / 云空间(云盘/云存储)对象**,优先使用 `lark-cli drive +search`。自然语言里"最近我编辑过的"、"我创建的"(→ `--mine`,实为 owner 语义)、"最近一周我打开过的 xxx"、"某人 owner 的 docx" 等直接映射到扁平 flag避免手写嵌套 JSON。
- 用户要**盘点/整理/治理云空间、知识库或文档库资产**,读取 [`lark-drive-knowledge-overview.md`](references/lark-drive-knowledge-overview.md) 做范围和 recipe 路由。
- 用户要**搜文档 / Wiki / 电子表格 / 多维表格 / 云空间(云盘/云存储)对象**,优先使用 `lark-cli drive +search`。自然语言里"最近我编辑过的"、"我创建的"(→ `--mine`,实为 owner 语义)、"最近一周我打开过的 xxx"、"某人 owner 的 docx" 等直接映射到扁平 flag避免手写嵌套 JSON。
- 用户要把本地 `.xlsx` / `.csv` / `.base` 导入成 Base / 多维表格 / bitable第一步必须使用 `lark-cli drive +import --type bitable`
- 用户要把本地 `.md` / `.docx` / `.doc` / `.txt` / `.html` 导入成在线文档,使用 `lark-cli drive +import --type docx`
- 用户要把本地 `.pptx` 导入成飞书幻灯片,使用 `lark-cli drive +import --type slides`;当前 PPTX 导入上限是 500MB。
@@ -28,19 +27,9 @@ metadata:
- 用户要查看、下载、回滚或删除文件的**历史版本**,使用 `drive +version-history``drive +version-get``drive +version-revert``drive +version-delete`;这组命令同时支持 `--as user``--as bot`,自动化场景优先 `--as bot`
- 用户要把本地 `.xlsx` / `.xls` / `.csv` 导入成电子表格,使用 `lark-cli drive +import --type sheet`
- 用户要在云空间(云盘/云存储)里新建文件夹,优先使用 `lark-cli drive +create-folder`
- 用户要把本地文件上传到知识库 / 文档库里的某个节点下时,仍然使用 `lark-cli drive +upload --wiki-token <wiki_token>`;不要误切到 `wiki` 域命令。
- 用户要把本地文件上传到知识库 / 文档库里的某个 wiki 节点下时,仍然使用 `lark-cli drive +upload --wiki-token <wiki_token>`;不要误切到 `wiki` 域命令。
- `lark-base` 只负责导入完成后的 Base 内部操作(表、字段、记录、视图),不要在“本地文件 -> Base”这一步提前切到 `lark-base`
## 知识资产整理 Workflow
触发上面的知识资产场景后,按以下顺序加载 reference不要把所有跨域细节塞进当前上下文
1. 先阅读 [`lark-drive-knowledge-overview.md`](references/lark-drive-knowledge-overview.md) 判断目标范围和 recipe。
2. 需要标准中间产物时阅读 [`lark-drive-knowledge-artifacts.md`](references/lark-drive-knowledge-artifacts.md)。
3. 涉及任何写操作或权限治理时阅读 [`lark-drive-knowledge-safety.md`](references/lark-drive-knowledge-safety.md)。
4. 涉及知识库节点树、知识库成员、文档库 / my_library 时,切到 [`lark-wiki`](../lark-wiki/SKILL.md) 读取具体命令规则。
5. 涉及文档正文读取或报告创建时,切到 [`lark-doc`](../lark-doc/SKILL.md);涉及 Sheets 台账时,切到 [`lark-sheets`](../lark-sheets/SKILL.md)。
## 修改标题
- 使用 `drive files patch` 命令通过new_title字段可以修改标题支持 docx、sheet、bitable、file、wiki、folder 类型

View File

@@ -1,176 +0,0 @@
# 知识资产整理 Artifact 协议
> 用途:让 inventory、organize、permission-audit 等 workflow 可串联、可恢复、可评测。字段保持最小稳定;缺失信息用空值、`warnings` 或 `unsupported_checks` 表达,不要编造。
## 目录约定
```text
./lark-drive-knowledge/<run-id>/
scope.json
inventory.json
organize-plan.json
permission-audit.json
execution-log.json
report.md
```
`run-id` 建议使用 `YYYYMMDD-HHMMSS-<short-scope>`,例如 `20260526-143000-wiki-space`.
## scope.json
记录用户目标和本次实际处理范围。
```json
{
"run_id": "",
"requested_by_user": "",
"scope_type": "drive_folder|wiki_space|wiki_node|my_library|mixed",
"root": {
"url": "",
"space_id": "",
"folder_token": "",
"node_token": ""
},
"limits": {
"max_depth": -1,
"page_limit": 0,
"content_read": "none|outline|targeted"
},
"generated_at": ""
}
```
## inventory.json
记录事实清单。所有后续分析优先消费它。
```json
{
"run_id": "",
"summary": {
"total": 0,
"by_source": {},
"by_type": {},
"warnings_count": 0
},
"items": [
{
"source": "drive|wiki|my_library",
"title": "",
"path": "",
"url": "",
"token": "",
"type": "folder|docx|doc|sheet|bitable|file|slides|mindnote|wiki|shortcut",
"space_id": "",
"node_token": "",
"obj_token": "",
"obj_type": "",
"folder_token": "",
"parent_token": "",
"depth": 0,
"has_child": false,
"owner": "",
"created_time": "",
"modified_time": "",
"evidence": []
}
],
"warnings": [],
"generated_at": ""
}
```
## organize-plan.json
只表达计划,不代表已经执行。
```json
{
"run_id": "",
"mode": "plan",
"summary": {
"actions_count": 0,
"requires_confirmation_count": 0
},
"actions": [
{
"id": "act-001",
"action": "create_drive_folder|create_wiki_node|move_drive|move_wiki_node|create_drive_shortcut|create_wiki_shortcut",
"source": {},
"target": {},
"reason": "",
"evidence": [],
"risk": "read|write|high-risk-write",
"requires_confirmation": true,
"dry_run_command": "",
"execute_command": ""
}
],
"blocked": [],
"warnings": [],
"generated_at": ""
}
```
第一版不生成 delete、overwrite、permission patch、owner transfer 动作。
## permission-audit.json
记录权限审计事实、推断和能力边界。
```json
{
"run_id": "",
"summary": {
"audited_items": 0,
"risk_findings": 0,
"unsupported_checks": []
},
"items": [
{
"title": "",
"path": "",
"url": "",
"token": "",
"type": "",
"wiki_space_members": [],
"public_permission": {},
"risk_findings": [
{
"type": "",
"severity": "low|medium|high",
"fact": "",
"inference": "",
"suggestion": "",
"evidence": [],
"requires_confirmation": true
}
],
"unsupported_checks": []
}
],
"warnings": [],
"generated_at": ""
}
```
## execution-log.json
仅在用户明确确认执行 organize plan 后生成。
```json
{
"run_id": "",
"plan_file": "",
"results": [
{
"action_id": "act-001",
"status": "success|failed|skipped",
"command": "",
"result": {},
"error": ""
}
],
"generated_at": ""
}
```

View File

@@ -1,115 +0,0 @@
# 知识资产盘点 Workflow
> 前置条件:先读 [`lark-drive-knowledge-overview.md`](lark-drive-knowledge-overview.md) 和 [`lark-drive-knowledge-artifacts.md`](lark-drive-knowledge-artifacts.md)。涉及 Wiki 或文档库时再读 [`../../lark-wiki/SKILL.md`](../../lark-wiki/SKILL.md)。
## 目标
对云空间文件夹、知识库、知识库子树或文档库做结构化盘点,生成 `inventory.json`,作为整理、权限审计和报告的事实底座。
## Step 1: 归一化范围
- 云空间文件夹 URL提取 `folder_token`,或用 `drive +inspect` 确认类型。
- 知识库 URL`wiki +node-get``drive +inspect` 获取 `space_id``node_token``obj_type``obj_token`
- `my_library` / 文档库:固定走 `wiki +node-list --space-id my_library --as user`
记录到 `scope.json`
## Step 2: 盘点云空间文件夹
先查看 schema
```bash
lark-cli schema drive.files.list --format json
```
读取直接子项:
```bash
lark-cli drive files list \
--params '{"folder_token":"<folder_token>","page_size":200}' \
--page-all --page-limit 0 --format json --as user
```
对返回的 `type=folder` 子项递归调用同一命令。记录字段:
- `name` -> `title`
- `token`
- `type`
- `url`
- `parent_token`
- `owner_id`
- `created_time`
- `modified_time`
如需按关键词或时间补充范围,可用 `drive +search`,但目录树以 `drive files list` 为准。
## Step 3: 盘点知识库或文档库
读取根层:
```bash
lark-cli wiki +node-list \
--space-id "<space_id_or_my_library>" \
--page-all --page-limit 0 --format json --as user
```
`has_child=true` 的节点递归:
```bash
lark-cli wiki +node-list \
--space-id "<space_id_or_my_library>" \
--parent-node-token "<node_token>" \
--page-all --page-limit 0 --format json --as user
```
必要时补节点详情:
```bash
lark-cli wiki +node-get --node-token "<node_token>" --format json --as user
```
记录字段:
- `title`
- `node_token`
- `obj_token`
- `obj_type`
- `node_type`
- `parent_node_token`
- `has_child`
- `space_id`
- `owner`(如果详情返回)
## Step 4: 可选读取内容结构
只有用户需要“按内容归类”“识别主题”“生成导航页”时才读取正文结构。优先读 outline不全量读正文
```bash
lark-cli docs +fetch \
--api-version v2 \
--doc "<url_or_token>" \
--scope outline \
--max-depth 3 \
--doc-format markdown \
--format json --as user
```
失败时保留结构盘点结果,并在 `warnings` 中标记 `content_outline_failed`
## Step 5: 输出 inventory.json
把所有条目写入 `./lark-drive-knowledge/<run-id>/inventory.json`。聊天回复只输出摘要:
- 总数
- 按 source/type 统计
- 空标题数量
- 重复标题候选数量
- 未读取或失败的节点数量
- artifact 路径
## 停止条件
- 没有权限读取根节点或根文件夹:停止并给出授权建议。
- 分页失败或 cursor 不前进:保留已读结果,写入 `warnings`
- 结果规模过大:停止深挖正文,只输出结构清单并提示用户缩小范围。
- 知识库和云空间混合范围不清楚:先让用户确认是否都处理。

View File

@@ -1,107 +0,0 @@
# 散乱知识整理 Workflow
> 前置条件:先有 `inventory.json`,并阅读 [`lark-drive-knowledge-safety.md`](lark-drive-knowledge-safety.md)。第一版默认只生成整理计划,不直接执行。
## 目标
帮助用户把云空间文件夹、知识库或文档库下的散乱文档组织成更清晰的目录结构。输出 `organize-plan.json`
## 输入
- 必需:`inventory.json`
- 可选:用户给出的目标分类规则,例如“按项目”“按业务线”“按系统”“按年份”“按文档类型”
- 可选outline 读取结果,用于按内容主题分类
## 分析规则
优先基于事实字段:
- 路径层级过深或过平。
- 空标题、重复标题、相似标题。
- 同一主题散落在多个目录。
- 云空间文件夹中在线文档和普通文件混放。
- 知识库 shortcut 复用或源文档散落。
- 文档标题包含“旧版”“废弃”“草稿”“临时”等治理信号。
模型推断必须写入 `reason``evidence`,不能把推断当事实。
## 生成计划
允许生成的 action
| action | 说明 |
|-|-|
| `create_drive_folder` | 在 Drive 中创建目标文件夹 |
| `create_wiki_node` | 在知识库或文档库 / `my_library` 中创建目录节点 |
| `move_drive` | 移动 Drive 文件/文件夹 |
| `move_wiki_node` | 移动知识库节点 |
| `create_drive_shortcut` | 在 Drive 目标文件夹创建快捷方式 |
| `create_wiki_shortcut` | 在知识库中创建 shortcut 节点 |
禁止生成的 action
- delete
- overwrite
- permission patch
- member remove
- owner transfer
每个 action 必须包含:
- source
- target
- reason
- evidence
- `requires_confirmation=true`
- dry-run command
- execute command
## 命令模板
Drive 创建文件夹:
```bash
lark-cli drive +create-folder --name "<folder_name>" --folder-token "<parent_folder_token>" --dry-run --as user
```
Drive 移动:
```bash
lark-cli drive +move --file-token "<token>" --type "<type>" --folder-token "<target_folder_token>" --dry-run --as user
```
Drive 快捷方式:
```bash
lark-cli drive +create-shortcut --file-token "<token>" --type "<type>" --folder-token "<target_folder_token>" --dry-run --as user
```
Wiki 创建节点:
```bash
lark-cli wiki +node-create --space-id "<space_id_or_my_library>" --parent-node-token "<parent_node_token>" --title "<title>" --obj-type docx --dry-run --as user
```
Wiki 移动节点:
```bash
lark-cli wiki +move --node-token "<node_token>" --target-parent-token "<target_parent_node_token>" --dry-run --as user
```
Wiki shortcut
```bash
lark-cli wiki +node-create --space-id "<space_id>" --parent-node-token "<parent_node_token>" --node-type shortcut --origin-node-token "<source_node_token>" --title "<title>" --dry-run --as user
```
## 执行与验收
只有用户明确确认某个 `organize-plan.json` 后,才逐条执行。执行后写 `execution-log.json`,并重新跑 inventory 验证目标目录结构。
聊天回复要给出:
- 计划文件路径
- action 数量
- 需要确认的高影响动作
- 被阻塞的动作和原因
- 下一步确认方式

View File

@@ -1,59 +0,0 @@
# 知识资产整理 Workflow 总览
> **前置条件:** 先阅读 [`../lark-shared/SKILL.md`](../../lark-shared/SKILL.md) 了解认证、全局参数和安全规则。
## 适用场景
- 盘点某个云空间文件夹、知识库、知识库节点或文档库下的文档和目录。
- 整理散乱文档:归类、生成目录结构、移动计划、快捷方式计划、导航页建议。
- 治理知识资产:重复、空标题、孤立、未归档、命名混乱、内容缺失或过期。
- 审计权限风险Wiki 空间成员、文档公开链接、组织外访问、分享/复制/下载设置。
## 目标范围模型
| 用户目标 | 视作 | 主入口 |
|-|-|-|
| 云空间文件夹 | Drive folder tree | `drive files list` / `drive +search` |
| 知识库 / 知识库节点 | Wiki space/node tree | `wiki +node-list` / `wiki +node-get` |
| 文档库 / my_library | Wiki personal library | `wiki +node-list --space-id my_library --as user` |
| 文档正文或标题结构 | Docs content | `docs +fetch --api-version v2` |
| 报告或台账 | Docs / Sheets | `docs +create` / `sheets +create` |
不要把“文档库”当成 Drive 根目录;它应走 Wiki personal library。
## Recipe 路由
| 用户意图 | 读取文件 | 产物 |
|-|-|-|
| 盘点、梳理、导出清单、看目录结构 | [`lark-drive-knowledge-inventory.md`](lark-drive-knowledge-inventory.md) | `inventory.json` |
| 整理、归类、组织目录、生成移动计划 | [`lark-drive-knowledge-organize.md`](lark-drive-knowledge-organize.md) | `organize-plan.json` |
| 权限风险、公开链接、组织外访问、成员过多 | [`lark-drive-knowledge-permission-audit.md`](lark-drive-knowledge-permission-audit.md) | `permission-audit.json` |
复合意图按链路执行:
```text
盘点并整理 -> inventory -> organize
盘点并审计权限 -> inventory -> permission-audit
整理并输出报告 -> inventory -> organize -> report artifact
```
## 执行原则
- 大范围结果默认写入本地 artifact不把完整清单塞进聊天。
- 每次 run 使用独立目录:`./lark-drive-knowledge/<run-id>/`
- 工作流之间通过 artifact 串联,优先复用已有 `inventory.json`,不要无故重复爬取。
- 所有判断必须区分事实、推断、建议;没有证据的内容写入 `warnings``unsupported_checks`
- 原生 API 调用前必须先运行 `lark-cli schema <service>.<resource>.<method>` 校验参数结构。
- 写操作和权限治理必须遵守 [`lark-drive-knowledge-safety.md`](lark-drive-knowledge-safety.md)。
## 当前能力边界
| 能力 | 状态 | 说明 |
|-|-|-|
| 知识库节点盘点 | 支持 | 递归 `wiki +node-list` |
| 云空间文件夹盘点 | 支持 | 递归 `drive files list` |
| 文档标题结构读取 | 支持 | `docs +fetch --scope outline` |
| Wiki 空间成员审计 | 支持 | `wiki +member-list` |
| 文档公开权限审计 | 支持 | `drive permission.public get` |
| 单文档协作者全量枚举 | 暂不支持 | 当前 Drive permission members 只有 `auth/create/transfer_owner` |
| 自动删除、覆盖、降权、改权限 | 第一版不执行 | 只输出计划和建议 |

View File

@@ -1,109 +0,0 @@
# 权限风险审计 Workflow
> 前置条件:先读 [`lark-drive-knowledge-overview.md`](lark-drive-knowledge-overview.md)、[`lark-drive-knowledge-artifacts.md`](lark-drive-knowledge-artifacts.md) 和 [`lark-drive-knowledge-safety.md`](lark-drive-knowledge-safety.md)。第一版只审计和建议,不自动改权限。
## 目标
审计 云空间 / 知识库 / 文档库范围内的权限风险,重点覆盖:
- Wiki 空间成员和角色。
- 文档公开权限、外部访问、链接分享、复制/下载/打印限制。
- 当前能力无法验证的权限项。
## 当前能力边界
| 检查项 | 状态 | 命令 |
|-|-|-|
| Wiki 空间成员 | 支持 | `wiki +member-list` |
| 文档公开权限 | 支持 | `drive permission.public get`,仅限 schema 支持的文档类型 |
| Drive 文件夹公开权限 | 暂不支持 | `drive.permission.public.get` 不支持 `type=folder` |
| 当前用户/应用是否具备某权限 | 支持 | `drive permission.members auth` |
| 单文档显式协作者全量枚举 | 暂不支持 | 当前无 `drive.permission.members.list` |
| 自动关闭公开权限或降权 | 第一版不执行 | 只输出建议 |
## Step 1: 输入范围
优先消费 `inventory.json`。如果用户只给一个 URL先按 [`lark-drive-knowledge-inventory.md`](lark-drive-knowledge-inventory.md) 做最小盘点。
## Step 2: Wiki 空间成员审计
涉及知识库空间或文档库 / `my_library` 时:
```bash
lark-cli wiki +member-list \
--space-id "<space_id_or_my_library>" \
--page-all --page-limit 0 --format json --as user
```
审计信号:
- admin 数量异常多。
- 成员包含部门、群或开放范围较大的 member_type。
- 文档库成员结果缺失或不适用时写入 `warnings`,不要推断。
## Step 3: 文档公开权限审计
先查看 schema
```bash
lark-cli schema drive.permission.public.get --format json
```
只对 inventory 中 `type` 属于以下集合的条目查询:
```text
doc, docx, sheet, bitable, file, wiki, mindnote, minutes, slides
```
`type=folder` 的 Drive 文件夹不要调用 `drive.permission.public get`。将该 item 写入 `unsupported_checks``warnings`,例如 `drive_folder_public_permission_unsupported`,并继续审计其他条目。
```bash
lark-cli drive permission.public get \
--params '{"token":"<token>","type":"<type>"}' \
--format json --as user
```
Token/type 选择:
- 云空间文件:用 Drive `token``type=file`
- 云空间文件夹:不执行 `permission.public.get`,记录为未覆盖检查。
- 知识库节点权限:优先用 `node_token``type=wiki`
- 底层文档权限:用 `obj_token``obj_type`
如果某类 token 查询失败,不要中断全局审计;写入该 item 的 `warnings`。如果是 `type=folder`,不要把它当作 API 失败,而是写入 `unsupported_checks`
## 风险规则
| 字段 | 高风险 | 中风险 |
|-|-|-|
| `link_share_entity` | `anyone_editable`, `anyone_readable` | `tenant_editable` |
| `external_access` | `true` 且链接或分享范围较宽 | `true` |
| `share_entity` | `anyone` | `same_tenant` |
| `security_entity` | `anyone_can_view` | `anyone_can_edit` |
| Wiki member role | admin 过多 | 部门/群成员需确认 |
每条风险都必须写清楚:
- `fact`API 返回字段。
- `inference`:为什么可能有风险。
- `suggestion`:建议 owner 或管理员确认的动作。
- `evidence`token、URL、path、字段名和值。
- `requires_confirmation=true`
## 输出
写入 `permission-audit.json`,聊天回复只给摘要:
- 审计对象数量。
- 高/中/低风险数量。
- 公开权限风险 Top N。
- Wiki 成员风险摘要。
- 未覆盖检查:必须包含 `explicit_collaborator_list`,说明当前 CLI 无法枚举单文档显式协作者列表。
- 如果 inventory 中包含 `type=folder`,未覆盖检查必须包含 `drive_folder_public_permission`,说明 `drive.permission.public.get` 不支持 Drive 文件夹。
## 禁止事项
- 不自动调用 `permission.public.patch`
- 不自动调用 `permission.members.create``transfer_owner` 或任何成员删除/降权接口。
- 不把“无法枚举协作者”说成“没有协作者风险”。
- 不把模型推断写成事实。

View File

@@ -1,50 +0,0 @@
# 知识资产整理安全策略
> 默认安全级别:只读或计划优先。任何会改变用户文档、目录或权限的操作,都必须先生成计划并让用户明确确认。
## 动作分级
| 分级 | 示例 | 策略 |
|-|-|-|
| Read-only | 列目录、查元数据、读 outline、读公开权限 | 可直接执行 |
| Plan-only | 生成整理计划、权限治理建议、报告草稿 | 可直接执行 |
| Confirmed write | 创建文件夹、创建知识库节点、移动文档、创建快捷方式、新建报告 | 必须先展示计划,用户确认后执行 |
| High-risk write | 删除、覆盖、改权限、转移 owner、移除成员、公开权限 patch | 第一版 workflow 不执行 |
| Unsupported | 单文档协作者全量枚举 | 明确说明当前能力无法验证 |
## 强制规则
- 盘点、整理建议、权限审计默认不修改任何资源。
- `organize-plan.json` 不是执行授权;只有用户明确说执行某个 plan才进入执行阶段。
- 执行前必须展示 action 列表,包括 source、target、reason、risk、dry-run command。
- 执行写操作前先跑对应 `--dry-run`dry-run 通过不等于用户已确认真实执行。
- 不自动执行 delete、overwrite、permission patch、member remove、owner transfer。
- 权限治理第一版只输出风险和建议,不自动收敛权限。
- 对无法确认的风险,写成 `unsupported_checks``requires_confirmation=true`,不要当作事实。
## 需要停下来问用户的情况
- 目标范围不明确,例如同时给出多个 folder/wiki URL 但未说明是否都处理。
- 计划包含跨空间移动、跨 Drive/Wiki 移动或大量移动。
- 目标目录已有同名节点/文件夹,无法判断是否复用。
- 用户要求“直接整理好”,但计划还没有展示和确认。
- 用户要求“治理权限”,但动作涉及改公开权限、移除成员、降权或转移 owner。
- 节点数量或文件数量超出上下文可处理范围,需要改为文件产物或缩小范围。
## 权限风险表述
表述必须区分:
- 事实API 返回的字段,例如 `external_access=true`
- 推断:基于规则得到的风险,例如“可能允许组织外传播”。
- 建议:下一步动作,例如“建议 owner 人工确认是否关闭外部访问”。
- 未覆盖:当前 API/CLI 不能验证的项,例如“无法枚举单文档显式协作者列表”。
示例:
```text
事实link_share_entity=anyone_editable。
推断:互联网获得链接的人可能可编辑,属于高风险公开权限。
建议:请 owner 确认是否需要关闭链接分享或收敛为 tenant_readable。
未覆盖:未检查显式协作者列表,因为当前 CLI 没有 permission.members.list。
```

View File

@@ -99,6 +99,9 @@ Minutes (妙记) ← minute_token 标识
> - 用户说"通过文件生成妙记 / 把音视频转妙记" → 先上传获取 `file_token`,然后使用 `minutes +upload`
> - 用户说"把音视频文件转成纪要 / 逐字稿 / 文字稿 / 撰写文字 / 总结 / 待办 / 章节" → 先上传获取 `file_token`,调用 `minutes +upload` 生成 `minute_url`,再提取 `minute_token` 走 `vc +notes --minute-tokens`
> - 用户说"替换 / 更新这个妙记的总结" → [`minutes +summary`](references/lark-minutes-summary.md)
> - 用户说"新增 / 更新 / 删除这个妙记的某条待办" → [`minutes +todo`](references/lark-minutes-todo.md)
## Shortcuts推荐优先使用
Shortcut 是对常用操作的高级封装(`lark-cli minutes +<verb> [flags]`)。有 Shortcut 的操作优先使用。
@@ -108,10 +111,14 @@ Shortcut 是对常用操作的高级封装(`lark-cli minutes +<verb> [flags]`
| [`+search`](references/lark-minutes-search.md) | Search minutes by keyword, owners, participants, and time range |
| [`+download`](references/lark-minutes-download.md) | Download audio/video media file of a minute |
| [`+upload`](references/lark-minutes-upload.md) | Upload a media file token to generate a minute |
| [`+summary`](references/lark-minutes-summary.md) | Replace the AI summary of a minute |
| [`+todo`](references/lark-minutes-todo.md) | Add, update, or delete a single todo item |
- 使用 `+search` 命令时,必须阅读 [references/lark-minutes-search.md](references/lark-minutes-search.md),了解搜索参数和返回值结构。
- 使用 `+download` 命令时,必须阅读 [references/lark-minutes-download.md](references/lark-minutes-download.md),了解下载参数和返回值结构。
- 使用 `+upload` 命令时,必须阅读 [references/lark-minutes-upload.md](references/lark-minutes-upload.md),了解生成参数和返回值结构。
- 使用 `+summary` 时,必须阅读 [references/lark-minutes-summary.md](references/lark-minutes-summary.md);妙记端通常可良好展示:一级/二级/三级标题(`#` / `##` / `###`)、加粗(`**text**`)、无序列表(`-` / `*`)、有序列表(`1.`),以及纯文本与换行;不支持的 Markdown 语法会按原始文本展示接口不会因此拒绝Agent 写入时应优先使用可展示子集。
- 使用 `+todo` 时,必须阅读 [references/lark-minutes-todo.md](references/lark-minutes-todo.md);对单条待办做增删改:新增传 `--todo`(纯文本)+`--is-done`,更新再加 `--todo-id`,删除只传 `--todo-id`。待办 id 通过 `vc +notes` 读取,不向用户展示。
<!-- AUTO-GENERATED-START — gen-skills.py 管理,勿手动编辑 -->

View File

@@ -0,0 +1,122 @@
# minutes +summary
> **前置条件:** 先阅读 [`../lark-shared/SKILL.md`](../../lark-shared/SKILL.md) 了解认证、全局参数和安全规则。
替换妙记的 AI 总结内容。写操作,会覆盖当前总结。
本 skill 对应 shortcut`lark-cli minutes +summary`(调用 `PUT /open-apis/minutes/v1/minutes/{minute_token}/summary`)。
## 典型触发表达
- "把这条妙记的总结改成……"
- "更新 / 替换妙记的 AI 总结"
- "修正总结内容后写回妙记"
## 命令
```bash
# 直接传入总结内容Markdown 子集)
lark-cli minutes +summary --minute-token obcnxxxxxxxxxxxxxxxxxxxx --summary "**会议结论**\n- 方案 A 通过\n- 下周跟进排期"
# 从文件读取总结内容
lark-cli minutes +summary --minute-token obcnxxxxxxxxxxxxxxxxxxxx --summary @summary.md
# 从 stdin 读取
echo "**结论**" | lark-cli minutes +summary --minute-token obcnxxxxxxxxxxxxxxxxxxxx --summary @-
# 预览 API 调用
lark-cli minutes +summary --minute-token obcnxxxxxxxxxxxxxxxxxxxx --summary @summary.md --dry-run
```
## 参数
| 参数 | 必填 | 说明 |
|------|------|------|
| `--minute-token <token>` | 是 | 妙记 Token |
| `--summary <text>` | 是 | 替换后的总结内容,支持 `@file` / `@-`stdin |
| `--dry-run` | 否 | 预览 API 调用,不执行 |
## 核心约束
### 1. 先读后写
替换前建议先用 `lark-cli vc +notes --minute-tokens <token>` 读取当前总结,确认 `minute_token` 与待替换内容无误。
### 2. Markdown 展示说明
接口接受任意总结文本,**不会因 Markdown 格式校验失败而拒绝请求**。妙记客户端通常只能良好渲染以下 Markdown 子集;不支持的语法(如链接、代码块、四级标题等)会**按原始文本展示**(保留 Markdown 标记字符不会渲染成对应样式。Agent 写入时应优先使用可展示语法,避免用户在妙记里看到字面量的 `[链接](url)`、`` `code` `` 等:
| 支持 | 写法 | 示例 |
|------|------|------|
| 纯文本 | 普通段落 | `本次会议讨论了 Q2 预算` |
| 换行 | `\n` 或空行 | 分段落书写 |
| 一级标题 | `# ` + 标题文字 | `# 会议结论` |
| 二级标题 | `## ` + 标题文字 | `## 行动项` |
| 三级标题 | `### ` + 标题文字 | `### 跟进事项` |
| 加粗 | `**文字**` | `**重点结论**` |
| 无序列表 | `- ` 或 `* ` | `- 跟进预算审批` |
| 有序列表 | `1. ` | `1. 确认需求` |
> 标题语法建议:`#` 后保留空格,并优先使用 13 级(`#` / `##` / `###`)。四级及以上(`####`)无法渲染,会以原始文本形式展示。
**不建议使用**(会按原始文本展示):链接、图片、代码块、表格、引用块、斜体、删除线、四级及以上标题等。
合法示例:
```markdown
# 会议结论
## 核心讨论
**方案 A 通过**,下周启动排期。
### 待跟进
- 预算审批
- 排期确认
1. 张三负责预算
2. 李四负责排期
```
### 3. 所需权限
| 身份 | 所需权限 |
|------|---------|
| user | `minutes:minutes:update` |
## 输出结果
```json
{
"minute_token": "obcnxxxxxxxxxxxxxxxxxxxx",
"updated": true
}
```
| 字段 | 说明 |
|------|------|
| `minute_token` | 妙记 Token |
| `updated` | 是否已成功更新 |
## 如何获取 minute_token
| 来源 | 获取方式 |
|------|---------|
| 妙记 URL | 从 URL 末尾提取,如 `https://sample.feishu.cn/minutes/obcnxxxxxxxxxxxxxxxxxxxx` |
| 妙记搜索 | `lark-cli minutes +search --query "关键词"` |
| 会议产物查询 | `lark-cli vc +notes --minute-tokens <token>` |
## 常见错误与排查
| 错误现象 | 错误码 | 根本原因 | 解决方案 |
|---------|--------|---------|---------|
| 总结展示为原始 Markdown 文本 | — | 总结含链接、四级标题等妙记端无法渲染的语法 | 改用标题(####)、加粗、列表等可展示格式;接口不会因此报错 |
| 参数无效 | — | `minute_token` 缺失或格式错误 | 检查 token 是否完整 |
| 权限不足 | — | 缺少 `minutes:minutes:update` | 运行 `auth login --scope "minutes:minutes:update"` |
## 参考
- [lark-minutes](../SKILL.md) — 妙记全部命令
- [minutes +todo](lark-minutes-todo.md) — 替换待办项
- [lark-vc-notes](../../lark-vc/references/lark-vc-notes.md) — 读取总结、待办等 AI 产物
- [lark-shared](../../lark-shared/SKILL.md) — 认证和全局参数

View File

@@ -0,0 +1,122 @@
# minutes +todo
> **前置条件:** 先阅读 [`../lark-shared/SKILL.md`](../../lark-shared/SKILL.md) 了解认证、全局参数和安全规则。
对妙记中的**单条**待办做新增 / 更新 / 删除。写操作。
本 skill 对应 shortcut`lark-cli minutes +todo`(调用 `PUT /open-apis/minutes/v1/minutes/{minute_token}/todo`)。
## 典型触发表达
- "给这条妙记加一条待办"
- "把某条待办改成……"
- "标记某条待办为已完成 / 取消完成"
- "删除某条待办"
## 命令
```bash
# 新增一条待办(不带 idcontent 与 is_done 成对)
lark-cli minutes +todo --minute-token obcnxxxxxxxxxxxxxxxxxxxx --todo "跟进预算审批" --is-done=false
# 更新已有待办(带 id覆盖内容与完成状态
lark-cli minutes +todo --minute-token obcnxxxxxxxxxxxxxxxxxxxx --todo-id 1234567890 --todo "整理会议纪要" --is-done
# 删除已有待办(只带 id不带 --todo
lark-cli minutes +todo --minute-token obcnxxxxxxxxxxxxxxxxxxxx --todo-id 1234567890
# 预览 API 调用
lark-cli minutes +todo --minute-token obcnxxxxxxxxxxxxxxxxxxxx --todo "新待办" --is-done --dry-run
```
## 参数
| 参数 | 必填 | 说明 |
|------|------|------|
| `--minute-token <token>` | 是 | 妙记 Token |
| `--todo <text>` | 视操作 | 待办纯文本;新增 / 更新时必填,且必须与 `--is-done` 成对出现;删除时不传 |
| `--is-done` | 视操作 | 完成状态布尔值;传 `--is-done` 表示 `true`,传 `--is-done=false` 表示 `false`;仅在带 `--todo` 时使用 |
| `--todo-id <id>` | 视操作 | 已有待办的 id更新 / 删除时必填,新增时不传 |
| `--dry-run` | 否 | 预览 API 调用,不执行 |
## 三种操作的判定
| `--todo-id` | `--todo` | 行为 |
|-------------|----------|------|
| 不传 | 有内容 | **新增**一条待办(需 `--is-done` |
| 传 | 有内容 | **更新** id 对应的待办(需 `--is-done` |
| 传 | 不传 | **删除** id 对应的待办 |
## 核心约束
### 1. 先读后写,待办 id 如何获取
更新 / 删除前先用 `lark-cli vc +notes --minute-tokens <token>` 读取当前待办。返回的每条待办带 `todo_id` 字段,用作 `--todo-id` 的取值。
> 待办 id 仅用于程序内部定位某条待办,不必展示给用户;本命令的输出也不会回显 id。
读取与写入均使用 `is_done` 布尔字段。已删除的待办不会出现在读取结果中。
### 2. 待办内容为纯文本
`content` **不是 Markdown**,请直接传入待办描述文字。
- 不要写 `# 标题``**加粗**``- 列表` 等 Markdown 语法
- 如需多行内容,可直接使用换行;但不会被渲染为 Markdown 格式
### 3. 请求体字段
请求体 `todo_items` 始终只包含**一条**待办:
| CLI | JSON 字段 | 说明 |
|-----|-----------|------|
| `--todo` | `content` | 纯文本待办描述(新增 / 更新必填;删除不传) |
| `--is-done` | `is_done` | 是否已完成(新增 / 更新必填;删除不传) |
| `--todo-id` | `todo_id` | 已有待办 id更新 / 删除必填;新增不传) |
### 4. 所需权限
| 身份 | 所需权限 |
|------|---------|
| user | `minutes:minutes:update` |
## 输出结果
```json
{
"minute_token": "obcnxxxxxxxxxxxxxxxxxxxx",
"operation": "add",
"updated": true
}
```
| 字段 | 说明 |
|------|------|
| `minute_token` | 妙记 Token |
| `operation` | 本次操作类型:`add` / `update` / `delete` |
| `updated` | 是否已成功提交 |
## 如何获取 minute_token
| 来源 | 获取方式 |
|------|---------|
| 妙记 URL | 从 URL 末尾提取,如 `https://sample.feishu.cn/minutes/obcnxxxxxxxxxxxxxxxxxxxx` |
| 妙记搜索 | `lark-cli minutes +search --query "关键词"` |
| 会议产物查询 | `lark-cli vc +notes --minute-tokens <token>` |
## 常见错误与排查
| 错误现象 | 根本原因 | 解决方案 |
|---------|---------|---------|
| 参数无效 | `minute_token` 缺失 | 检查 token |
| 未指定操作 | 既没传 `--todo` 也没传 `--todo-id` | 新增 / 更新需 `--todo`,删除需 `--todo-id` |
| 缺少 `is_done` | 传了 `--todo` 未传 `--is-done` | `--todo``--is-done` 必须成对出现 |
| 删除时多传了 `--is-done` | 删除只需 `--todo-id` | 删除时不要传 `--todo` / `--is-done` |
| 权限不足 | 缺少 `minutes:minutes:update` | 运行 `auth login --scope "minutes:minutes:update"` |
## 参考
- [lark-minutes](../SKILL.md) — 妙记全部命令
- [minutes +summary](lark-minutes-summary.md) — 替换 AI 总结(不支持的 Markdown 会按原始文本展示,详见该文档)
- [lark-vc-notes](../../lark-vc/references/lark-vc-notes.md) — 读取总结、待办等 AI 产物
- [lark-shared](../../lark-shared/SKILL.md) — 认证和全局参数

View File

@@ -91,7 +91,7 @@ lark-cli vc +notes --meeting-ids 69xxxxxxxxxxxxx28 --dry-run
| 字段 | 说明 |
|------|------|
| `artifacts.summary` | AI 总结JSON 内联) |
| `artifacts.todos` | 待办事项JSON 内联) |
| `artifacts.todos` | 待办事项JSON 内联);每条含 `content``is_done``todo_id``todo_id` 仅供 `minutes +todo` 更新/删除单条待办时使用,不必展示给用户 |
| `artifacts.chapters` | 章节纪要JSON 内联) |
| `artifacts.transcript_file` | 逐字稿本地文件路径。默认落到 `./minutes/{minute_token}/transcript.txt`(与 `minutes +download` 聚合);显式 `--output-dir` 时走旧布局 `./{output-dir}/artifact-{title}-{token}/transcript.txt` |

View File

@@ -1,7 +1,7 @@
---
name: lark-wiki
version: 1.0.0
description: "飞书知识库:管理知识空间、空间成员和文档节点。创建和查询知识空间、查看和管理空间成员、管理节点层级结构、在知识库中组织文档和快捷方式。当用户需要在知识库中查找或创建文档、浏览知识空间结构、查看或管理空间成员、移动或复制节点时使用。知识库 / 文档库的文档和目录盘点、整理、治理 workflow 统一从 lark-drive 进入;本 skill 只处理知识空间、成员和节点操作。"
description: "飞书知识库:管理知识空间、空间成员和文档节点。创建和查询知识空间、查看和管理空间成员、管理节点层级结构、在知识库中组织文档和快捷方式。当用户需要在知识库中查找或创建文档、浏览知识空间结构、查看或管理空间成员、移动或复制节点时使用。"
metadata:
requires:
bins: ["lark-cli"]
@@ -24,7 +24,6 @@ metadata:
## 快速决策
- 用户要**盘点、整理、治理知识库 / 文档库中的文档和目录**,不要在本 skill 里展开 workflow切到 [`lark-drive`](../lark-drive/SKILL.md),按其 `lark-drive-knowledge-overview.md` 入口编排。
- 用户给的是知识库 URL`.../wiki/<token>`),且后续要查成员/加成员/删成员:先调用 `lark-cli wiki spaces get_node --params '{"token":"<wiki_token>"}'` 获取 `space_id`,后续成员接口统一使用 `space_id`
- 用户要**删除**知识空间(`wiki +delete-space`)但只给了名称或 URL**不能**把名称 / URL 原样传给 `--space-id`,必须先解析出真实 `space_id`。解析方式:
- URL`.../wiki/<token>``lark-cli wiki spaces get_node --params '{"token":"<wiki_token>"}' --format json`,读 `data.node.space_id`