mirror of
https://github.com/larksuite/cli.git
synced 2026-07-03 22:24:31 +08:00
Compare commits
1 Commits
feat/lark-
...
feat/summa
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
58466a3d94 |
73
shortcuts/minutes/minutes_summary.go
Normal file
73
shortcuts/minutes/minutes_summary.go
Normal 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
|
||||
},
|
||||
}
|
||||
221
shortcuts/minutes/minutes_summary_todo_test.go
Normal file
221
shortcuts/minutes/minutes_summary_todo_test.go
Normal 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")
|
||||
}
|
||||
123
shortcuts/minutes/minutes_todo.go
Normal file
123
shortcuts/minutes/minutes_todo.go
Normal 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")
|
||||
}
|
||||
}
|
||||
@@ -11,5 +11,7 @@ func Shortcuts() []common.Shortcut {
|
||||
MinutesSearch,
|
||||
MinutesDownload,
|
||||
MinutesUpload,
|
||||
MinutesSummary,
|
||||
MinutesTodo,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 管理,勿手动编辑 -->
|
||||
|
||||
|
||||
122
skills/lark-minutes/references/lark-minutes-summary.md
Normal file
122
skills/lark-minutes/references/lark-minutes-summary.md
Normal 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. 确认需求` |
|
||||
|
||||
> 标题语法建议:`#` 后保留空格,并优先使用 1~3 级(`#` / `##` / `###`)。四级及以上(`####`)无法渲染,会以原始文本形式展示。
|
||||
|
||||
**不建议使用**(会按原始文本展示):链接、图片、代码块、表格、引用块、斜体、删除线、四级及以上标题等。
|
||||
|
||||
合法示例:
|
||||
|
||||
```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) — 认证和全局参数
|
||||
122
skills/lark-minutes/references/lark-minutes-todo.md
Normal file
122
skills/lark-minutes/references/lark-minutes-todo.md
Normal 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
|
||||
# 新增一条待办(不带 id,content 与 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) — 认证和全局参数
|
||||
@@ -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` |
|
||||
|
||||
|
||||
Reference in New Issue
Block a user