mirror of
https://github.com/larksuite/cli.git
synced 2026-07-03 14:02:43 +08:00
fix: optimize skill
This commit is contained in:
@@ -253,18 +253,18 @@ func TestMeeting_Execute_NoMeeting(t *testing.T) {
|
||||
|
||||
func TestSearchEvent_Validation_InvalidTimeRange(t *testing.T) {
|
||||
f, _, _, _ := cmdutil.TestFactory(t, calDefaultConfig())
|
||||
err := calMountAndRun(t, CalendarSearchEvent, []string{"+search-event", "--time-range", "bad-format", "--as", "user"}, f, nil)
|
||||
err := calMountAndRun(t, CalendarSearchEvent, []string{"+search-event", "--start", "bad-format", "--end", "2026-04-27", "--as", "user"}, f, nil)
|
||||
if err == nil {
|
||||
t.Fatal("expected validation error for invalid time-range")
|
||||
t.Fatal("expected validation error for invalid --start")
|
||||
}
|
||||
if !strings.Contains(err.Error(), "start~end") {
|
||||
if !strings.Contains(err.Error(), "--start") {
|
||||
t.Errorf("unexpected error: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSearchEvent_Validation_TimeRangeStartAfterEnd(t *testing.T) {
|
||||
f, _, _, _ := cmdutil.TestFactory(t, calDefaultConfig())
|
||||
err := calMountAndRun(t, CalendarSearchEvent, []string{"+search-event", "--time-range", "2026-04-27~2026-04-20", "--as", "user"}, f, nil)
|
||||
err := calMountAndRun(t, CalendarSearchEvent, []string{"+search-event", "--start", "2026-04-27", "--end", "2026-04-20", "--as", "user"}, f, nil)
|
||||
if err == nil {
|
||||
t.Fatal("expected validation error for start after end")
|
||||
}
|
||||
@@ -367,21 +367,27 @@ func TestSearchEvent_Execute_Empty(t *testing.T) {
|
||||
func TestParseSearchEventTimeRange(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
input string
|
||||
start string
|
||||
end string
|
||||
wantErr bool
|
||||
}{
|
||||
{"empty", "", false},
|
||||
{"valid", "2026-04-20~2026-04-27", false},
|
||||
{"no tilde", "2026-04-20", true},
|
||||
{"empty parts", "~", true},
|
||||
{"start after end", "2026-04-27~2026-04-20", true},
|
||||
{"empty", "", "", false},
|
||||
{"valid", "2026-04-20", "2026-04-27", false},
|
||||
{"start only defaults end", "2026-04-20", "", false},
|
||||
{"end only defaults start", "", "2026-04-27", false},
|
||||
{"invalid start format", "not-a-date", "2026-04-27", true},
|
||||
{"start after end", "2026-04-27", "2026-04-20", true},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
cmd := &cobra.Command{Use: "test"}
|
||||
cmd.Flags().String("time-range", "", "")
|
||||
if tt.input != "" {
|
||||
_ = cmd.Flags().Set("time-range", tt.input)
|
||||
cmd.Flags().String("start", "", "")
|
||||
cmd.Flags().String("end", "", "")
|
||||
if tt.start != "" {
|
||||
_ = cmd.Flags().Set("start", tt.start)
|
||||
}
|
||||
if tt.end != "" {
|
||||
_ = cmd.Flags().Set("end", tt.end)
|
||||
}
|
||||
runtime := common.TestNewRuntimeContext(cmd, calDefaultConfig())
|
||||
_, _, err := parseSearchEventTimeRange(runtime)
|
||||
@@ -390,6 +396,42 @@ func TestParseSearchEventTimeRange(t *testing.T) {
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
t.Run("start only fills end with end-of-day", func(t *testing.T) {
|
||||
cmd := &cobra.Command{Use: "test"}
|
||||
cmd.Flags().String("start", "", "")
|
||||
cmd.Flags().String("end", "", "")
|
||||
_ = cmd.Flags().Set("start", "2026-04-20")
|
||||
runtime := common.TestNewRuntimeContext(cmd, calDefaultConfig())
|
||||
startRFC, endRFC, err := parseSearchEventTimeRange(runtime)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
if !strings.HasPrefix(startRFC, "2026-04-20T00:00:00") {
|
||||
t.Errorf("start = %s, want 2026-04-20T00:00:00...", startRFC)
|
||||
}
|
||||
if !strings.HasPrefix(endRFC, "2026-04-20T23:59:59") {
|
||||
t.Errorf("end = %s, want 2026-04-20T23:59:59...", endRFC)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("end only fills start with start-of-day", func(t *testing.T) {
|
||||
cmd := &cobra.Command{Use: "test"}
|
||||
cmd.Flags().String("start", "", "")
|
||||
cmd.Flags().String("end", "", "")
|
||||
_ = cmd.Flags().Set("end", "2026-04-27")
|
||||
runtime := common.TestNewRuntimeContext(cmd, calDefaultConfig())
|
||||
startRFC, endRFC, err := parseSearchEventTimeRange(runtime)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
if !strings.HasPrefix(startRFC, "2026-04-27T00:00:00") {
|
||||
t.Errorf("start = %s, want 2026-04-27T00:00:00...", startRFC)
|
||||
}
|
||||
if !strings.HasPrefix(endRFC, "2026-04-27T23:59:59") {
|
||||
t.Errorf("end = %s, want 2026-04-27T23:59:59...", endRFC)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func TestBuildSearchEventFilter(t *testing.T) {
|
||||
|
||||
@@ -66,7 +66,8 @@ type roomFindSlot struct {
|
||||
type roomFindTimeSlot struct {
|
||||
Start string `json:"start,omitempty"`
|
||||
End string `json:"end,omitempty"`
|
||||
MeetingRooms []*roomFindSuggestion `json:"meeting_rooms,omitempty"`
|
||||
MeetingRooms []*roomFindSuggestion `json:"meeting_rooms"`
|
||||
Hint string `json:"hint,omitempty"`
|
||||
}
|
||||
|
||||
type roomFindOutput struct {
|
||||
@@ -103,11 +104,18 @@ func collectRoomFindResults(slots []roomFindSlot, limit int, fetch func(roomFind
|
||||
}
|
||||
return
|
||||
}
|
||||
out.TimeSlots = append(out.TimeSlots, &roomFindTimeSlot{
|
||||
if suggestions == nil {
|
||||
suggestions = []*roomFindSuggestion{}
|
||||
}
|
||||
ts := &roomFindTimeSlot{
|
||||
Start: slot.Start,
|
||||
End: slot.End,
|
||||
MeetingRooms: suggestions,
|
||||
})
|
||||
}
|
||||
if len(suggestions) == 0 {
|
||||
ts.Hint = "no meeting room matches the current filters for this slot"
|
||||
}
|
||||
out.TimeSlots = append(out.TimeSlots, ts)
|
||||
}(slot)
|
||||
}
|
||||
wg.Wait()
|
||||
@@ -374,6 +382,10 @@ var CalendarRoomFind = common.Shortcut{
|
||||
}
|
||||
for _, slot := range out.TimeSlots {
|
||||
fmt.Fprintf(w, "%s - %s\n", slot.Start, slot.End)
|
||||
if len(slot.MeetingRooms) == 0 {
|
||||
fmt.Fprintf(w, "0 meeting room(s) found: %s\n", slot.Hint)
|
||||
continue
|
||||
}
|
||||
var rows []map[string]interface{}
|
||||
for _, room := range slot.MeetingRooms {
|
||||
rows = append(rows, map[string]interface{}{
|
||||
@@ -384,6 +396,7 @@ var CalendarRoomFind = common.Shortcut{
|
||||
})
|
||||
}
|
||||
output.PrintTable(w, rows)
|
||||
fmt.Fprintf(w, "%d meeting room(s) found\n", len(slot.MeetingRooms))
|
||||
fmt.Fprintln(w)
|
||||
}
|
||||
})
|
||||
|
||||
@@ -4,6 +4,8 @@
|
||||
package calendar
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
@@ -82,3 +84,60 @@ func TestCollectRoomFindResults_LimitsConcurrency(t *testing.T) {
|
||||
t.Fatalf("expected %d time slots, got %d", len(slots), len(out.TimeSlots))
|
||||
}
|
||||
}
|
||||
|
||||
func TestCollectRoomFindResults_EmptySlotEmitsHintAndArray(t *testing.T) {
|
||||
slots := []roomFindSlot{
|
||||
{Start: "2026-03-27T14:00:00+08:00", End: "2026-03-27T15:00:00+08:00"},
|
||||
{Start: "2026-03-27T15:00:00+08:00", End: "2026-03-27T16:00:00+08:00"},
|
||||
}
|
||||
|
||||
out, err := collectRoomFindResults(slots, 2, func(slot roomFindSlot) ([]*roomFindSuggestion, error) {
|
||||
if strings.HasPrefix(slot.Start, "2026-03-27T14") {
|
||||
return []*roomFindSuggestion{{RoomID: "rm_1", RoomName: "Room A"}}, nil
|
||||
}
|
||||
return nil, nil
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("collectRoomFindResults returned error: %v", err)
|
||||
}
|
||||
if len(out.TimeSlots) != 2 {
|
||||
t.Fatalf("expected 2 time slots, got %d", len(out.TimeSlots))
|
||||
}
|
||||
|
||||
for _, ts := range out.TimeSlots {
|
||||
if ts.MeetingRooms == nil {
|
||||
t.Fatalf("meeting_rooms should be non-nil for slot %s", ts.Start)
|
||||
}
|
||||
switch {
|
||||
case strings.HasPrefix(ts.Start, "2026-03-27T14"):
|
||||
if len(ts.MeetingRooms) != 1 {
|
||||
t.Fatalf("expected 1 room for first slot, got %d", len(ts.MeetingRooms))
|
||||
}
|
||||
if ts.Hint != "" {
|
||||
t.Fatalf("non-empty slot should not carry hint, got %q", ts.Hint)
|
||||
}
|
||||
case strings.HasPrefix(ts.Start, "2026-03-27T15"):
|
||||
if len(ts.MeetingRooms) != 0 {
|
||||
t.Fatalf("expected 0 rooms for empty slot, got %d", len(ts.MeetingRooms))
|
||||
}
|
||||
if ts.Hint == "" {
|
||||
t.Fatal("empty slot should carry a hint explaining the filters")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
emptySlot := out.TimeSlots[0]
|
||||
if !strings.HasPrefix(emptySlot.Start, "2026-03-27T15") {
|
||||
emptySlot = out.TimeSlots[1]
|
||||
}
|
||||
raw, err := json.Marshal(emptySlot)
|
||||
if err != nil {
|
||||
t.Fatalf("marshal empty slot: %v", err)
|
||||
}
|
||||
if !strings.Contains(string(raw), `"meeting_rooms":[]`) {
|
||||
t.Fatalf("expected meeting_rooms:[] in JSON, got %s", raw)
|
||||
}
|
||||
if !strings.Contains(string(raw), `"hint"`) {
|
||||
t.Fatalf("expected hint field in JSON, got %s", raw)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -78,40 +78,47 @@ type searchEventOutput struct {
|
||||
PageToken string `json:"page_token"`
|
||||
}
|
||||
|
||||
// parseSearchEventTimeRange parses --time-range (start~end format) into RFC3339 strings.
|
||||
// parseSearchEventTimeRange parses --start / --end into RFC3339 strings.
|
||||
// When only one side is provided, the other defaults to the same day's
|
||||
// boundary (start → end-of-day, end → start-of-day).
|
||||
func parseSearchEventTimeRange(runtime *common.RuntimeContext) (string, string, error) {
|
||||
tr := strings.TrimSpace(runtime.Str("time-range"))
|
||||
if tr == "" {
|
||||
startInput := strings.TrimSpace(runtime.Str("start"))
|
||||
endInput := strings.TrimSpace(runtime.Str("end"))
|
||||
if startInput == "" && endInput == "" {
|
||||
return "", "", nil
|
||||
}
|
||||
parts := strings.SplitN(tr, "~", 2)
|
||||
if len(parts) != 2 {
|
||||
return "", "", errs.NewValidationError(errs.SubtypeInvalidArgument, "--time-range: expected start~end format (e.g. 2026-04-20~2026-04-27)").WithParam("--time-range")
|
||||
|
||||
var startSec, endSec int64
|
||||
|
||||
if startInput != "" {
|
||||
ts, err := common.ParseTime(startInput)
|
||||
if err != nil {
|
||||
return "", "", errs.NewValidationError(errs.SubtypeInvalidArgument, "--start: %v", err).WithParam("--start")
|
||||
}
|
||||
startSec, _ = strconv.ParseInt(ts, 10, 64)
|
||||
}
|
||||
startInput := strings.TrimSpace(parts[0])
|
||||
endInput := strings.TrimSpace(parts[1])
|
||||
if startInput == "" || endInput == "" {
|
||||
return "", "", errs.NewValidationError(errs.SubtypeInvalidArgument, "--time-range: both start and end must be provided").WithParam("--time-range")
|
||||
if endInput != "" {
|
||||
ts, err := common.ParseTime(endInput, "end")
|
||||
if err != nil {
|
||||
return "", "", errs.NewValidationError(errs.SubtypeInvalidArgument, "--end: %v", err).WithParam("--end")
|
||||
}
|
||||
endSec, _ = strconv.ParseInt(ts, 10, 64)
|
||||
}
|
||||
|
||||
startTs, err := common.ParseTime(startInput)
|
||||
if err != nil {
|
||||
return "", "", errs.NewValidationError(errs.SubtypeInvalidArgument, "--time-range start: %v", err).WithParam("--time-range")
|
||||
if startInput == "" {
|
||||
t := time.Unix(endSec, 0).In(time.Local)
|
||||
startSec = time.Date(t.Year(), t.Month(), t.Day(), 0, 0, 0, 0, t.Location()).Unix()
|
||||
}
|
||||
endTs, err := common.ParseTime(endInput, "end")
|
||||
if err != nil {
|
||||
return "", "", errs.NewValidationError(errs.SubtypeInvalidArgument, "--time-range end: %v", err).WithParam("--time-range")
|
||||
if endInput == "" {
|
||||
t := time.Unix(startSec, 0).In(time.Local)
|
||||
endSec = time.Date(t.Year(), t.Month(), t.Day(), 23, 59, 59, 0, t.Location()).Unix()
|
||||
}
|
||||
|
||||
startSec, _ := strconv.ParseInt(startTs, 10, 64)
|
||||
endSec, _ := strconv.ParseInt(endTs, 10, 64)
|
||||
if startSec > endSec {
|
||||
return "", "", errs.NewValidationError(errs.SubtypeInvalidArgument, "--time-range: start must be before end").WithParam("--time-range")
|
||||
return "", "", errs.NewValidationError(errs.SubtypeInvalidArgument, "--start must be before --end").WithParam("--start")
|
||||
}
|
||||
|
||||
startRFC3339 := time.Unix(startSec, 0).Format(time.RFC3339)
|
||||
endRFC3339 := time.Unix(endSec, 0).Format(time.RFC3339)
|
||||
return startRFC3339, endRFC3339, nil
|
||||
return time.Unix(startSec, 0).Format(time.RFC3339), time.Unix(endSec, 0).Format(time.RFC3339), nil
|
||||
}
|
||||
|
||||
// buildSearchEventFilter builds the filter object for the search_event API.
|
||||
@@ -182,7 +189,8 @@ var CalendarSearchEvent = common.Shortcut{
|
||||
{Name: "calendar-id", Desc: "calendar ID (default: primary)"},
|
||||
{Name: "query", Desc: "search keyword"},
|
||||
{Name: "attendee-ids", Desc: "attendee IDs, comma-separated (supports user ou_, chat oc_, room omm_)"},
|
||||
{Name: "time-range", Desc: "search time range in start~end format (e.g. 2026-04-20~2026-04-27)"},
|
||||
{Name: "start", Desc: "search time range start (ISO 8601 or YYYY-MM-DD)"},
|
||||
{Name: "end", Desc: "search time range end (ISO 8601 or YYYY-MM-DD)"},
|
||||
{Name: "page-token", Desc: "page token for next page"},
|
||||
{Name: "page-size", Default: "20", Desc: "page size, 1-30 (default 20)"},
|
||||
},
|
||||
|
||||
@@ -40,7 +40,8 @@ var scopesDetailMinuteTokens = []string{
|
||||
// minuteDetailItem represents a single minute detail result.
|
||||
type minuteDetailItem struct {
|
||||
MinuteToken string `json:"minute_token"`
|
||||
Title string `json:"title,omitempty"`
|
||||
Title string `json:"title"`
|
||||
NoteID string `json:"note_id"`
|
||||
Artifacts map[string]any `json:"artifacts,omitempty"`
|
||||
Error string `json:"error,omitempty"`
|
||||
}
|
||||
@@ -68,6 +69,9 @@ func fetchMinuteDetail(ctx context.Context, runtime *common.RuntimeContext, minu
|
||||
if v, ok := minute["title"].(string); ok && v != "" {
|
||||
result.Title = v
|
||||
}
|
||||
if v, ok := minute["note_id"].(string); ok && v != "" {
|
||||
result.NoteID = v
|
||||
}
|
||||
|
||||
// Fetch artifacts selectively based on flags
|
||||
needSummary := runtime.Bool("summary")
|
||||
@@ -247,9 +251,8 @@ var MinutesDetail = common.Shortcut{
|
||||
row["error"] = r.Error
|
||||
} else {
|
||||
row["status"] = "OK"
|
||||
if r.Title != "" {
|
||||
row["title"] = r.Title
|
||||
}
|
||||
row["title"] = r.Title
|
||||
row["note_id"] = r.NoteID
|
||||
if len(r.Artifacts) > 0 {
|
||||
var parts []string
|
||||
if _, ok := r.Artifacts["summary"]; ok {
|
||||
|
||||
@@ -219,6 +219,9 @@ func TestDetail_Execute_BasicInfo(t *testing.T) {
|
||||
if m["title"] != "Test Meeting" {
|
||||
t.Errorf("title = %v, want Test Meeting", m["title"])
|
||||
}
|
||||
if _, ok := m["note_id"]; ok {
|
||||
t.Errorf("note_id should be omitted when minute has no note_id, got %v", m["note_id"])
|
||||
}
|
||||
}
|
||||
|
||||
func TestDetail_Execute_WithSummaryAndTodo(t *testing.T) {
|
||||
@@ -243,6 +246,9 @@ func TestDetail_Execute_WithSummaryAndTodo(t *testing.T) {
|
||||
t.Fatalf("expected 1 minute, got %d", len(minutes))
|
||||
}
|
||||
m, _ := minutes[0].(map[string]any)
|
||||
if m["note_id"] != "note_art" {
|
||||
t.Errorf("note_id = %v, want note_art", m["note_id"])
|
||||
}
|
||||
arts, _ := m["artifacts"].(map[string]any)
|
||||
if arts == nil {
|
||||
t.Fatal("expected artifacts to be present")
|
||||
|
||||
@@ -184,12 +184,6 @@ func minuteSearchAppLink(item map[string]interface{}) string {
|
||||
return common.GetString(meta, "app_link")
|
||||
}
|
||||
|
||||
// minuteSearchAvatar extracts the avatar URL from a search result item.
|
||||
func minuteSearchAvatar(item map[string]interface{}) string {
|
||||
meta := common.GetMap(item, "meta_data")
|
||||
return common.GetString(meta, "avatar")
|
||||
}
|
||||
|
||||
// buildMinuteSearchRows converts API items into pretty output rows.
|
||||
func buildMinuteSearchRows(items []interface{}) []map[string]interface{} {
|
||||
rows := make([]map[string]interface{}, 0, len(items))
|
||||
@@ -203,12 +197,27 @@ func buildMinuteSearchRows(items []interface{}) []map[string]interface{} {
|
||||
"display_info": common.TruncateStr(minuteSearchDisplayInfo(item), 40),
|
||||
"description": common.TruncateStr(minuteSearchDescription(item), 40),
|
||||
"app_link": common.TruncateStr(minuteSearchAppLink(item), 80),
|
||||
"avatar": common.TruncateStr(minuteSearchAvatar(item), 80),
|
||||
})
|
||||
}
|
||||
return rows
|
||||
}
|
||||
|
||||
// stripAvatarFromItems removes meta_data.avatar from each search item in place
|
||||
// so the structured output does not surface avatars to AI agents.
|
||||
func stripAvatarFromItems(items []interface{}) {
|
||||
for _, raw := range items {
|
||||
item, _ := raw.(map[string]interface{})
|
||||
if item == nil {
|
||||
continue
|
||||
}
|
||||
meta, _ := item["meta_data"].(map[string]interface{})
|
||||
if meta == nil {
|
||||
continue
|
||||
}
|
||||
delete(meta, "avatar")
|
||||
}
|
||||
}
|
||||
|
||||
// MinutesSearch searches minutes by keyword, owners, participants, and time range.
|
||||
var MinutesSearch = common.Shortcut{
|
||||
Service: "minutes",
|
||||
@@ -298,13 +307,13 @@ var MinutesSearch = common.Shortcut{
|
||||
}
|
||||
|
||||
items := minuteSearchItems(data)
|
||||
stripAvatarFromItems(items)
|
||||
hasMore, _ := data["has_more"].(bool)
|
||||
pageToken, _ := data["page_token"].(string)
|
||||
rows := buildMinuteSearchRows(items)
|
||||
|
||||
outData := map[string]interface{}{
|
||||
"items": items,
|
||||
"total": data["total"],
|
||||
"has_more": data["has_more"],
|
||||
"page_token": data["page_token"],
|
||||
}
|
||||
|
||||
@@ -526,7 +526,7 @@ func TestMinutesSearchExecuteRendersRowsAndMoreHint(t *testing.T) {
|
||||
}
|
||||
|
||||
out := stdout.String()
|
||||
for _, want := range []string{"minute_1", "周会摘要", "周会纪要", "https://meetings.feishu.cn/minutes/obcn123", "https://p3-lark-file.byteimg.com/img/xxxx.jpg", "next_token", "more available"} {
|
||||
for _, want := range []string{"minute_1", "周会摘要", "周会纪要", "https://meetings.feishu.cn/minutes/obcn123", "next_token", "more available"} {
|
||||
if !strings.Contains(out, want) {
|
||||
t.Fatalf("output missing %q, got: %s", want, out)
|
||||
}
|
||||
@@ -663,7 +663,6 @@ func TestMinuteSearchFieldExtractors(t *testing.T) {
|
||||
"meta_data": map[string]interface{}{
|
||||
"description": "周会纪要",
|
||||
"app_link": "https://meetings.feishu.cn/minutes/obcn123",
|
||||
"avatar": "https://p3-lark-file.byteimg.com/img/xxxx.jpg",
|
||||
},
|
||||
}
|
||||
|
||||
@@ -679,9 +678,6 @@ func TestMinuteSearchFieldExtractors(t *testing.T) {
|
||||
if got := minuteSearchAppLink(item); got != "https://meetings.feishu.cn/minutes/obcn123" {
|
||||
t.Fatalf("minuteSearchAppLink() = %q", got)
|
||||
}
|
||||
if got := minuteSearchAvatar(item); got != "https://p3-lark-file.byteimg.com/img/xxxx.jpg" {
|
||||
t.Fatalf("minuteSearchAvatar() = %q", got)
|
||||
}
|
||||
}
|
||||
|
||||
// TestMinuteSearchFieldExtractorsFallbacks verifies extractors keep working for alternate sample data.
|
||||
@@ -694,7 +690,6 @@ func TestMinuteSearchFieldExtractorsFallbacks(t *testing.T) {
|
||||
"meta_data": map[string]interface{}{
|
||||
"description": "回退纪要",
|
||||
"app_link": "https://meetings.feishu.cn/minutes/fallback",
|
||||
"avatar": "https://p3-lark-file.byteimg.com/img/fallback.jpg",
|
||||
},
|
||||
}
|
||||
|
||||
@@ -707,9 +702,6 @@ func TestMinuteSearchFieldExtractorsFallbacks(t *testing.T) {
|
||||
if got := minuteSearchAppLink(item); got != "https://meetings.feishu.cn/minutes/fallback" {
|
||||
t.Fatalf("minuteSearchAppLink() = %q", got)
|
||||
}
|
||||
if got := minuteSearchAvatar(item); got != "https://p3-lark-file.byteimg.com/img/fallback.jpg" {
|
||||
t.Fatalf("minuteSearchAvatar() = %q", got)
|
||||
}
|
||||
}
|
||||
|
||||
// TestMinuteSearchFieldExtractorsMissingMetaData verifies extractors fall back to empty values without metadata.
|
||||
@@ -730,7 +722,32 @@ func TestMinuteSearchFieldExtractorsMissingMetaData(t *testing.T) {
|
||||
if got := minuteSearchAppLink(item); got != "" {
|
||||
t.Fatalf("minuteSearchAppLink() = %q, want empty", got)
|
||||
}
|
||||
if got := minuteSearchAvatar(item); got != "" {
|
||||
t.Fatalf("minuteSearchAvatar() = %q, want empty", got)
|
||||
}
|
||||
|
||||
// TestStripAvatarFromItems verifies the avatar field is removed from items in place.
|
||||
func TestStripAvatarFromItems(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
items := []interface{}{
|
||||
map[string]interface{}{
|
||||
"token": "minute_1",
|
||||
"meta_data": map[string]interface{}{
|
||||
"description": "周会纪要",
|
||||
"avatar": "https://p3-lark-file.byteimg.com/img/xxxx.jpg",
|
||||
},
|
||||
},
|
||||
nil,
|
||||
map[string]interface{}{"token": "minute_no_meta"},
|
||||
}
|
||||
|
||||
stripAvatarFromItems(items)
|
||||
|
||||
first, _ := items[0].(map[string]interface{})
|
||||
meta, _ := first["meta_data"].(map[string]interface{})
|
||||
if _, ok := meta["avatar"]; ok {
|
||||
t.Fatalf("avatar should be stripped, got meta = %v", meta)
|
||||
}
|
||||
if meta["description"] != "周会纪要" {
|
||||
t.Fatalf("description should be preserved, got %v", meta["description"])
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,322 +0,0 @@
|
||||
// Copyright (c) 2026 Lark Technologies Pte. Ltd.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package note
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"strings"
|
||||
"sync"
|
||||
"testing"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"github.com/larksuite/cli/internal/cmdutil"
|
||||
"github.com/larksuite/cli/internal/core"
|
||||
"github.com/larksuite/cli/internal/httpmock"
|
||||
"github.com/larksuite/cli/internal/output"
|
||||
"github.com/larksuite/cli/shortcuts/common"
|
||||
)
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// helpers
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
var noteWarmOnce sync.Once
|
||||
|
||||
func noteWarmTokenCache(t *testing.T) {
|
||||
t.Helper()
|
||||
noteWarmOnce.Do(func() {
|
||||
f, _, _, reg := cmdutil.TestFactory(t, noteDefaultConfig())
|
||||
reg.Register(&httpmock.Stub{
|
||||
URL: "/open-apis/test/v1/warm",
|
||||
Body: map[string]interface{}{"code": 0, "msg": "ok", "data": map[string]interface{}{}},
|
||||
})
|
||||
s := common.Shortcut{
|
||||
Service: "test",
|
||||
Command: "+warm",
|
||||
AuthTypes: []string{"bot"},
|
||||
Execute: func(_ context.Context, rctx *common.RuntimeContext) error {
|
||||
_, err := rctx.CallAPI("GET", "/open-apis/test/v1/warm", nil, nil)
|
||||
return err
|
||||
},
|
||||
}
|
||||
parent := &cobra.Command{Use: "test"}
|
||||
s.Mount(parent, f)
|
||||
parent.SetArgs([]string{"+warm"})
|
||||
parent.SilenceErrors = true
|
||||
parent.SilenceUsage = true
|
||||
parent.Execute()
|
||||
})
|
||||
}
|
||||
|
||||
func noteDefaultConfig() *core.CliConfig {
|
||||
return &core.CliConfig{
|
||||
AppID: "test-app", AppSecret: "test-secret", Brand: core.BrandFeishu,
|
||||
UserOpenId: "ou_testuser",
|
||||
}
|
||||
}
|
||||
|
||||
func noteMountAndRun(t *testing.T, s common.Shortcut, args []string, f *cmdutil.Factory, stdout *bytes.Buffer) error {
|
||||
t.Helper()
|
||||
noteWarmTokenCache(t)
|
||||
parent := &cobra.Command{Use: "note"}
|
||||
s.Mount(parent, f)
|
||||
parent.SetArgs(args)
|
||||
parent.SilenceErrors = true
|
||||
parent.SilenceUsage = true
|
||||
if stdout != nil {
|
||||
stdout.Reset()
|
||||
}
|
||||
return parent.Execute()
|
||||
}
|
||||
|
||||
func noteBotExec(t *testing.T, name string, f *cmdutil.Factory, fn func(context.Context, *common.RuntimeContext) error) error {
|
||||
t.Helper()
|
||||
noteWarmTokenCache(t)
|
||||
s := common.Shortcut{
|
||||
Service: "test",
|
||||
Command: "+" + name,
|
||||
AuthTypes: []string{"bot"},
|
||||
HasFormat: true,
|
||||
Execute: fn,
|
||||
}
|
||||
parent := &cobra.Command{Use: "note"}
|
||||
s.Mount(parent, f)
|
||||
parent.SetArgs([]string{"+" + name, "--format", "json"})
|
||||
parent.SilenceErrors = true
|
||||
parent.SilenceUsage = true
|
||||
return parent.Execute()
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Validation tests
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
func TestNoteDetail_Validation_MissingNoteID(t *testing.T) {
|
||||
f, _, _, _ := cmdutil.TestFactory(t, noteDefaultConfig())
|
||||
err := noteMountAndRun(t, NoteDetail, []string{"+detail", "--as", "user"}, f, nil)
|
||||
if err == nil {
|
||||
t.Fatal("expected validation error for missing --note-id")
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// DryRun tests
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
func TestNoteDetail_DryRun(t *testing.T) {
|
||||
f, stdout, _, _ := cmdutil.TestFactory(t, noteDefaultConfig())
|
||||
err := noteMountAndRun(t, NoteDetail, []string{"+detail", "--note-id", "note001", "--dry-run", "--as", "user"}, f, stdout)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
if !strings.Contains(stdout.String(), "/open-apis/vc/v1/notes/") {
|
||||
t.Errorf("dry-run should show notes API path, got: %s", stdout.String())
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Execute tests with mocked HTTP
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
func noteDetailStub(noteID string) *httpmock.Stub {
|
||||
return &httpmock.Stub{
|
||||
Method: "GET",
|
||||
URL: "/open-apis/vc/v1/notes/" + noteID,
|
||||
Body: map[string]interface{}{
|
||||
"code": 0, "msg": "ok",
|
||||
"data": map[string]interface{}{
|
||||
"note": map[string]interface{}{
|
||||
"creator_id": "ou_creator",
|
||||
"create_time": "1700000000",
|
||||
"artifacts": []interface{}{
|
||||
map[string]interface{}{"doc_token": "doc_main", "artifact_type": 1},
|
||||
map[string]interface{}{"doc_token": "doc_verbatim", "artifact_type": 2},
|
||||
},
|
||||
"references": []interface{}{
|
||||
map[string]interface{}{"doc_token": "doc_shared1"},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func TestNoteDetail_Execute_Success(t *testing.T) {
|
||||
f, stdout, _, reg := cmdutil.TestFactory(t, noteDefaultConfig())
|
||||
reg.Register(noteDetailStub("note_exec1"))
|
||||
|
||||
err := noteMountAndRun(t, NoteDetail, []string{"+detail", "--note-id", "note_exec1", "--as", "user"}, f, stdout)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
|
||||
var resp map[string]any
|
||||
if err := json.Unmarshal(stdout.Bytes(), &resp); err != nil {
|
||||
t.Fatalf("failed to parse output: %v", err)
|
||||
}
|
||||
data, _ := resp["data"].(map[string]any)
|
||||
notes, _ := data["notes"].(map[string]any)
|
||||
if notes == nil {
|
||||
t.Fatal("expected notes object in data")
|
||||
}
|
||||
if notes["note_id"] != "note_exec1" {
|
||||
t.Errorf("note_id = %v, want note_exec1", notes["note_id"])
|
||||
}
|
||||
if notes["note_doc_token"] != "doc_main" {
|
||||
t.Errorf("note_doc_token = %v, want doc_main", notes["note_doc_token"])
|
||||
}
|
||||
if notes["verbatim_doc_token"] != "doc_verbatim" {
|
||||
t.Errorf("verbatim_doc_token = %v, want doc_verbatim", notes["verbatim_doc_token"])
|
||||
}
|
||||
}
|
||||
|
||||
func TestNoteDetail_Execute_NoPermission(t *testing.T) {
|
||||
f, stdout, _, reg := cmdutil.TestFactory(t, noteDefaultConfig())
|
||||
reg.Register(&httpmock.Stub{
|
||||
Method: "GET",
|
||||
URL: "/open-apis/vc/v1/notes/note_noperm",
|
||||
Body: map[string]interface{}{"code": 121005, "msg": "no permission"},
|
||||
})
|
||||
|
||||
err := noteMountAndRun(t, NoteDetail, []string{"+detail", "--note-id", "note_noperm", "--as", "user"}, f, stdout)
|
||||
if err == nil {
|
||||
t.Fatal("expected partial failure error")
|
||||
}
|
||||
var pfErr *output.PartialFailureError
|
||||
if !errors.As(err, &pfErr) {
|
||||
t.Fatalf("expected *output.PartialFailureError, got %T: %v", err, err)
|
||||
}
|
||||
if pfErr.Code != output.ExitAPI {
|
||||
t.Errorf("Code = %d, want ExitAPI", pfErr.Code)
|
||||
}
|
||||
}
|
||||
|
||||
func TestNoteDetail_Execute_NotFound(t *testing.T) {
|
||||
f, stdout, _, reg := cmdutil.TestFactory(t, noteDefaultConfig())
|
||||
reg.Register(&httpmock.Stub{
|
||||
Method: "GET",
|
||||
URL: "/open-apis/vc/v1/notes/note_nf",
|
||||
Body: map[string]interface{}{"code": 121004, "msg": "not found"},
|
||||
})
|
||||
|
||||
err := noteMountAndRun(t, NoteDetail, []string{"+detail", "--note-id", "note_nf", "--as", "user"}, f, stdout)
|
||||
if err == nil {
|
||||
t.Fatal("expected partial failure error")
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Pure function tests
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
func TestParseArtifactType(t *testing.T) {
|
||||
tests := []struct {
|
||||
input any
|
||||
want int
|
||||
}{
|
||||
{float64(1), 1},
|
||||
{float64(2), 2},
|
||||
{"unknown", 0},
|
||||
{nil, 0},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
got := parseArtifactType(tt.input)
|
||||
if got != tt.want {
|
||||
t.Errorf("parseArtifactType(%v) = %d, want %d", tt.input, got, tt.want)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestExtractArtifactTokens(t *testing.T) {
|
||||
artifacts := []any{
|
||||
map[string]any{"doc_token": "main_doc", "artifact_type": float64(1)},
|
||||
map[string]any{"doc_token": "verbatim_doc", "artifact_type": float64(2)},
|
||||
map[string]any{"doc_token": "unknown_doc", "artifact_type": float64(99)},
|
||||
nil,
|
||||
}
|
||||
noteDoc, verbatimDoc := extractArtifactTokens(artifacts)
|
||||
if noteDoc != "main_doc" {
|
||||
t.Errorf("noteDoc = %q, want %q", noteDoc, "main_doc")
|
||||
}
|
||||
if verbatimDoc != "verbatim_doc" {
|
||||
t.Errorf("verbatimDoc = %q, want %q", verbatimDoc, "verbatim_doc")
|
||||
}
|
||||
}
|
||||
|
||||
func TestExtractDocTokens(t *testing.T) {
|
||||
refs := []any{
|
||||
map[string]any{"doc_token": "shared1"},
|
||||
map[string]any{"doc_token": "shared2"},
|
||||
map[string]any{"doc_token": ""},
|
||||
map[string]any{},
|
||||
nil,
|
||||
}
|
||||
tokens := extractDocTokens(refs)
|
||||
if len(tokens) != 2 || tokens[0] != "shared1" || tokens[1] != "shared2" {
|
||||
t.Errorf("extractDocTokens = %v, want [shared1 shared2]", tokens)
|
||||
}
|
||||
}
|
||||
|
||||
func TestExtractDocTokens_Empty(t *testing.T) {
|
||||
tokens := extractDocTokens(nil)
|
||||
if len(tokens) != 0 {
|
||||
t.Errorf("expected empty slice for nil input, got %v", tokens)
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// fetchNoteDetail via botExec
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
func TestFetchNoteDetail_Success(t *testing.T) {
|
||||
t.Setenv("LARKSUITE_CLI_CONFIG_DIR", t.TempDir())
|
||||
f, _, _, reg := cmdutil.TestFactory(t, noteDefaultConfig())
|
||||
reg.Register(noteDetailStub("note_fn"))
|
||||
|
||||
if err := noteBotExec(t, "detail-fn", f, func(_ context.Context, rctx *common.RuntimeContext) error {
|
||||
result := fetchNoteDetail(context.Background(), rctx, "note_fn")
|
||||
if result.NoteID != "note_fn" {
|
||||
t.Errorf("note_id = %v, want note_fn", result.NoteID)
|
||||
}
|
||||
if result.NoteDocToken != "doc_main" {
|
||||
t.Errorf("note_doc_token = %v, want doc_main", result.NoteDocToken)
|
||||
}
|
||||
if result.VerbatimDocToken != "doc_verbatim" {
|
||||
t.Errorf("verbatim_doc_token = %v, want doc_verbatim", result.VerbatimDocToken)
|
||||
}
|
||||
if result.Error != "" {
|
||||
t.Errorf("unexpected error: %v", result.Error)
|
||||
}
|
||||
return nil
|
||||
}); err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestFetchNoteDetail_NoPermission(t *testing.T) {
|
||||
t.Setenv("LARKSUITE_CLI_CONFIG_DIR", t.TempDir())
|
||||
f, _, _, reg := cmdutil.TestFactory(t, noteDefaultConfig())
|
||||
reg.Register(&httpmock.Stub{
|
||||
Method: "GET",
|
||||
URL: "/open-apis/vc/v1/notes/note_perm",
|
||||
Body: map[string]interface{}{"code": 121005, "msg": "no permission"},
|
||||
})
|
||||
|
||||
if err := noteBotExec(t, "detail-perm", f, func(_ context.Context, rctx *common.RuntimeContext) error {
|
||||
result := fetchNoteDetail(context.Background(), rctx, "note_perm")
|
||||
if result.Error == "" {
|
||||
t.Error("expected error for no permission")
|
||||
}
|
||||
if !strings.Contains(result.Error, "no read permission") {
|
||||
t.Errorf("error = %q, want contains 'no read permission'", result.Error)
|
||||
}
|
||||
return nil
|
||||
}); err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
}
|
||||
@@ -67,7 +67,8 @@ lark-cli calendar +agenda --as user
|
||||
| 查询过去的会议("昨天的会议""上周的会") | [`../lark-vc/SKILL.md`](../lark-vc/SKILL.md)(会议数据含即时会议,仅查日程会遗漏) |
|
||||
| 查询日历/日程或未来时间的会议 | 本 skill |
|
||||
| 按关键词搜索日程 | 本 skill(`+search-event`) |
|
||||
| 从日程获取关联的会议 ID 或纪要文档 | 本 skill(`+meeting`) |
|
||||
| 从日程获取关联的视频会议 ID 或用户绑定的会议纪要文档 | 本 skill(`+meeting`) |
|
||||
| 从日程进一步拿 AI 智能纪要 / 逐字稿 / 妙记产物 | 先 `+meeting` 取 `meeting_id`,再 [`vc +detail`](../lark-vc/references/lark-vc-detail.md) → [`note +detail`](../lark-note/references/lark-note-detail.md) / [`minutes +detail`](../lark-minutes/references/lark-minutes-detail.md) |
|
||||
| 预约/改约日程、添加/移除参会人、添加/更换会议室、调整时间 | 先判断新建 vs 编辑,再进入 [schedule-meeting 工作流](references/lark-calendar-schedule-meeting.md) |
|
||||
|
||||
## 任务类型分流
|
||||
|
||||
@@ -1,66 +1,40 @@
|
||||
|
||||
# calendar +meeting
|
||||
|
||||
> **前置条件:** 先阅读 [`../lark-shared/SKILL.md`](../../lark-shared/SKILL.md) 了解认证、全局参数和安全规则。
|
||||
|
||||
通过日程 ID(event_id) 获取关联的视频会议信息,包括会议 ID(meeting_id) 和绑定的会议纪要文档(meeting_note)。只读操作。
|
||||
|
||||
本 skill 对应 shortcut:`lark-cli calendar +meeting`。
|
||||
通过日程 ID(`event_id`) 获取关联的视频会议信息(`meeting_id`、`meeting_note`)。只读。
|
||||
|
||||
## 命令
|
||||
|
||||
```bash
|
||||
# 查询单个日程的会议信息
|
||||
lark-cli calendar +meeting --event-ids <event_id>
|
||||
# 单个 / 批量(逗号分隔,最多 50 个)
|
||||
lark-cli calendar +meeting --event-ids <event_id1>,<event_id2>
|
||||
|
||||
# 指定日历 ID(默认使用主日历)
|
||||
lark-cli calendar +meeting --event-ids <event_id1> --calendar-id <calendar_id>
|
||||
# 默认使用主日历,需要时显式传 --calendar-id
|
||||
lark-cli calendar +meeting --event-ids <event_id> --calendar-id <calendar_id>
|
||||
```
|
||||
|
||||
## 参数
|
||||
|
||||
| 参数 | 必填 | 说明 |
|
||||
|------|------|------|
|
||||
| `--event-ids <ids>` | 是 | 日程事件实例 ID,逗号分隔支持批量,最多 50 个 |
|
||||
| `--calendar-id <id>` | 否 | 日历 ID,默认使用主日历("primary") |
|
||||
|
||||
## 输出结果
|
||||
|
||||
返回 `meetings` 数组,每条记录包含:
|
||||
## 输出字段
|
||||
|
||||
| 字段 | 说明 |
|
||||
|------|------|
|
||||
| `event_id` | 日程 ID |
|
||||
| `meeting_id` | 关联的视频会议 ID(如有)。 |
|
||||
| `meeting_note` | 关联的会议纪要文档 Token(如有)。 |
|
||||
| `meeting_id` | 关联的视频会议 ID |
|
||||
| `meeting_note` | 用户主动绑定到日程的纪要文档 Token(`MeetingNotes`,由用户在日程页手动添加;)。**与会中产生的 AI 智能纪要 `note_doc_token` 是两份不同文档**,要拿 AI 纪要请继续走 `vc +detail` → `note +detail`。 |
|
||||
|
||||
## 典型场景
|
||||
## 下游链路
|
||||
|
||||
### 场景 1:从日程获取会议信息
|
||||
`calendar +meeting` 只把日程 ID 翻译为 `meeting_id` / `meeting_note`,要拿会中产生的产物(AI 智能纪要、逐字稿、妙记)需继续调用:
|
||||
|
||||
```bash
|
||||
# 1. 查看日程安排,获取 event_id
|
||||
lark-cli calendar +agenda --start 2026-06-10 --end 2026-06-11
|
||||
|
||||
# 2. 获取日程关联的会议信息
|
||||
lark-cli calendar +meeting --event-ids <event_id>
|
||||
|
||||
# 3. 用 meeting_id 进一步获取会议详情
|
||||
# 1. meeting_id → note_id + minute_token(同一会议两份产物,可能各自为空)
|
||||
lark-cli vc +detail --meeting-ids <meeting_id>
|
||||
```
|
||||
|
||||
## 与其他命令的关系
|
||||
# 2a. note_id → 纪要文档 token(note_doc_token / verbatim_doc_token / shared_doc_tokens)
|
||||
lark-cli note +detail --note-id <note_id>
|
||||
|
||||
| 需求 | 推荐命令 |
|
||||
|------|---------|
|
||||
| 从日程获取会议 ID 和纪要文档 | `calendar +meeting` |
|
||||
| 通过会议 ID 获取 note_id 和 minute_token | `vc +detail --meeting-ids` |
|
||||
| 通过 note_id 获取 note_doc_token / verbatim_doc_token / shared_doc_tokens | `note +detail --note-id` |
|
||||
| 读取纪要 / 逐字稿 / 共享文档正文 | `docs +fetch --api-version v2 --doc <doc_token>` |
|
||||
| 获取妙记产物(需手动指定 `--summary` / `--todo` / `--chapter` / `--keyword` / `--transcript`,不传不返回) | `minutes +detail --minute-tokens` |
|
||||
# 2b. minute_token → 妙记 AI 产物(按需获取,不传不返回任何 AI 内容)
|
||||
lark-cli minutes +detail --minute-tokens <minute_token> --summary --todo --chapter --keyword --transcript
|
||||
|
||||
## 参考
|
||||
|
||||
- [lark-calendar](../SKILL.md) — 日历全部命令
|
||||
- [lark-vc](../../lark-vc/SKILL.md) — 视频会议(进一步获取会议详情)
|
||||
- [lark-shared](../../lark-shared/SKILL.md) — 认证和全局参数
|
||||
# 3. 任意文档 token(meeting_note / note_doc_token / verbatim_doc_token / shared_doc_token)→ 正文
|
||||
lark-cli docs +fetch --api-version v2 --doc <doc_token> --doc-format markdown
|
||||
```
|
||||
@@ -1,79 +1,29 @@
|
||||
|
||||
# calendar +search-event
|
||||
|
||||
> **前置条件:** 先阅读 [`../lark-shared/SKILL.md`](../../lark-shared/SKILL.md) 了解认证、全局参数和安全规则。
|
||||
|
||||
按关键词、时间范围和参会人搜索日历日程。只读操作。
|
||||
|
||||
本 skill 对应 shortcut:`lark-cli calendar +search-event`。
|
||||
按关键词、时间范围和参会人搜索日历日程。只读。
|
||||
|
||||
## 命令
|
||||
|
||||
```bash
|
||||
# 按关键词搜索
|
||||
# 按关键词
|
||||
lark-cli calendar +search-event --query "周会"
|
||||
|
||||
# 按时间范围过滤
|
||||
lark-cli calendar +search-event --time-range "2026-04-20~2026-04-27"
|
||||
# 按时间范围(ISO 8601 或 YYYY-MM-DD)
|
||||
lark-cli calendar +search-event --start "2026-04-20T00:00:00+08:00" --end "2026-04-27T23:59:59+08:00"
|
||||
|
||||
# 按参会人过滤(支持用户 ou_、群聊 oc_、会议室 omm_)
|
||||
# 按参会人(自动识别 ou_ 用户 / oc_ 群聊 / omm_ 会议室前缀)
|
||||
lark-cli calendar +search-event --attendee-ids "ou_user1,oc_chat1,omm_room1"
|
||||
|
||||
# 组合搜索
|
||||
lark-cli calendar +search-event --query "周会" --time-range "2026-04-20~2026-04-27" --attendee-ids "ou_user1"
|
||||
|
||||
# 指定日历 ID(默认使用主日历)
|
||||
lark-cli calendar +search-event --query "周会" --calendar-id <calendar_id>
|
||||
# 组合
|
||||
lark-cli calendar +search-event --query "周会" --start 2026-04-20 --end 2026-04-27 --attendee-ids "ou_user1"
|
||||
```
|
||||
|
||||
## 参数
|
||||
## 输出字段
|
||||
|
||||
| 参数 | 必填 | 说明 |
|
||||
|------|------|------|
|
||||
| `--calendar-id <id>` | 否 | 日历 ID,默认使用主日历 |
|
||||
| `--query <keyword>` | 否 | 搜索关键词。默认为空 |
|
||||
| `--attendee-ids <ids>` | 否 | 参会人 ID,逗号分隔。支持用户(`ou_`)、群聊(`oc_`)、会议室(`omm_`)前缀自动识别 |
|
||||
| `--time-range <range>` | 否 | 搜索时间范围(ISO 8601 时间),格式 `start~end`(如 `2026-04-20T00:00:00+08:00~2026-04-27T23:59:59+08:00`) |
|
||||
| `--page-token <token>` | 否 | 分页 Token,获取下一页 |
|
||||
| `--page-size <size>` | 否 | 每页条数,1-30,默认 20 |
|
||||
`items` 列表每条返回 `event_id` / `summary` / `start` / `end` / `is_all_day` / `app_link`;外层有 `has_more`、`page_token`。**仅返回基础字段,要拿日程详情用 `calendar events get`。**
|
||||
|
||||
> 开始时间必须早于结束时间,否则校验失败。
|
||||
## 注意事项
|
||||
|
||||
## 输出结果
|
||||
|
||||
| 字段 | 说明 |
|
||||
|------|------|
|
||||
| `calendar_id` | 搜索的日历 ID |
|
||||
| `items` | 日程列表 |
|
||||
| `has_more` | 是否有更多结果 |
|
||||
| `page_token` | 下一页 Token |
|
||||
|
||||
### items 中的字段
|
||||
|
||||
| 字段 | 说明 |
|
||||
|------|------|
|
||||
| `event_id` | 日程 ID |
|
||||
| `summary` | 日程主题 |
|
||||
| `start` | 开始时间 |
|
||||
| `end` | 结束时间 |
|
||||
| `is_all_day` | 是否全天日程 |
|
||||
| `app_link` | 日程应用链接 |
|
||||
|
||||
> 注意:如需日程详情,请使用 `calendar events get`。
|
||||
|
||||
## 分页
|
||||
|
||||
- 使用 `page_size` 控制每页条数(1-30),默认 20。
|
||||
- 当 `has_more` 为 `true` 时,使用返回的 `page_token` 获取下一页。
|
||||
- 必须持续翻页直到 `has_more` 为 `false`,确保不遗漏结果。
|
||||
|
||||
## 提示
|
||||
|
||||
- 搜索已结束的会议应优先使用 `vc +search`,因为即时会议不会出现在日历中。
|
||||
|
||||
## 参考
|
||||
|
||||
- [lark-calendar](../SKILL.md) — 日历全部命令
|
||||
- [lark-calendar-meeting](lark-calendar-meeting.md) — 从日程获取会议信息
|
||||
- [lark-vc](../../lark-vc/SKILL.md) — 搜索历史会议
|
||||
- [lark-shared](../../lark-shared/SKILL.md) — 认证和全局参数
|
||||
- 分页:`has_more=true` 时持续用 `page_token` 翻页直到 false,不要遗漏;`page-size` 最大 30。
|
||||
- 已结束的会议优先用 `vc +search`——日历不收录"即时会议",只查日程会漏。
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
---
|
||||
name: lark-minutes
|
||||
version: 1.0.0
|
||||
description: "飞书妙记:搜索妙记列表、查看妙记基础信息、下载妙记音视频文件、上传音视频生成妙记、更新妙记标题、替换说话人。当需要获取、操作或者生成妙记时使用。也支持将本地音视频文件转成纪要和逐字稿(优先使用本 skill,不要用 ffmpeg/whisper 本地转写)。不负责:获取会议关联妙记,或仅按自然语言标题定位纪要"
|
||||
description: "飞书妙记:搜索妙记、查看妙记基础信息、下载/上传音视频、读取或编辑妙记的产物内容、改标题、替换说话人/关键词。当给出minute_token、本地音视频文件,要查/改/转妙记产物时使用;本地音视频转纪要/逐字稿优先走本 skill,不要用 ffmpeg/whisper 本地转写。不负责:获取会议关联妙记,或仅按自然语言标题定位纪要"
|
||||
metadata:
|
||||
requires:
|
||||
bins: ["lark-cli"]
|
||||
@@ -27,28 +27,34 @@ metadata:
|
||||
| Shortcut | 说明 |
|
||||
|----------|------|
|
||||
| [`+search`](references/lark-minutes-search.md) | 按关键词、所有者、参与者、时间范围搜索妙记 |
|
||||
| [`+detail`](references/lark-minutes-detail.md) | 查询妙记详情,按需获取 AI 产物(总结、待办、章节、逐字稿、关键词) |
|
||||
| [`+detail`](references/lark-minutes-detail.md) | 查询妙记详情(标题和关联的纪要note_id),按需获取 AI 产物(总结、待办、章节、逐字稿、关键词) |
|
||||
| [`+download`](references/lark-minutes-download.md) | 下载妙记音视频媒体文件 |
|
||||
| [`+upload`](references/lark-minutes-upload.md) | 上传 file_token 生成妙记 |
|
||||
| [`+update`](references/lark-minutes-update.md) | 更新妙记标题 |
|
||||
| [`+speaker-replace`](references/lark-minutes-speaker-replace.md) | 替换妙记逐字稿中的说话人(仅支持用户 ID,不支持姓名) |
|
||||
| `+word-replace` | 批量替换逐字稿关键词(详见 `lark-cli minutes +word-replace --help`) |
|
||||
| [`+summary`](references/lark-minutes-summary.md) | 替换妙记 AI 总结全文 |
|
||||
| [`+todo`](references/lark-minutes-todo.md) | 新建/更新/删除妙记 AI 待办(单条或 `--todos` 批量;不是 lark-task) |
|
||||
|
||||
- 使用任何 Shortcut 前,必须先读其对应 reference 文档。
|
||||
|
||||
## 意图路由
|
||||
|
||||
| 用户意图 | 路由到 |
|
||||
|----------|--------|
|
||||
| "我的妙记""搜索妙记""妙记列表" | 本 skill(`+search`) |
|
||||
| "这个妙记的标题/时长/封面/链接" | 本 skill(`minutes get`) |
|
||||
| "下载妙记的视频/音频" | 本 skill(`+download`) |
|
||||
| "把音视频转妙记/上传文件生成妙记" | 本 skill(`+upload`) |
|
||||
| "重命名妙记/改妙记标题" | 本 skill(`+update`) |
|
||||
| "替换说话人/把 A 的发言改成 B" | 本 skill(`+speaker-replace`) |
|
||||
| "这个妙记的逐字稿/总结/待办/章节(需保存逐字稿到文件)" | 本 skill(`+detail --minute-tokens --transcript`) |
|
||||
| "xx 纪要的逐字稿/原始记录/谁说了什么" 且没有 `minute_token` / 妙记 URL / 本地音视频文件 | 不走本 skill;路由到 [lark-drive](../lark-drive/SKILL.md) / [lark-doc](../lark-doc/SKILL.md),必要时再到 [lark-note](../lark-note/SKILL.md) |
|
||||
| "把音视频文件转成纪要/逐字稿/文字稿" | 先本 skill(`+upload`),再本 skill(`minutes +detail --minute-tokens`) |
|
||||
| 用户同时提到"会议/开会"和"妙记" | 先 [lark-vc](../lark-vc/SKILL.md)(`+search` → `+recording`),再本 skill |
|
||||
| 用户意图 | 命令 |
|
||||
|---------|------|
|
||||
| 我的妙记 / 搜索妙记 / 某段时间的妙记 | `+search` |
|
||||
| 妙记基础信息:标题 / 时长 / 封面 / 链接 | `minutes get` |
|
||||
| 下载妙记音视频文件、获取媒体下载链接 | `+download`(仅媒体;要妙记内容用 `+detail`) |
|
||||
| 妙记总结 / 章节 / 待办 / 关键词 / 逐字稿 | `+detail --minute-tokens <token>` + 显式产物 flag |
|
||||
| 基于妙记**提炼/总结/分析/回顾**会议 | `+detail --minute-tokens <token> --transcript`,再独立分析(**禁止照搬 AI 总结**) |
|
||||
| 拿这条妙记关联的纪要文档(`note_doc_token` / `verbatim_doc_token` / `shared_doc_tokens`) | `+detail` 取顶层 `note_id` → [`note +detail --note-id`](../lark-note/SKILL.md) |
|
||||
| 把本地音视频转纪要 / 逐字稿 / 文字稿 | `drive +upload` 取 `file_token` → `+upload` 生成 `minute_url` → `+detail` 拿产物 |
|
||||
| 在妙记里增加 / 更改 / 删除 AI 待办 | `+todo`(**禁止走 lark-task**) |
|
||||
| 替换妙记的AI 总结 | `+summary` |
|
||||
| 重命名妙记/改妙记标题 | `+update` |
|
||||
| 替换说话人/把 A 的发言改成 B/重新归属发言人 | `+speaker-replace` |
|
||||
| 批量替换逐字稿关键词 | `+word-replace` |
|
||||
| 用户同时提到"会议/开会"和"妙记" | 先 [lark-vc](../lark-vc/SKILL.md)(`+search` → `+recording`)获取 `minute_token`,再本 skill |
|
||||
|
||||
## 核心概念
|
||||
|
||||
@@ -59,53 +65,21 @@ metadata:
|
||||
|
||||
### 1. 搜索妙记
|
||||
|
||||
1. 当用户描述的是"我的妙记""包含某个关键词的妙记""某段时间内的妙记",优先使用 `minutes +search`。
|
||||
2. 仅支持使用关键词、时间段、参与者、所有者等筛选条件搜索妙记记录,对于不支持的筛选条件,需要提示用户。
|
||||
3. 搜索结果存在多条数据时,务必注意分页数据获取,不要遗漏任何妙记记录。
|
||||
4. 如果是会议的妙记,应优先通过 [lark-vc](../lark-vc/SKILL.md) 定位会议并获取 `minute_token`。
|
||||
5. 会议场景的妙记路由,以及"参与的妙记"如何解释,统一以 [minutes +search](references/lark-minutes-search.md) 为准。
|
||||
1. 如果是会议的妙记,应优先通过 [lark-vc](../lark-vc/SKILL.md) 定位会议并获取 `minute_token`。
|
||||
2. 会议场景的妙记路由,以及"参与的妙记"如何解释,统一以 [minutes +search](references/lark-minutes-search.md) 为准。
|
||||
|
||||
|
||||
### 2. 查看妙记基础信息
|
||||
|
||||
1. 当用户只需要确认某条妙记的标题、封面、时长、所有者、URL 等基础信息时,使用 `minutes minutes get`。
|
||||
2. 如果用户给的是妙记 URL,应先从 URL 末尾提取 `minute_token`,再调用 `minutes minutes get`。
|
||||
3. 如果是会议 / 日程上下文中的妙记基础信息,先通过 VC 链路拿到 `minute_token`,再调用 `minutes minutes get`。
|
||||
4. 用户意图不明确时,默认先给基础元信息,帮助确认是否命中目标妙记。
|
||||
2. 如果是会议 / 日程上下文中的妙记基础信息,先通过 VC/Calendar 链路拿到 `minute_token`,再调用 `minutes minutes get`。
|
||||
3. 用户意图不明确时,默认先给基础元信息,帮助确认是否命中目标妙记。
|
||||
|
||||
> 使用 `lark-cli schema minutes.minutes.get` 可查看完整返回值结构。核心字段包含:`title`(标题)、`cover`(封面 URL)、`duration`(时长,毫秒)、`owner_id`(所有者 ID)、`url`(妙记链接)。
|
||||
|
||||
### 3. 下载妙记音视频文件
|
||||
### 3. 上传音视频文件生成妙记(并可继续获取纪要 / 逐字稿)
|
||||
|
||||
1. 下载妙记音视频文件到本地,或获取有效期 1 天的下载链接。详见 [minutes +download](references/lark-minutes-download.md)。
|
||||
2. `+download` 只负责音视频媒体文件。用户需要逐字稿、总结、待办、章节等纪要内容时,请使用 `minutes +detail`。
|
||||
3. 用户只想拿可分享的下载地址时,使用 `--url-only`;用户要落地到本地文件时,直接下载。
|
||||
4. 未显式指定路径时,文件默认落到 `./minutes/{minute_token}/<server-filename>`,与 `minutes +detail` 的逐字稿共享同一目录便于聚合。
|
||||
|
||||
> **注意**:`+download` 只负责音视频媒体文件。如果用户需要的是逐字稿、总结、待办、章节等纪要内容,请使用 `minutes +detail`。
|
||||
|
||||
### 4. 读取妙记的逐字稿、总结、待办、章节(只读)
|
||||
|
||||
1. 当用户说"这个妙记的逐字稿""总结""待办""章节"时,请使用本 skill 的 `+detail` 命令。
|
||||
2. `minutes +detail` **必须显式指定**要获取的产物 flag(`--summary` / `--todo` / `--chapter` / `--keyword` / `--transcript`),未传任何产物 flag 时只返回基础信息(如 `title`),不会返回任何 AI 产物内容。请求的产物即使为空也返回空值字段,便于程序化处理。
|
||||
3. 如果当前上下文中已有 `minute_token`,可直接传给 `minutes +detail`;如果只有妙记 URL,先提取 `minute_token`。
|
||||
4. 如果用户给的是**本地音视频文件**,但目标是"转成纪要""转成逐字稿""转成文字稿""转成撰写文字",也支持;此时应先按下文第 5 节上传文件生成妙记,再把返回的 `minute_url` 提取成 `minute_token`,继续调用 `minutes +detail --minute-tokens`。
|
||||
5. 用户如果直接给出本地文件名或路径,并要求"转逐字稿""转文字稿""整理成撰写文字",这也是本 skill 的明确触发信号。
|
||||
|
||||
```bash
|
||||
# 通过 minute_token 获取指定产物(必须显式列出要拿的 flag)
|
||||
lark-cli minutes +detail --minute-tokens <minute_token> --summary --todo --chapter
|
||||
|
||||
# 仅获取逐字稿并保存到文件
|
||||
lark-cli minutes +detail --minute-tokens <minute_token> --transcript
|
||||
```
|
||||
> 用户要**新建 / 修改 / 删除**妙记内的 AI 待办或替换 AI 总结,见下文第 6 节,**不要**走 [lark-task](../lark-task/SKILL.md)。
|
||||
|
||||
### 5. 上传音视频文件生成妙记(并可继续获取纪要 / 逐字稿)
|
||||
|
||||
1. 当用户需要通过上传本地音视频文件来生成妙记时使用。
|
||||
2. 当用户说"把音视频文件转成纪要""把录音转成逐字稿/文字稿/撰写文字""把 mp4/mp3 转成总结/待办/章节"时,也先走这个入口。
|
||||
3. **处理流程**:
|
||||
1. 当用户说"把音视频文件转成纪要""把录音转成逐字稿/文字稿/撰写文字""把 mp4/mp3 转成总结/待办/章节"时,也先走这个入口。
|
||||
2. **处理流程**:
|
||||
- **上传音视频获取 `file_token`**:使用 [`lark-cli drive +upload`](../lark-drive/references/lark-drive-upload.md) 上传本地文件到云空间(云盘/云存储)并获取 `file_token`。
|
||||
- **生成妙记**:获取到 `file_token` 后,调用 [`lark-cli minutes +upload`](references/lark-minutes-upload.md) 将文件转换为妙记并获取 `minute_url` 链接。
|
||||
- **继续获取纪要 / 逐字稿(按需)**:如果用户目标不是只要妙记链接,而是要纪要、逐字稿、总结、待办或章节,则从 `minute_url` 中提取 `minute_token`,再调用 [`lark-cli minutes +detail --minute-tokens`](references/lark-minutes-detail.md) 获取对应产物。
|
||||
@@ -114,7 +88,7 @@ lark-cli minutes +detail --minute-tokens <minute_token> --transcript
|
||||
>
|
||||
> **不要误走本地转写工具**:当用户目标是把本地音视频文件转成纪要、逐字稿、文字稿、撰写文字时,不要改用 `ffmpeg`、`whisper` 或其他本地 ASR/转码命令;标准路径就是 `drive +upload -> minutes +upload -> minutes +detail --minute-tokens`。
|
||||
|
||||
### 6. 编辑妙记的 AI 待办与 AI 总结(写入)
|
||||
### 5. 编辑妙记的 AI 待办与 AI 总结(写入)
|
||||
|
||||
当用户要在**某条妙记内**操作 AI 待办或 AI 总结时使用本节。**不是**飞书任务(Task)清单里的待办。
|
||||
|
||||
@@ -154,61 +128,34 @@ lark-cli minutes +todo --minute-token <token> --as user --todos '[
|
||||
|
||||
> 使用 `+todo` 前必须阅读 [references/lark-minutes-todo.md](references/lark-minutes-todo.md);使用 `+summary` 前必须阅读 [references/lark-minutes-summary.md](references/lark-minutes-summary.md)。
|
||||
|
||||
## 资源关系
|
||||
## 行为规则
|
||||
|
||||
```text
|
||||
Minutes (妙记) ← minute_token 标识
|
||||
├── Metadata (标题、封面、时长、owner、url) → minutes minutes get
|
||||
└── MediaFile (音频/视频文件) → minutes +download
|
||||
### 1. `+detail` 必须显式声明产物 flag
|
||||
|
||||
不传 `--summary` / `--todo` / `--chapter` / `--keyword` / `--transcript` 时只返回基础信息(含顶层 `note_id`),AI 产物字段一律不返回。即使产物为空也会返回空值字段,便于程序化处理。
|
||||
|
||||
```bash
|
||||
# 拿全产物
|
||||
lark-cli minutes +detail --minute-tokens <token> --summary --todo --chapter --keyword --transcript
|
||||
```
|
||||
|
||||
> **能力边界**:`minutes` 负责 **搜索妙记、查看基础元信息、下载/上传音视频、编辑妙记 AI 待办与 AI 总结、重命名、逐字稿说话人/关键词替换**。
|
||||
>
|
||||
> **路由规则**:
|
||||
>
|
||||
> - 用户说"妙记列表 / 搜索妙记 / 某个关键词的妙记" → `minutes +search`
|
||||
> - 用户只是想看"我的妙记 / 某段时间内的妙记 / 妙记列表",不要先走 [lark-vc](../lark-vc/SKILL.md),而应直接使用本 skill
|
||||
> - 用户如果同时提到"会议 / 会 / 开会 / 某场会",即使也提到了"妙记",也应优先走 [lark-vc](../lark-vc/SKILL.md) 先定位会议,再通过 [vc +recording](../lark-vc/references/lark-vc-recording.md) 获取 `minute_token`
|
||||
> - 用户如果要的是妙记基础信息,拿到 `minute_token` 后用 `minutes minutes get`;用户如果要的是逐字稿、文字稿、撰写文字、总结、待办、章节,走 `minutes +detail`
|
||||
> - “我的妙记”“参与的妙记”等自然语言映射细则,以 [minutes +search](references/lark-minutes-search.md) 为准
|
||||
> - 结果有多页时,使用 `page_token` 持续翻页,直到确认没有更多结果
|
||||
> - `minutes +search` 单次最多返回 `200` 条;结果总数没有固定上限
|
||||
> - 用户说"这个妙记的标题 / 时长 / 封面 / 链接" → `minutes minutes get`
|
||||
> - 用户说"下载这个妙记的视频 / 音频 / 媒体文件" → `minutes +download`
|
||||
> - 用户说"这个妙记的逐字稿 / 文字稿 / 撰写文字 / 总结 / 待办 / 章节" → 使用 [vc +notes --minute-tokens](../lark-vc/references/lark-vc-notes.md)
|
||||
> - 用户说"通过文件生成妙记 / 把音视频转妙记" → 先上传获取 `file_token`,然后使用 `minutes +upload`
|
||||
> - 用户说"把音视频文件转成纪要 / 逐字稿 / 文字稿 / 撰写文字 / 总结 / 待办 / 章节" → 先上传获取 `file_token`,调用 `minutes +upload` 生成 `minute_url`,再提取 `minute_token` 走 `minutes +detail --minute-tokens`
|
||||
> - 用户说"重命名妙记 / 改妙记标题 / 修改妙记名字" → `minutes +update`
|
||||
> - 用户说"替换说话人 / 把 A 的发言改成 B / 重新归属发言人" → `minutes +speaker-replace`
|
||||
> - 用户说"批量替换逐字稿关键词" → `minutes +word-replace`
|
||||
>
|
||||
> **Note 域边界(禁止规则)**:`minute_token` 是妙记文件标识,**不是** `note_id`。
|
||||
> - 不要把 `minute_token` 传给 `note +detail` 或 `note +transcript`。
|
||||
> - 已有 `minute_token` 且要读取纪要产物时,先走 [lark-vc](../lark-vc/SKILL.md);只有自然语言纪要标题时不要从 Minutes 反查。
|
||||
### 2. "提炼 / 总结"必须基于 Transcript,不要照搬 AI 总结
|
||||
|
||||
## Shortcuts(推荐优先使用)
|
||||
AI 总结是模型对会议的二次压缩,可能遗漏争论过程和隐含决策。用户要求"提炼"或"重新总结"时,期望基于原始发言独立分析,而非搬运 AI 产物。**优先 `--transcript`,再独立写结论**。
|
||||
|
||||
Shortcut 是对常用操作的高级封装(`lark-cli minutes +<verb> [flags]`)。有 Shortcut 的操作优先使用。
|
||||
### 3. 从妙记反查纪要:不绕 lark-vc
|
||||
|
||||
| Shortcut | 说明 |
|
||||
| -------------------------------------------------- | --------------------------------------------------------------- |
|
||||
| [`+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 |
|
||||
| [`+update`](references/lark-minutes-update.md) | Update a minute's title |
|
||||
| [`+speaker-replace`](references/lark-minutes-speaker-replace.md) | Replace a speaker in a minute's transcript (rebind from one user to another) |
|
||||
| [`+summary`](references/lark-minutes-summary.md) | Replace the full AI summary text of a minute |
|
||||
| [`+todo`](references/lark-minutes-todo.md) | Add, update, or delete **AI todo(s) inside a minute** (single or batch via `--todos`; not Feishu Task) |
|
||||
`minutes +detail` 顶层直接返回 `note_id`(仅在该妙记关联纪要时存在)。不需要绕回 [lark-vc](../lark-vc/SKILL.md),直接:
|
||||
|
||||
- 使用 `+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),了解生成参数和返回值结构。
|
||||
- 使用 `+update` 命令时,必须阅读 [references/lark-minutes-update.md](references/lark-minutes-update.md),了解修改参数和返回值结构。
|
||||
- 使用 `+speaker-replace` 命令时,必须阅读 [references/lark-minutes-speaker-replace.md](references/lark-minutes-speaker-replace.md),了解参数和限制(仅支持用户 ID,不支持姓名)。
|
||||
- 使用 `+summary` 命令时,必须阅读 [references/lark-minutes-summary.md](references/lark-minutes-summary.md),了解全文替换参数。
|
||||
- 使用 `+todo` 命令时,必须阅读 [references/lark-minutes-todo.md](references/lark-minutes-todo.md),了解单条与 `--todos` 批量模式;**不要**用 lark-task。
|
||||
```bash
|
||||
# 1) 取 note_id(顶层 .minutes[0].note_id)
|
||||
lark-cli minutes +detail --minute-tokens <minute_token> --format json
|
||||
# 2) 用上一步拿到的 note_id 读纪要 token
|
||||
lark-cli note +detail --note-id <note_id> # 拿 note_doc_token / verbatim_doc_token / shared_doc_tokens
|
||||
```
|
||||
|
||||
顶层无 `note_id` 字段即代表无关联纪要,到此为止——不要继续尝试用 `minute_token` 当 `note_id`。
|
||||
|
||||
<!-- AUTO-GENERATED-START — gen-skills.py 管理,勿手动编辑 -->
|
||||
|
||||
## API Resources
|
||||
|
||||
@@ -224,6 +171,8 @@ lark-cli minutes <resource> <method> [flags]
|
||||
|
||||
## 不在本 skill 范围
|
||||
|
||||
- 只有自然语言纪要标题的逐字稿查询 → 文档搜索 / Docx 正文读取;有显式 `vc-node-id` 才进入 [lark-note](../lark-note/SKILL.md)
|
||||
- 搜索历史会议记录 → [lark-vc](../lark-vc/SKILL.md)
|
||||
- 查询未来的会议日程 → [lark-calendar](../lark-calendar/SKILL.md)
|
||||
- 搜索历史会议记录、查参会人快照 → [lark-vc](../lark-vc/SKILL.md)
|
||||
- 未来日程 / 日历查询 → [lark-calendar](../lark-calendar/SKILL.md)
|
||||
- 已知 `note_id` 直接读纪要详情 → [lark-note](../lark-note/SKILL.md)
|
||||
- 飞书任务清单(个人 Todo / 共享清单) → [lark-task](../lark-task/SKILL.md)
|
||||
- 只有自然语言纪要标题、没有 `minute_token` / 妙记 URL / 本地音视频时定位逐字稿 → 文档搜索([lark-drive](../lark-drive/SKILL.md) / [lark-doc](../lark-doc/SKILL.md))
|
||||
|
||||
@@ -1,85 +1,62 @@
|
||||
|
||||
# minutes +detail
|
||||
|
||||
> **前置条件:** 先阅读 [`../lark-shared/SKILL.md`](../../lark-shared/SKILL.md) 了解认证、全局参数和安全规则。
|
||||
通过 `minute_token` 查询妙记详情,按需获取 AI 产物(总结/待办/章节/逐字稿/关键词)。只读。
|
||||
|
||||
通过妙记 Token 查询妙记详情,支持按需获取 AI 产物(总结、待办、章节、逐字稿、关键词)。只读操作。
|
||||
|
||||
> **重要约束**:必须**显式指定**要获取哪些产物的 flag(`--summary` / `--todo` / `--chapter` / `--keyword` / `--transcript`),未传任何产物 flag 时只返回基础信息(如 `title`),不会返回任何 AI 产物内容。一次性获取所有产物可使用:`--summary --todo --chapter --keyword --transcript`。
|
||||
|
||||
本 skill 对应 shortcut:`lark-cli minutes +detail`。
|
||||
> `--summary` / `--todo` / `--chapter` / `--keyword` / `--transcript` 至少一个;不传任何产物 flag 时只返回基础信息(如 `title`),AI 产物字段都不会出现。一次性获取所有产物:`--summary --todo --chapter --keyword --transcript`。
|
||||
|
||||
## 命令
|
||||
|
||||
```bash
|
||||
# 查询妙记基础信息(标题)
|
||||
# 仅基础信息
|
||||
lark-cli minutes +detail --minute-tokens obcxxxxxxxxxx
|
||||
|
||||
# 批量查询(逗号分隔,最多 50 个)
|
||||
lark-cli minutes +detail --minute-tokens obcxxxxxxxxxx,obcyyyyyyyyyy
|
||||
# 批量(逗号分隔,最多 50 个)
|
||||
lark-cli minutes +detail --minute-tokens obcxxx,obcyyy --summary --todo
|
||||
|
||||
# 按需获取 AI 产物
|
||||
lark-cli minutes +detail --minute-tokens obcxxxxxxxxxx --summary --todo --chapter --transcript --keyword
|
||||
# 全产物
|
||||
lark-cli minutes +detail --minute-tokens obcxxx --summary --todo --chapter --keyword --transcript
|
||||
|
||||
# 逐字稿输出到文件(默认 ./minutes/{minute_token}/transcript.txt)
|
||||
lark-cli minutes +detail --minute-tokens obcxxxxxxxxxx --transcript
|
||||
|
||||
# 覆盖已有逐字稿文件
|
||||
lark-cli minutes +detail --minute-tokens obcxxxxxxxxxx --transcript --overwrite
|
||||
# 仅逐字稿,覆盖已有文件
|
||||
lark-cli minutes +detail --minute-tokens obcxxx --transcript --overwrite
|
||||
```
|
||||
|
||||
## 参数
|
||||
## 输出
|
||||
|
||||
| 参数 | 必填 | 说明 |
|
||||
|------|------|------|
|
||||
| `--minute-tokens <tokens>` | 是 | 妙记 Token,逗号分隔支持批量,最多 50 个。仅支持小写字母和数字 |
|
||||
| `--summary` | 否 | 包含 AI 总结 |
|
||||
| `--todo` | 否 | 包含待办事项 |
|
||||
| `--chapter` | 否 | 包含章节纪要 |
|
||||
| `--transcript` | 否 | 包含逐字稿(保存到本地文件) |
|
||||
| `--keyword` | 否 | 包含推荐关键词 |
|
||||
| `--overwrite` | 否 | 覆盖已存在的逐字稿文件(仅 `--transcript` 有效) |
|
||||
| `--dry-run` | 否 | 预览 API 调用,不执行 |
|
||||
|
||||
## 输出结果
|
||||
|
||||
返回 `minutes` 数组,每条记录包含:
|
||||
|
||||
| 字段 | 说明 |
|
||||
|------|------|
|
||||
| `minute_token` | 妙记 Token |
|
||||
| `title` | 妙记标题(如有) |
|
||||
| `artifacts` | AI 产物(仅指定了 `--summary`/`--todo`/`--chapter`/`--transcript`/`--keyword` 时返回) |
|
||||
|
||||
### artifacts 字段
|
||||
|
||||
请求的 AI 产物**始终返回对应字段**:
|
||||
`minutes` 数组每条含 `minute_token`、`title`、`note_id`、`artifacts`。`note_id` 仅在该妙记关联了会议纪要时返回,可直接传给 [`note +detail`](../../lark-note/references/lark-note-detail.md) 拿纪要文档 token,无需再绕回 `vc +detail`。`artifacts` 中**只包含本次请求的产物**:
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| `artifacts.summary` | string | AI 总结。为空时返回 `""` |
|
||||
| `artifacts.todos` | array | 待办事项列表。为空时返回 `[]` |
|
||||
| `artifacts.chapters` | array | 章节列表。为空时返回 `[]` |
|
||||
| `artifacts.keywords` | array | 关键词列表。为空时返回 `[]` |
|
||||
| `artifacts.transcript_file` | string | 逐字稿本地文件路径。为空时返回 `""` |
|
||||
| `artifacts.summary` | string | AI 总结。 |
|
||||
| `artifacts.todos` | array | 待办事项列表。 |
|
||||
| `artifacts.chapters` | array | 章节列表。 |
|
||||
| `artifacts.keywords` | array | 关键词列表。 |
|
||||
| `artifacts.transcript_file` | string | 逐字稿本地文件路径。 |
|
||||
|
||||
> 未请求的产物不会出现在 `artifacts` 中。例如只传了 `--summary`,则 `artifacts` 中只有 `summary` 字段。
|
||||
逐字稿默认落地 `./minutes/{minute_token}/transcript.txt`,与 `minutes +download` 同目录便于聚合。
|
||||
|
||||
### 逐字稿文件路径
|
||||
## minute_token 来源
|
||||
|
||||
指定 `--transcript` 时,逐字稿默认保存到 `./minutes/{minute_token}/transcript.txt`,与 `minutes +download` 的默认落点保持一致,便于 Agent 聚合同一妙记的所有产物。
|
||||
| 来源 | 取值字段 |
|
||||
|------|---------|
|
||||
| 妙记 URL `https://*.feishu.cn/minutes/obcxxx` | 截路径最后一段 `obcxxx` |
|
||||
| `vc +detail --meeting-ids` | `minute_token` |
|
||||
| `vc +recording --meeting-ids` | `minute_token` |
|
||||
| `minutes +search` | `minute_token` |
|
||||
|
||||
## 如何获取输入参数
|
||||
## 典型链路:从 minute_token 拿纪要文档 token
|
||||
|
||||
| 输入参数 | 获取方式 |
|
||||
|---------|---------|
|
||||
| `minute_token` | 从妙记 URL 中提取,如 `https://sample.feishu.cn/minutes/obcxxx` → `obcxxx` |
|
||||
| `minute_token` | `vc +detail --meeting-ids` → 结果中的 `minute_token` 字段 |
|
||||
| `minute_token` | `vc +recording --meeting-ids` → 结果中的 `minute_token` 字段 |
|
||||
| `minute_token` | `minutes +search` → 结果中的 `minute_token` 字段 |
|
||||
只持有 `minute_token`(如妙记 URL 入口),又想拿 AI 智能纪要 / 逐字稿文档时:
|
||||
|
||||
## 参考
|
||||
```bash
|
||||
# 1. 取妙记关联的 note_id,没有关联会议纪要则为空
|
||||
lark-cli minutes +detail --minute-tokens <minute_token>
|
||||
|
||||
- [lark-minutes](../SKILL.md) — 妙记全部命令
|
||||
- [lark-vc](../../lark-vc/SKILL.md) — 视频会议(获取 minute_token)
|
||||
- [lark-shared](../../lark-shared/SKILL.md) — 认证和全局参数
|
||||
# 2. 用 note_id 拿 note_doc_token / verbatim_doc_token / shared_doc_tokens
|
||||
lark-cli note +detail --note-id <note_id>
|
||||
|
||||
# 3. 读纪要 / 逐字稿正文
|
||||
lark-cli docs +fetch --api-version v2 --doc <note_doc_token> --doc-format markdown
|
||||
```
|
||||
|
||||
> `minute_token` 不要直接传给 `note +detail`:必须先用本命令拿到 `note_id` 再调用 `note +detail`。
|
||||
|
||||
@@ -190,7 +190,7 @@ lark-cli minutes +detail --minute-tokens obcn_EXAMPLE_TOKEN
|
||||
- 当用户说“我的妙记”时,优先理解为 `--owner-ids me`。
|
||||
- 当用户说“我参与的妙记”“我参加过的妙记”时,默认理解为 `--owner-ids me` 与 `--participant-ids me` 两次查询后的并集。
|
||||
- 当用户明确说“仅我参与但不是我拥有”时,才优先理解为 `--participant-ids me`。
|
||||
- 当用户同时提到“会议 / 会 / 开会 / 某场会”和“妙记”时,优先先定位会议;如果要的是妙记信息,走 `vc +detail` 获取 `minute_token` → `minutes minutes get`,只有要纪要内容时才走 `minutes +detail --minute-tokens`。
|
||||
- 当用户同时提到“会议 / 会 / 开会 / 某场会”和“妙记”时,优先先定位会议;如果要的是妙记信息,走 `vc +recording` 获取 `minute_token` → `minutes minutes get`,只有要妙记产物内容时才走 `minutes +detail --minute-tokens`。
|
||||
- 必须使用 `--format json` 输出,你更加擅长解析 JSON 数据。
|
||||
- 排查参数与请求结构时优先使用 `--dry-run`。
|
||||
- 搜索的时间范围最大为 1 个月,如果需要搜索更长时间范围的妙记,需要拆分为多次时间范围为一个月查询。
|
||||
|
||||
@@ -40,7 +40,7 @@ lark-cli minutes +summary --minute-token obcnxxxxxxxxxxxxxxxxxxxx --summary @sum
|
||||
|
||||
### 1. 先读后写
|
||||
|
||||
替换前建议先用 `lark-cli vc +notes --minute-tokens <token>` 读取当前总结,确认 `minute_token` 与待替换内容无误。
|
||||
替换前建议先用 `lark-cli minutes +detail --minute-tokens <token> --summary` 读取当前总结,确认 `minute_token` 与待替换内容无误。
|
||||
|
||||
### 2. Markdown 展示说明
|
||||
|
||||
@@ -104,7 +104,7 @@ lark-cli minutes +summary --minute-token obcnxxxxxxxxxxxxxxxxxxxx --summary @sum
|
||||
|------|---------|
|
||||
| 妙记 URL | 从 URL 末尾提取,如 `https://sample.feishu.cn/minutes/obcnxxxxxxxxxxxxxxxxxxxx` |
|
||||
| 妙记搜索 | `lark-cli minutes +search --query "关键词"` |
|
||||
| 会议产物查询 | `lark-cli vc +notes --minute-tokens <token>` |
|
||||
| 会议产物查询 | `lark-cli vc +detail --meeting-ids <id>` 拿到 `minute_token`,或 `vc +recording` |
|
||||
|
||||
## 常见错误与排查
|
||||
|
||||
@@ -118,5 +118,5 @@ lark-cli minutes +summary --minute-token obcnxxxxxxxxxxxxxxxxxxxx --summary @sum
|
||||
|
||||
- [lark-minutes](../SKILL.md) — 妙记全部命令
|
||||
- [minutes +todo](lark-minutes-todo.md) — 替换待办项
|
||||
- [lark-vc-notes](../../lark-vc/references/lark-vc-notes.md) — 读取总结、待办等 AI 产物
|
||||
- [minutes +detail](lark-minutes-detail.md) — 读取总结、待办等 AI 产物
|
||||
- [lark-shared](../../lark-shared/SKILL.md) — 认证和全局参数
|
||||
|
||||
@@ -94,7 +94,7 @@ lark-cli minutes +todo --minute-token obcnxxxxxxxxxxxxxxxxxxxx --operation add -
|
||||
|
||||
### 1. 先读后写,待办 id 如何获取
|
||||
|
||||
更新 / 删除前先用 `lark-cli vc +notes --minute-tokens <token>` 读取当前待办。返回的每条待办带 `todo_id` 字段。
|
||||
更新 / 删除前先用 `lark-cli minutes +detail --minute-tokens <token> --todo` 读取当前待办。返回的每条待办带 `todo_id` 字段。
|
||||
|
||||
> 待办 id 仅用于程序内部定位,不必展示给用户。
|
||||
|
||||
@@ -134,5 +134,5 @@ lark-cli minutes +todo --minute-token obcnxxxxxxxxxxxxxxxxxxxx --operation add -
|
||||
|
||||
- [lark-minutes](../SKILL.md)
|
||||
- [minutes +summary](lark-minutes-summary.md)
|
||||
- [lark-vc-notes](../../lark-vc/references/lark-vc-notes.md)
|
||||
- [minutes +detail](lark-minutes-detail.md)
|
||||
- [lark-shared](../../lark-shared/SKILL.md)
|
||||
|
||||
@@ -25,6 +25,9 @@ Note 域只接受显式 `note_id`:用户直接提供,或 `docs +fetch --api-
|
||||
|---------|------|
|
||||
| 已知 `note_id`,查纪要类型 / 文档 token | `note +detail --note-id NOTE_ID` |
|
||||
| `docs +fetch --api-version v2` 返回 `<vc-transcribe-tab vc-node-id="...">` | 取 `vc-node-id` 作为 `NOTE_ID`,先 `note +detail --note-id NOTE_ID` |
|
||||
| 只持有 `meeting_id` | 先 `vc +detail --meeting-ids <id>` 拿 `note_id`,再 `note +detail --note-id NOTE_ID` |
|
||||
| 只持有 `minute_token`(妙记 URL) | 先 `minutes +detail --minute-tokens <token>` 顶层取 `note_id`,再 `note +detail --note-id NOTE_ID`(不要把 `minute_token` 当 `note_id`) |
|
||||
| 只持有日程 `event_id` | 先 `calendar +meeting --event-ids <id>` 拿 `meeting_id`,再按上一行继续 |
|
||||
| 已知 `note_id`,读纪要正文 | `note +detail` → `docs +fetch --api-version v2 --doc <note_doc_token>` |
|
||||
| 已知 `note_id`,查 unified 原始记录 / 逐字稿 | `note +transcript --note-id NOTE_ID` |
|
||||
| 只有自然语言纪要标题,用户要逐字稿 / 原始记录 / 谁说了什么 | 不进本 skill;先走文档搜索与 `docs +fetch`,拿到 `vc-node-id` 后再回来 |
|
||||
@@ -49,7 +52,9 @@ Note 域只接受显式 `note_id`:用户直接提供,或 `docs +fetch --api-
|
||||
|
||||
## 不在本 Skill 范围
|
||||
|
||||
- 通过 `meeting_id` / `calendar_event_id` / `minute_token` 定位纪要 → [lark-vc](../lark-vc/SKILL.md)。
|
||||
- 通过 `meeting_id` 定位纪要(`note_id`)→ [lark-vc](../lark-vc/SKILL.md)(`vc +detail`)。
|
||||
- 通过 `minute_token` 定位纪要(`note_id`)→ [lark-minutes](../lark-minutes/SKILL.md)(`minutes +detail` 顶层返回 `note_id`)。
|
||||
- 通过日程 `event_id` 定位会议(`meeting_id`) / 用户绑定纪要(`meeting_note`) → [lark-calendar](../lark-calendar/SKILL.md)(`calendar +meeting`)。
|
||||
- 自然语言纪要标题搜索 → [lark-drive](../lark-drive/SKILL.md) / [lark-doc](../lark-doc/SKILL.md)。
|
||||
- Docx 正文读取 → [lark-doc](../lark-doc/SKILL.md)。
|
||||
- 妙记基础信息与媒体文件 → [lark-minutes](../lark-minutes/SKILL.md)。
|
||||
@@ -60,3 +65,30 @@ Note 域只接受显式 `note_id`:用户直接提供,或 `docs +fetch --api-
|
||||
|----------|------|
|
||||
| [`+detail`](references/lark-note-detail.md) | 需要解释输出字段或根据展示类型继续路由 |
|
||||
| [`+transcript`](references/lark-note-transcript.md) | 需要拉取 unified 原始记录或处理本地输出文件 |
|
||||
|
||||
## 核心概念
|
||||
|
||||
- **会议纪要(Note)**:视频会议结束后生成的结构化文档,通过 `note_id` 标识。一个 Note 包含 AI 智能纪要文档、逐字稿文档和会中共享文档。
|
||||
- **note_id**:纪要的唯一标识符,可通过 `vc +detail --meeting-ids` 获取。
|
||||
- **AI 智能纪要(MainDoc)**:AI 生成的会议总结与待办,对应 `note_doc_token`。
|
||||
- **逐字稿(VerbatimDoc)**:会议的逐句发言记录,含说话人和时间戳,对应 `verbatim_doc_token`。
|
||||
- **共享文档(SharedDoc)**:会中投屏共享的文档,对应 `shared_doc_tokens`。
|
||||
|
||||
## 核心场景
|
||||
|
||||
### 1. 通过 note_id 获取纪要文档 Token
|
||||
|
||||
1. 当用户已有 `note_id`,需要获取对应的 `note_doc_token`、`verbatim_doc_token` 或 `shared_doc_tokens` 时,使用 `note +detail`。
|
||||
2. `note_id` 通常来自 `vc +detail` 的返回结果。
|
||||
3. 获取到文档 Token 后,可使用 `docs +fetch --api-version v2` 读取文档内容,或使用 `drive metas batch_query` 获取文档元信息。
|
||||
|
||||
```bash
|
||||
# 1. 从会议获取 note_id
|
||||
lark-cli vc +detail --meeting-ids <meeting_id>
|
||||
|
||||
# 2. 用 note_id 拿文档 Token
|
||||
lark-cli note +detail --note-id <note_id>
|
||||
|
||||
# 3. 读取纪要文档内容
|
||||
lark-cli docs +fetch --api-version v2 --doc <note_doc_token> --doc-format markdown
|
||||
```
|
||||
@@ -1,9 +1,11 @@
|
||||
# note +detail
|
||||
|
||||
`note +detail` 只做一件事:按显式 `note_id` 返回纪要展示类型和相关文档 token。
|
||||
通过 `note_id` 查询会议纪要详情,获取下挂文档 Token(AI 智能纪要、逐字稿、会中共享文档)。只读,仅支持 `--as user`。
|
||||
|
||||
## 命令
|
||||
|
||||
```bash
|
||||
lark-cli note +detail --note-id NOTE_ID --format json
|
||||
lark-cli note +detail --note-id <note_id>
|
||||
```
|
||||
|
||||
## `note_id` 来源
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# note +transcript
|
||||
|
||||
只在 `note +detail` 或 `vc +notes` 已确认 `note_display_type=unified` 时使用。普通纪要逐字稿是独立 Docx 文档,应回到 [lark-doc](../../lark-doc/SKILL.md) 读取 `verbatim_doc_token`。
|
||||
只在 `note +detail` 已确认 `note_display_type=unified` 时使用。普通纪要逐字稿是独立 Docx 文档,应回到 [lark-doc](../../lark-doc/SKILL.md) 读取 `verbatim_doc_token`。
|
||||
|
||||
```bash
|
||||
lark-cli note +transcript --note-id NOTE_ID
|
||||
|
||||
@@ -114,7 +114,7 @@ Shortcut 是对常用操作的高级封装(`lark-cli vc +<verb> [flags]`)。
|
||||
## 延伸
|
||||
|
||||
- 查已结束会议、参会人快照、搜索历史会议 → [`lark-vc`](../lark-vc/SKILL.md)
|
||||
- 会议纪要、逐字稿 → [`lark-vc`](../lark-vc/SKILL.md) 的 `+notes`
|
||||
- 妙记产物(AI 总结 / 转写 / 章节)→ [`lark-minutes`](../lark-minutes/SKILL.md)
|
||||
- 已知 `note_id` 直查会议纪要 / 逐字稿 → [`lark-note`](../lark-note/SKILL.md)(先 `vc +detail` 拿 `note_id`,再 `note +detail` / `note +transcript`)
|
||||
- 妙记产物(AI 总结 / 转写 / 章节 / 待办)→ [`lark-minutes`](../lark-minutes/SKILL.md)(先 `vc +detail` 拿 `minute_token`,再 `minutes +detail`)
|
||||
- 会后把产物发到群 / 私聊 → [`lark-im`](../lark-im/SKILL.md)
|
||||
- 认证、身份切换、scope 管理 → [`lark-shared`](../lark-shared/SKILL.md)
|
||||
|
||||
@@ -24,17 +24,17 @@ metadata:
|
||||
|
||||
```bash
|
||||
# BAD — 查昨天的会议用 calendar,会漏掉即时会议
|
||||
lark-cli calendar events search_event --query "站会" --start-time ...
|
||||
lark-cli calendar +search-event --query "站会" --start <start_time> --end <end_time>
|
||||
|
||||
# GOOD — 查已结束的会议用 vc +search
|
||||
lark-cli vc +search --query "站会" --start-time ...
|
||||
lark-cli vc +search --query "站会" --start <start_time> --end <end_time>
|
||||
```
|
||||
|
||||
## Shortcuts (推荐优先使用)
|
||||
|
||||
| Shortcut | 说明 |
|
||||
|----------|------|
|
||||
| [`+search`](references/lark-vc-search.md) | 搜索历史会议记录(需至少一个筛选条件) |
|
||||
| [`+search`](references/lark-vc-search.md) | 搜索历史会议记录(需至关键词、时间范围、组织者、参与者、会议室少一个筛选条件) |
|
||||
| [`+detail`](references/lark-vc-detail.md) | 通过 meeting-ids 获取会议详情,包括 note_id 和 minute_token |
|
||||
| [`+recording`](references/lark-vc-recording.md) | 通过 meeting-ids 或 calendar-event-ids 查询 minute_token |
|
||||
|
||||
@@ -58,7 +58,7 @@ lark-cli vc +search --query "站会" --start-time ...
|
||||
- **会议纪要(Note)**:视频会议结束后生成的结构化文档,通过 `note_id` 标识,包含纪要文档(总结、待办)和逐字稿文档。`note_display_type` 区分**普通纪要(`normal`)**和 **unified 纪要**;已知 `note_id` 的直查与 unified 原始记录请用 [lark-note](../lark-note/SKILL.md)。
|
||||
- **妙记(Minutes)**:来源于飞书视频会议的录制产物或用户上传的音视频文件,支持视频/音频的转写,包含总结、待办、章节和文字记录,通过 minute_token 标识。
|
||||
- **纪要文档(MainDoc)**:AI 智能纪要的主文档,包含 AI 生成的总结和待办,对应 `note_doc_token`。
|
||||
- **用户会议纪要(MeetingNotes)**:用户主动绑定到会议的纪要文档,对应 `meeting_notes`。仅通过 `--calendar-event-ids` 路径返回。
|
||||
- **用户会议纪要(MeetingNotes)**:用户主动绑定到日程的纪要文档,对应 `meeting_note`。需先通过 [`calendar +meeting`](../lark-calendar/references/lark-calendar-meeting.md) 由 `event_id` 获取。
|
||||
- **逐字稿(VerbatimDoc)**:会议的逐句文字记录,包含说话人和时间戳。
|
||||
|
||||
## 产物选择决策
|
||||
@@ -71,7 +71,7 @@ lark-cli vc +search --query "站会" --start-time ...
|
||||
| 直接看 AI 总结结果 | AI 纪要(`note_doc_token`) | — |
|
||||
| 谁说了什么/完整发言记录 | 原始对话记录(按下方逐字稿路由取得) | — |
|
||||
|
||||
> **逐字稿路由**:先看 `vc +notes` 返回的 `note_display_type`,不要只看 `verbatim_doc_token` 是否为空。具体路由以 [`+notes`](references/lark-vc-notes.md) 和 [lark-note](../lark-note/SKILL.md) 为准。
|
||||
> **逐字稿路由**:先用 `vc +detail` 拿到 `note_id`,再 [`note +detail`](../lark-note/SKILL.md) 看 `note_display_type`,**不要只看 `verbatim_doc_token` 是否为空**。具体路由以 [lark-note](../lark-note/SKILL.md) 的 `note_display_type` 规则为准。
|
||||
>
|
||||
> **为什么"提炼/总结"必须从原始对话记录出发?** AI 纪要是模型对会议的二次压缩,可能遗漏讨论细节、争论过程和隐含决策。用户要求"提炼"或"重新总结"时,期望的是基于原始对话的独立分析,而非对 AI 产物的重新排版。
|
||||
|
||||
@@ -102,10 +102,10 @@ lark-cli docs +media-download --type whiteboard --token <whiteboard_token> --out
|
||||
> **产物目录规范**:同一会议的所有下载产物(录像、逐字稿、封面图等)统一放到 `./minutes/{minute_token}/` 目录下。这与 `minutes +download` 和 `minutes +detail --minute-tokens` 的默认落点保持一致,便于 Agent 聚合。显式路径(如封面图)需手动对齐到同一目录。
|
||||
|
||||
> **纪要相关文档 — 根据用户意图选择:**
|
||||
> - `note_doc_token` → **AI 智能纪要**(AI 总结 + 待办)
|
||||
> - `meeting_notes` → **用户绑定的会议纪要**(用户主动关联到会议的文档,仅 `--calendar-event-ids` 路径返回)
|
||||
> - 用户说"逐字稿""完整记录""谁说了什么"时 → 按 `note_display_type` 路由,详见 [`+notes`](references/lark-vc-notes.md)
|
||||
> - 用户说"纪要""总结""纪要内容"时,应同时返回 `note_doc_token` 和 `meeting_notes`(如有)
|
||||
> - `note_doc_token` → **AI 智能纪要**(AI 总结 + 待办),由 `note +detail --note-id <note_id>` 返回
|
||||
> - `meeting_note` → **用户绑定到日程的会议纪要**,由 [`calendar +meeting --event-ids <event_id>`](../lark-calendar/references/lark-calendar-meeting.md) 返回
|
||||
> - 用户说"逐字稿""完整记录""谁说了什么"时 → 按 `note_display_type` 路由,详见 [lark-note](../lark-note/SKILL.md)
|
||||
> - 用户说"纪要""总结""纪要内容"时,应同时返回 `note_doc_token` 和 `meeting_note`(如有)
|
||||
> - 用户意图不明确时,应展示所有文档链接让用户选择,而不是替用户决定
|
||||
> - 如果用户提供的是**本地音视频文件**并说"转纪要""转逐字稿",不要直接从 `vc +detail` 开始;应先用 [minutes +upload](../lark-minutes/references/lark-minutes-upload.md) 生成 `minute_url`,再提取 `minute_token` 调用 `minutes +detail --minute-tokens`
|
||||
|
||||
@@ -152,7 +152,7 @@ Meeting (视频会议)
|
||||
│ ├── VerbatimDoc (逐字稿, verbatim_doc_token) ← normal 路径
|
||||
│ ├── UnifiedTranscript (unified 原始记录) ← unified 路径,note +transcript(lark-note)
|
||||
│ └── SharedDoc (会中共享文档)
|
||||
└── Minutes (妙记) ← minute_token 标识,+recording 从 meeting_id 获取
|
||||
└── Minutes (妙记) ← minute_token 标识,由 `vc +detail` 或 `vc +recording` 桥接获取,产物详情走 [lark-minutes](../lark-minutes/SKILL.md)
|
||||
├── Transcript (文字记录)
|
||||
├── Summary (总结)
|
||||
├── Todos (待办)
|
||||
@@ -160,12 +160,16 @@ Meeting (视频会议)
|
||||
└── Keywords (推荐关键词)
|
||||
```
|
||||
|
||||
> **妙记边界**:`+notes` 负责纪要内容、逐字稿和 AI 产物;妙记基础信息请优先看 [`+recording`](references/lark-vc-recording.md) 与 [lark-minutes](../lark-minutes/SKILL.md)。
|
||||
> **MeetingNotes 边界**:用户绑定到日程的会议纪要文档(`meeting_note`)属于日程域,不在 VC 资源关系内;从 `event_id` 用 [`calendar +meeting`](../lark-calendar/references/lark-calendar-meeting.md) 获取。
|
||||
>
|
||||
> **Note 域边界**:`vc +notes` 是从**会议线索**(`meeting_id` / `calendar_event_id` / `minute_token`)定位纪要的入口,返回 `note_id` 和 `note_display_type`。
|
||||
> - 已有 `note_id` → [lark-note](../lark-note/SKILL.md)。
|
||||
> **妙记边界**:`+recording` 仅负责把 `meeting_id` / `calendar_event_id` 桥接到 `minute_token`;妙记的总结/待办/章节/逐字稿等产物归 [lark-minutes](../lark-minutes/SKILL.md)(`minutes +detail`)。
|
||||
>
|
||||
> **Note 域边界**:VC 域只负责把 `meeting_id` 转成 `note_id` / `minute_token`,纪要详情归 [lark-note](../lark-note/SKILL.md)。
|
||||
> - 入口选择:从 `meeting_id` 出发用 `vc +detail` 拿 `note_id` 和 `minute_token`;从 `minute_token` 出发用 [`minutes +detail`](../lark-minutes/references/lark-minutes-detail.md) 也会返回关联的 `note_id`,可继续走 `note +detail` 拿纪要文档 token。
|
||||
> - 已有 `note_id` → 直接走 [`note +detail`](../lark-note/SKILL.md) / [`note +transcript`](../lark-note/SKILL.md),不要绕回 VC。
|
||||
> - 已有 `doc_token` 且目标是读正文 → [lark-doc](../lark-doc/SKILL.md)。
|
||||
> - 只有自然语言纪要标题 → 文档搜索 / Docx 正文读取;有显式 `vc-node-id` 才进入 [lark-note](../lark-note/SKILL.md)。
|
||||
> - 从日程出发(只有 `event_id`)→ 先走 [`calendar +meeting`](../lark-calendar/references/lark-calendar-meeting.md) 拿到 `meeting_id` 或 `meeting_note`,再按上述路径继续。
|
||||
|
||||
## API Resources
|
||||
|
||||
@@ -187,12 +191,12 @@ lark-cli vc meeting get --params '{"meeting_id": "<meeting_id>", "with_participa
|
||||
|
||||
### minutes(跨域,详见 [lark-minutes](../lark-minutes/SKILL.md))
|
||||
|
||||
- `get` — 获取妙记基础信息(标题、时长、封面);查询妙记**内容**请用 `+notes --minute-tokens <minute-token>`
|
||||
- `get` — 获取妙记基础信息(标题、时长、封面);查询妙记**内容**(总结/待办/章节/逐字稿)请用 [`minutes +detail`](../lark-minutes/references/lark-minutes-detail.md)
|
||||
|
||||
## 不在本 skill 范围
|
||||
|
||||
- 查询未来的会议日程 → [lark-calendar](../lark-calendar/SKILL.md)
|
||||
- Agent 真实入会/离会、会中实时事件 → [lark-vc-agent](../lark-vc-agent/SKILL.md)
|
||||
- 只有纪要文档标题的逐字稿查询 → 文档搜索 / Docx 正文读取;有显式 `vc-node-id` 才进入 [lark-note](../lark-note/SKILL.md)
|
||||
- 本地音视频文件转纪要/逐字稿 → [lark-minutes](../lark-minutes/SKILL.md)(上传后使用 `minutes +detail`)
|
||||
- 妙记搜索/下载/上传/重命名/替换说话人 → [lark-minutes](../lark-minutes/SKILL.md)
|
||||
- 本地音视频文件转纪要/逐字稿、妙记搜索/下载/上传/重命名/替换说话人 → [lark-minutes](../lark-minutes/SKILL.md)
|
||||
- 通过 `note_id` 取纪要文档 Token → [lark-note](../lark-note/SKILL.md)
|
||||
|
||||
@@ -1,28 +1,16 @@
|
||||
|
||||
# vc +detail
|
||||
|
||||
> **前置条件:** 先阅读 [`../lark-shared/SKILL.md`](../../lark-shared/SKILL.md) 了解认证、全局参数和安全规则。
|
||||
|
||||
通过会议 ID 获取会议详情,包括会议基本信息、关联的纪要 ID(`note_id`)和妙记 Token(`minute_token`)。只读操作。
|
||||
|
||||
本 skill 对应 shortcut:`lark-cli vc +detail`。
|
||||
通过会议 ID 获取会议详情,包括基本信息、关联的纪要 ID(`note_id`)和妙记 Token(`minute_token`)。只读。
|
||||
|
||||
## 命令
|
||||
|
||||
```bash
|
||||
# 批量查询(逗号分隔,最多 50 个)
|
||||
lark-cli vc +detail --meeting-ids 69xxxxxxxxxxxxx28,69xxxxxxxxxxxxx29
|
||||
# 单个 / 批量(逗号分隔,最多 50 个)
|
||||
lark-cli vc +detail --meeting-ids <meeting_id1>,<meeting_id2>
|
||||
```
|
||||
|
||||
## 参数
|
||||
|
||||
| 参数 | 必填 | 说明 |
|
||||
|------|------|------|
|
||||
| `--meeting-ids <ids>` | 是 | 会议 ID,逗号分隔支持批量,最多 50 个 |
|
||||
|
||||
## 输出结果
|
||||
|
||||
返回 `meetings` 数组,每条记录包含:
|
||||
## 输出字段
|
||||
|
||||
| 字段 | 说明 |
|
||||
|------|------|
|
||||
@@ -42,7 +30,7 @@ lark-cli vc +detail --meeting-ids 69xxxxxxxxxxxxx28,69xxxxxxxxxxxxx29
|
||||
|
||||
```bash
|
||||
# 1. 获取会议详情,拿到 note_id 和 minute_token
|
||||
lark-cli vc +detail --meeting-ids 69xxxxxxxxxxxxx28
|
||||
lark-cli vc +detail --meeting-ids <meeting_id>
|
||||
|
||||
# 2. 用 note_id 获取纪要文档 Token(note_doc_token / verbatim_doc_token / shared_doc_tokens)
|
||||
lark-cli note +detail --note-id <note_id>
|
||||
@@ -54,18 +42,3 @@ lark-cli minutes +detail --minute-tokens <minute_token> --todo --transcript
|
||||
```
|
||||
|
||||
> **路由建议**:当用户未明确指定使用妙记时,**优先**走 `note +detail` 链路(纪要文档信息更完整、含逐字稿原文),仅在 `note_id` 为空或用户要求妙记产物时才走 `minutes +detail`。
|
||||
|
||||
## 与其他命令的关系
|
||||
|
||||
| 需求 | 推荐命令 |
|
||||
|------|---------|
|
||||
| 只需获取 note_id 和 minute_token | `vc +detail` |
|
||||
| 需要纪要文档 Token(note_doc_token、verbatim_doc_token 等) | `note +detail --note-id` |
|
||||
| 需要妙记的 AI 产物(总结、待办、章节、逐字稿) | `minutes +detail --minute-tokens` |
|
||||
|
||||
## 参考
|
||||
|
||||
- [lark-vc](../SKILL.md) — 视频会议全部命令
|
||||
- [lark-note-detail](../../lark-note/references/lark-note-detail.md) — 查询纪要详情命令介绍
|
||||
- [lark-minutes-detail](../../lark-minutes/references/lark-minutes-detail.md) — 查询妙记详情命令介绍
|
||||
- [lark-shared](../../lark-shared/SKILL.md) — 认证和全局参数
|
||||
|
||||
@@ -1,148 +0,0 @@
|
||||
|
||||
# vc +notes
|
||||
|
||||
> **前置条件:** 先阅读 [`../lark-shared/SKILL.md`](../../lark-shared/SKILL.md) 了解认证、全局参数和安全规则。
|
||||
|
||||
查询会议纪要,支持通过会议 ID、妙记 Token 或日程事件 ID 获取纪要文档、逐字稿、AI 总结、待办和章节。只读操作。
|
||||
|
||||
本 skill 对应 shortcut:`lark-cli vc +notes`。
|
||||
|
||||
## 命令
|
||||
|
||||
```bash
|
||||
# 通过会议 ID 查询(逗号分隔支持批量,最多 50 个)
|
||||
lark-cli vc +notes --meeting-ids 69xxxxxxxxxxxxx28
|
||||
lark-cli vc +notes --meeting-ids 69xxxxxxxxxxxxx28,69xxxxxxxxxxxxx29
|
||||
|
||||
# 通过妙记 Token 查询(从妙记 URL 中提取)
|
||||
lark-cli vc +notes --minute-tokens obbxxxxxxxxxxxxxxxxxx
|
||||
lark-cli vc +notes --minute-tokens obbxxxxxxxxxxxxxxxxxx,obbyyyyyyyyyyyyyyyyyy
|
||||
|
||||
# 指定逐字稿输出目录(仅 --minute-tokens 路径有效)
|
||||
lark-cli vc +notes --minute-tokens obbxxxxxxxxxxxxxxxxxx --output-dir ./output
|
||||
lark-cli vc +notes --minute-tokens obbxxxxxxxxxxxxxxxxxx --overwrite
|
||||
|
||||
# 通过日程事件 ID 查询(从 calendar +agenda 获取 event_id)
|
||||
lark-cli vc +notes --calendar-event-ids xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx_0
|
||||
|
||||
# 输出格式
|
||||
lark-cli vc +notes --meeting-ids 69xxxxxxxxxxxxx28 --format json
|
||||
|
||||
# 预览 API 调用
|
||||
lark-cli vc +notes --meeting-ids 69xxxxxxxxxxxxx28 --dry-run
|
||||
```
|
||||
|
||||
## 参数
|
||||
|
||||
| 参数 | 必填 | 说明 |
|
||||
|------|------|------|
|
||||
| `--meeting-ids <ids>` | 三选一 | 会议 ID,逗号分隔支持批量 |
|
||||
| `--minute-tokens <tokens>` | 三选一 | 妙记 Token,逗号分隔支持批量 |
|
||||
| `--calendar-event-ids <ids>` | 三选一 | 日程事件 ID,逗号分隔支持批量 |
|
||||
| `--output-dir <dir>` | 否 | 逐字稿输出目录。未指定时默认落到 `./minutes/{minute_token}/transcript.txt`(与 `minutes +download` 共享目录);显式指定时沿用旧布局 `./{output-dir}/artifact-{title}-{token}/transcript.txt`。仅 `--minute-tokens` 路径有效 |
|
||||
| `--overwrite` | 否 | 覆盖已存在的逐字稿文件,仅 `--minute-tokens` 路径有效 |
|
||||
| `--dry-run` | 否 | 预览 API 调用,不执行 |
|
||||
|
||||
## 核心约束
|
||||
|
||||
### 1. 三种参数互斥
|
||||
|
||||
每次只能指定一种输入方式。同时传入多种会报错。
|
||||
|
||||
### 2. 仅支持 user 身份
|
||||
|
||||
该命令仅支持 `user` 身份,使用前需完成 `lark-cli auth login`。
|
||||
|
||||
### 3. 批量上限
|
||||
|
||||
每次最多传入 50 个 ID/Token。
|
||||
|
||||
### 4. 按路径检查权限
|
||||
|
||||
不同输入方式需要不同权限,命令会自动检查对应路径所需的 scope:
|
||||
|
||||
| 输入 | 所需权限 |
|
||||
|------|---------|
|
||||
| `--meeting-ids` | `vc:meeting.meetingevent:read`、`vc:note:read`、`vc:record:readonly` |
|
||||
| `--minute-tokens` | `vc:note:read`、`minutes:minutes:readonly`、`minutes:minutes.artifacts:read`、`minutes:minutes.transcript:export` |
|
||||
| `--calendar-event-ids` | `calendar:calendar:read`、`calendar:calendar.event:read`、`vc:meeting.meetingevent:read`、`vc:note:read`、`vc:record:readonly` |
|
||||
|
||||
## 输出结果
|
||||
|
||||
### 有纪要文档时
|
||||
|
||||
返回 `notes` 数组,每条记录包含:
|
||||
|
||||
| 字段 | 说明 |
|
||||
|------|------|
|
||||
| `meeting_id` | 会议 ID(`--meeting-ids` / `--calendar-event-ids` 路径) |
|
||||
| `minute_token` | **会议对应的妙记 Token**(`--meeting-ids` / `--calendar-event-ids` 路径自动通过录制 API 反查并附加)|
|
||||
| `note_id` | **纪要 ID** — 用于继续进入 Note 域(`note +detail` / `note +transcript`) |
|
||||
| `note_display_type` | **纪要展示类型**:`unknown` / `normal` / `unified`,区分普通纪要和 unified 纪要 |
|
||||
| `note_doc_token` | **AI 智能纪要**文档 Token — AI 生成的总结、待办、章节 |
|
||||
| `meeting_notes` | **用户绑定的会议纪要**文档 Token 列表 — 用户主动关联到会议的文档(仅 `--calendar-event-ids` 路径返回) |
|
||||
| `verbatim_doc_token` | **逐字稿**文档 Token — 完整的逐句文字记录,含说话人和时间戳;unified 纪要的逐字稿请改用 `note +transcript` |
|
||||
| `shared_doc_tokens` | 会中共享文档 Token 列表 |
|
||||
| `creator_id` | 创建者 ID |
|
||||
| `create_time` | 创建时间(格式化) |
|
||||
|
||||
> **选择哪个 token?** 用户说"会议纪要""总结""待办""纪要内容" → 返回 `note_doc_token` 和 `meeting_notes`(如有)。用户说"逐字稿""完整记录""谁说了什么" → 见下方「按 `note_display_type` 路由逐字稿」。意图不明确时,展示所有文档链接让用户选择。
|
||||
>
|
||||
> 📌 不确定该返回哪个 token?参见 [`vc-domain-boundaries.md`](vc-domain-boundaries.md) 的产物链路对比表,了解 AI 总结链路 vs 录制链路的区别。
|
||||
|
||||
### 按 `note_display_type` 路由逐字稿 / 原始记录
|
||||
|
||||
逐字稿走哪条路由由 `note_display_type` 决定,**不要只看 `verbatim_doc_token` 是否为空**:
|
||||
|
||||
| 字段 / 条件 | Agent 动作 |
|
||||
|------------|-----------|
|
||||
| 用户要纪要正文 / 总结 / 待办 / 章节 | `docs +fetch --api-version v2 --doc <note_doc_token>` |
|
||||
| `note_display_type=normal` + 用户要逐字稿 | `docs +fetch --api-version v2 --doc <verbatim_doc_token>` |
|
||||
| `note_display_type=unknown` + `verbatim_doc_token` 非空 + 用户要逐字稿 | `docs +fetch --api-version v2 --doc <verbatim_doc_token>`;不要猜成 unified |
|
||||
| `note_display_type=unknown` + 无可用逐字稿 token | 先 `note +detail --note-id <note_id>` 复核,再按返回的展示类型路由 |
|
||||
| `note_display_type=unified` + 用户要逐字稿 / 原始记录 | `note +transcript --note-id <note_id>` → 切到 [lark-note](../../lark-note/SKILL.md) |
|
||||
| `minute_token` 存在 + 用户要音视频媒体 | `minutes +download --minute-tokens <minute_token>` |
|
||||
|
||||
> **`unified` 纪要的逐字稿不是独立文档**,必须用 `note +transcript` 按 `note_id` 拉取,输出更结构化。即使 unified 也返回了非空 `verbatim_doc_token`,仍以 `note_display_type` 为准。
|
||||
|
||||
### minute-tokens 路径的 AI 产物
|
||||
|
||||
通过 `--minute-tokens` 查询时,返回的 `artifacts` 字段包含 AI 内置产物:
|
||||
|
||||
| 字段 | 说明 |
|
||||
|------|------|
|
||||
| `artifacts.summary` | AI 总结(JSON 内联) |
|
||||
| `artifacts.todos` | 待办事项(JSON 内联,**只读**);每条含 `content`、`is_done` 及 `todo_id`。`todo_id` 仅供 [`minutes +todo`](../../lark-minutes/references/lark-minutes-todo.md) 更新/删除待办时使用,不必展示给用户。**新建**妙记内待办请用 `minutes +todo`,不要用 lark-task |
|
||||
| `artifacts.chapters` | 章节纪要(JSON 内联) |
|
||||
| `artifacts.keywords` | 妙记推荐关键词(JSON 内联) |
|
||||
| `artifacts.transcript_file` | 逐字稿本地文件路径。默认落到 `./minutes/{minute_token}/transcript.txt`(与 `minutes +download` 聚合);显式 `--output-dir` 时走旧布局 `./{output-dir}/artifact-{title}-{token}/transcript.txt` |
|
||||
|
||||
## 如何获取输入参数
|
||||
|
||||
| 输入参数 | 获取方式 |
|
||||
|---------|---------|
|
||||
| `meeting_id` | `vc +search` 搜索历史会议 → 结果中的 `id` 字段 |
|
||||
| `minute_token` | 从妙记 URL 中提取,如 `https://sample.feishu.cn/minutes/obbyyyyyyyyyyyyyyyyyy` → `obbyyyyyyyyyyyyyyyyyy` |
|
||||
| `calendar_event_id` | `calendar +agenda` 查看日程 → 结果中的 `event_id` 字段 |
|
||||
|
||||
## 常见错误与排查
|
||||
|
||||
| 错误现象 | 根本原因 | 解决方案 |
|
||||
|---------|---------|---------|
|
||||
| `exactly one of ... is required` | 未传入参数或同时传了多种 | 只指定一种输入方式 |
|
||||
| `no notes available for this meeting` | 该会议未生成纪要 | 尝试用 `--minute-tokens` 路径 |
|
||||
| `121005 no permission` | 非会议参与者无权查看 | 使用 `--minute-tokens` 降级到内置产物 |
|
||||
| `missing required scope(s)` | 权限不足 | 按提示运行 `auth login --scope` |
|
||||
| `too many IDs` | 超过批量上限 | 分批查询,每批最多 50 个 |
|
||||
|
||||
## 提示
|
||||
- 默认使用 `--format json` 输出,你更佳擅长解析 JSON 数据。
|
||||
- 排查参数与请求结构时优先使用 `--dry-run`。
|
||||
- `--meeting-ids` 和 `--calendar-event-ids` 路径最终都走纪要详情 API,需要 `vc:note:read` 权限。
|
||||
- `--minute-tokens` 路径无纪要权限时会自动降级,**不会报错**,而是下载内置产物到本地。
|
||||
|
||||
## 参考
|
||||
|
||||
- [lark-vc](../SKILL.md) — 视频会议全部命令
|
||||
- [lark-vc-search](lark-vc-search.md) — 搜索历史会议(获取 meeting_id)
|
||||
- [lark-shared](../../lark-shared/SKILL.md) — 认证和全局参数
|
||||
@@ -1,11 +1,7 @@
|
||||
|
||||
# vc +search
|
||||
|
||||
> **前置条件:** 先阅读 [`../lark-shared/SKILL.md`](../../lark-shared/SKILL.md) 了解认证、全局参数和安全规则。
|
||||
|
||||
搜索已结束的历史会议记录,支持关键词、时间范围、组织者、参与者以及会议室等多条件过滤。只读操作,不修改任何会议数据。
|
||||
|
||||
本 skill 对应 shortcut:`lark-cli vc +search`(调用 `POST /open-apis/vc/v1/meetings/search`)。
|
||||
搜索已结束的历史会议记录,支持关键词、时间范围、组织者、参与者、会议室多条件过滤。只读,仅 `--as user`。
|
||||
|
||||
## 关键词使用边界
|
||||
|
||||
@@ -35,37 +31,17 @@ lark-cli vc +search --start 2026-03-10 --end 2026-03-10
|
||||
|
||||
# 按时间范围搜索
|
||||
lark-cli vc +search --start "2026-03-10T00:00+08:00" --end "2026-03-17T00:00+08:00"
|
||||
lark-cli vc +search --start 2026-03-10 --end 2026-03-17
|
||||
|
||||
# 关键词 + 时间范围
|
||||
lark-cli vc +search --query "周会" --start "2026-03-10T00:00+08:00" --end "2026-03-17T00:00+08:00"
|
||||
lark-cli vc +search --query "周会" --start "2026-03-10T00:00+08:00"
|
||||
lark-cli vc +search --query "周会" --end "2026-03-17T00:00+08:00"
|
||||
|
||||
# 按组织者过滤(open_id,逗号分隔)
|
||||
lark-cli vc +search --organizer-ids "ou_a,ou_b"
|
||||
|
||||
# 按参与者过滤(open_id,逗号分隔)
|
||||
lark-cli vc +search --participant-ids "ou_x,ou_y"
|
||||
|
||||
# 查询我这个月参加过的历史会议,不带关键词
|
||||
lark-cli vc +search --start "<YYYY-MM-DD>" --end "<YYYY-MM-DD>" --participant-ids "ou_me"
|
||||
|
||||
# 查询最近两周我组织的历史会议,不带关键词
|
||||
lark-cli vc +search --start "<YYYY-MM-DD>" --end "<YYYY-MM-DD>" --organizer-ids "ou_me"
|
||||
|
||||
# 按会议室过滤
|
||||
# 按组织者 / 参与者 / 会议室(逗号分隔)
|
||||
lark-cli vc +search --organizer-ids "ou_user1,ou_user2"
|
||||
lark-cli vc +search --participant-ids "ou_user1,ou_user2"
|
||||
lark-cli vc +search --room-ids "123,456"
|
||||
|
||||
# 多条件组合查询
|
||||
lark-cli vc +search --organizer-ids "ou_a" --room-ids "123" --start "2026-03-10T00:00+08:00"
|
||||
# 多条件组合
|
||||
lark-cli vc +search --organizer-ids "ou_user1" --room-ids "123" --start "2026-03-10T00:00+08:00"
|
||||
|
||||
# 分页查询
|
||||
lark-cli vc +search --query "周会" --page-size 15
|
||||
lark-cli vc +search --query "周会" --page-token "next_page_token"
|
||||
|
||||
# 输出为表格/可读格式
|
||||
lark-cli vc +search --query "周会" --format json
|
||||
# 翻页
|
||||
lark-cli vc +search --query "周会" --page-token "<PAGE_TOKEN>"
|
||||
```
|
||||
|
||||
## 参数
|
||||
@@ -185,9 +161,3 @@ lark-cli minutes minutes get --params '{"minute_token":"<MINUTE_TOKEN>"}'
|
||||
- 不要使用 `yesterday`、`today` 这类相对时间字面量;请先转换成明确日期,例如 `2026-03-10`。
|
||||
- 用户如果明确问的是“妙记信息”而不是“纪要内容”,不要默认走 `vc +detail`;应先用 `vc +recording`。
|
||||
|
||||
## 参考
|
||||
|
||||
- [lark-vc](../SKILL.md) -- 视频会议全部命令
|
||||
- [lark-vc-detail](lark-vc-detail.md) -- 获取会议详情
|
||||
- [lark-vc-recording](lark-vc-recording.md) -- 基于 meeting_id 查询 minute_token
|
||||
- [lark-shared](../../lark-shared/SKILL.md) -- 认证和全局参数
|
||||
|
||||
@@ -30,7 +30,7 @@
|
||||
| 逐字稿 | `verbatim_doc_token` | 飞书文档 | 完整的逐句发言记录(含说话人、时间戳)— **仅 `note_display_type=normal` 时是可读的独立文档**;`unified` 纪要的逐字稿用 `note +transcript --note-id <note_id>` 拉取(见下方 [Note 域](#note-域)) |
|
||||
| 共享文档 | `shared_doc_token` | 飞书文档 | 会中投屏共享的文档信息 |
|
||||
|
||||
此外,还存在**用户会议纪要(MeetingNotes)**,对应 `meeting_notes` 字段。这是用户主动绑定到会议的纪要文档,通常用于会前记录会议相关内容,与智能纪要文档相互独立。仅通过 `+notes --calendar-event-ids` 路径返回。
|
||||
此外,还存在**用户会议纪要(MeetingNotes)**,对应 `meeting_note` 字段。这是用户主动绑定到日程的纪要文档,通常用于会前记录会议相关内容,与智能纪要文档相互独立。仅通过 [`calendar +meeting --event-ids`](../../lark-calendar/references/lark-calendar-meeting.md) 路径返回。
|
||||
|
||||
#### 链路二:开启「录制」
|
||||
|
||||
@@ -153,8 +153,10 @@ lark-cli note +transcript --note-id <note_id>
|
||||
|
||||
## Note 域
|
||||
|
||||
- VC 只负责从 `meeting_id` / `calendar_event_id` / `minute_token` 定位会议产物和 `note_id`。
|
||||
- VC 只负责从 `meeting_id` 定位会议产物和 `note_id` / `minute_token`([`vc +detail`](lark-vc-detail.md))。
|
||||
- 已知 `note_id` 后切到 [lark-note](../../lark-note/SKILL.md);逐字稿路由以 `lark-note` 的 `note_display_type` 规则为准。
|
||||
- 已知 `minute_token` 时,[`minutes +detail`](../../lark-minutes/references/lark-minutes-detail.md) 顶层会一并返回该妙记关联的 `note_id`(如有);可直接传给 `note +detail` 取纪要文档 token,无需绕回 VC。
|
||||
- 仅有日程 `event_id` 时,先走 [`calendar +meeting`](../../lark-calendar/references/lark-calendar-meeting.md) 拿到 `meeting_id` 或用户绑定的 `meeting_note`,再按上述路径继续。
|
||||
- 只有自然语言纪要标题时,先走文档搜索与 `docs +fetch --api-version v2`;只有 `<vc-transcribe-tab vc-node-id="...">` 的 `vc-node-id` 可以进入 Note 域。
|
||||
- `doc_token` / Docx URL 不是 `note_id`。没有 `vc-node-id` 时不要反推 Note,继续按 Doc 域读取正文或正文中明确给出的逐字稿文档。
|
||||
|
||||
|
||||
Reference in New Issue
Block a user