Documentation
¶
Index ¶
Constants ¶
This section is empty.
Variables ¶
View Source
var VCNotes = common.Shortcut{ Service: "vc", Command: "+notes", Description: "Query meeting notes (via meeting-ids, minute-tokens, or calendar-event-ids)", Risk: "read", Scopes: []string{"vc:note:read"}, AuthTypes: []string{"user"}, HasFormat: true, Flags: []common.Flag{ {Name: "meeting-ids", Desc: "meeting IDs, comma-separated for batch"}, {Name: "minute-tokens", Desc: "minute tokens, comma-separated for batch"}, {Name: "calendar-event-ids", Desc: "calendar event instance IDs, comma-separated for batch"}, {Name: "output-dir", Desc: "output directory for artifact files (default: current dir)"}, {Name: "overwrite", Type: "bool", Desc: "overwrite existing artifact files"}, }, Validate: func(ctx context.Context, runtime *common.RuntimeContext) error { if err := common.ExactlyOne(runtime, "meeting-ids", "minute-tokens", "calendar-event-ids"); err != nil { return err } // batch input size limit const maxBatchSize = 50 for _, flag := range []string{"meeting-ids", "minute-tokens", "calendar-event-ids"} { if v := runtime.Str(flag); v != "" { if ids := common.SplitCSV(v); len(ids) > maxBatchSize { return output.ErrValidation("--%s: too many IDs (%d), maximum is %d", flag, len(ids), maxBatchSize) } } } if outDir := runtime.Str("output-dir"); outDir != "" { if err := common.ValidateSafeOutputDir(outDir); err != nil { return err } } // dynamic scope check based on which flag is provided var required []string switch { case runtime.Str("meeting-ids") != "": required = scopesMeetingIDs case runtime.Str("minute-tokens") != "": required = scopesMinuteTokens case runtime.Str("calendar-event-ids") != "": required = scopesCalendarEventIDs default: } appID := runtime.Config.AppID userOpenId := runtime.UserOpenId() if appID != "" && userOpenId != "" { stored := auth.GetStoredToken(appID, userOpenId) if stored != nil { if missing := auth.MissingScopes(stored.Scope, required); len(missing) > 0 { return output.ErrWithHint(output.ExitAuth, "missing_scope", fmt.Sprintf("missing required scope(s): %s", strings.Join(missing, ", ")), fmt.Sprintf("run `lark-cli auth login --scope \"%s\"` in the background. It blocks and outputs a verification URL — retrieve the URL and open it in a browser to complete login.", strings.Join(missing, " "))) } } } return nil }, DryRun: func(ctx context.Context, runtime *common.RuntimeContext) *common.DryRunAPI { if ids := runtime.Str("meeting-ids"); ids != "" { return common.NewDryRunAPI(). GET("/open-apis/vc/v1/meetings/{meeting_id}"). GET("/open-apis/vc/v1/notes/{note_id}"). Set("meeting_ids", common.SplitCSV(ids)). Set("steps", "meeting.get → note_id → note detail API") } if tokens := runtime.Str("minute-tokens"); tokens != "" { return common.NewDryRunAPI(). GET("/open-apis/minutes/v1/minutes/{minute_token}"). GET("/open-apis/vc/v1/notes/{note_id}"). GET("/open-apis/minutes/v1/minutes/{minute_token}/artifacts"). GET("/open-apis/minutes/v1/minutes/{minute_token}/transcript"). Set("minute_tokens", common.SplitCSV(tokens)). Set("steps", "minutes API → note detail + AI artifacts + transcript") } ids := runtime.Str("calendar-event-ids") return common.NewDryRunAPI(). POST("/open-apis/calendar/v4/calendars/primary"). POST("/open-apis/calendar/v4/calendars/{calendar_id}/events/mget_instance_relation_info"). GET("/open-apis/vc/v1/meetings/{meeting_id}"). GET("/open-apis/vc/v1/notes/{note_id}"). Set("calendar_event_ids", common.SplitCSV(ids)). Set("steps", "primary calendar → mget_instance_relation_info → meeting_id → meeting.get → note detail API") }, Execute: func(ctx context.Context, runtime *common.RuntimeContext) error { errOut := runtime.IO().ErrOut var results []any const batchDelay = 100 * time.Millisecond if ids := runtime.Str("meeting-ids"); ids != "" { meetingIDs := common.SplitCSV(ids) fmt.Fprintf(errOut, "%s querying %d meeting_id(s)\n", logPrefix, len(meetingIDs)) for i, id := range meetingIDs { if err := ctx.Err(); err != nil { return err } if i > 0 { time.Sleep(batchDelay) } fmt.Fprintf(errOut, "%s querying meeting_id=%s ...\n", logPrefix, sanitizeLogValue(id)) results = append(results, fetchNoteByMeetingID(ctx, runtime, id)) } } else if tokens := runtime.Str("minute-tokens"); tokens != "" { minuteTokens := common.SplitCSV(tokens) fmt.Fprintf(errOut, "%s querying %d minute_token(s)\n", logPrefix, len(minuteTokens)) for i, token := range minuteTokens { if err := ctx.Err(); err != nil { return err } if i > 0 { time.Sleep(batchDelay) } fmt.Fprintf(errOut, "%s querying minute_token=%s ...\n", logPrefix, sanitizeLogValue(token)) results = append(results, fetchNoteByMinuteToken(ctx, runtime, token)) } } else { instanceIDs := common.SplitCSV(runtime.Str("calendar-event-ids")) fmt.Fprintf(errOut, "%s querying %d calendar_event_id(s)\n", logPrefix, len(instanceIDs)) calendarID, err := getPrimaryCalendarID(runtime) if err != nil { return err } fmt.Fprintf(errOut, "%s primary calendar: %s\n", logPrefix, calendarID) for i, id := range instanceIDs { if err := ctx.Err(); err != nil { return err } if i > 0 { time.Sleep(batchDelay) } fmt.Fprintf(errOut, "%s querying calendar_event_id=%s ...\n", logPrefix, sanitizeLogValue(id)) results = append(results, fetchNoteByCalendarEventID(ctx, runtime, id, calendarID)) } } successCount := 0 for _, r := range results { m, _ := r.(map[string]any) if m["error"] == nil { successCount++ } } fmt.Fprintf(errOut, "%s done: %d total, %d succeeded, %d failed\n", logPrefix, len(results), successCount, len(results)-successCount) if successCount == 0 && len(results) > 0 { outData := map[string]any{"notes": results} runtime.OutFormat(outData, &output.Meta{Count: len(results)}, nil) return output.ErrAPI(0, fmt.Sprintf("all %d queries failed", len(results)), nil) } outData := map[string]any{"notes": results} runtime.OutFormat(outData, &output.Meta{Count: len(results)}, func(w io.Writer) { var rows []map[string]interface{} for _, r := range results { m, _ := r.(map[string]any) id, _ := m["meeting_id"].(string) if id == "" { id, _ = m["minute_token"].(string) } row := map[string]interface{}{"id": id} if errMsg, _ := m["error"].(string); errMsg != "" { row["status"] = "FAIL" row["error"] = errMsg } else { row["status"] = "OK" if v, _ := m["note_doc_token"].(string); v != "" { row["note_doc"] = v } if v, _ := m["verbatim_doc_token"].(string); v != "" { row["verbatim_doc"] = v } if v, _ := m["shared_doc_tokens"].([]string); len(v) > 0 { row["shared_docs"] = strings.Join(v, ", ") } if v, _ := m["source"].(string); v != "" { row["source"] = v } if v, _ := m["create_time"].(string); v != "" { row["create_time"] = v } if arts, _ := m["artifacts"].(map[string]any); arts != nil { if v, _ := arts["transcript_file"].(string); v != "" { row["transcript"] = v } } } rows = append(rows, row) } output.PrintTable(w, rows) fmt.Fprintf(w, "\n%d note(s), %d succeeded, %d failed\n", len(results), successCount, len(results)-successCount) }) return nil }, }
VCNotes queries meeting notes via meeting-ids, minute-tokens, or calendar-event-ids.
View Source
var VCSearch = common.Shortcut{ Service: "vc", Command: "+search", Description: "Search meeting records (requires at least one filter)", Risk: "read", Scopes: []string{"vc:meeting.search:read"}, AuthTypes: []string{"user"}, HasFormat: true, Flags: []common.Flag{ {Name: "query", Desc: "search keyword"}, {Name: "start", Desc: "start time (ISO 8601 or YYYY-MM-DD, e.g. 2026-03-24T00:00+08:00)"}, {Name: "end", Desc: "end time (ISO 8601 or YYYY-MM-DD, e.g. 2026-03-25)"}, {Name: "organizer-ids", Desc: "organizer open_id list, comma-separated"}, {Name: "participant-ids", Desc: "participant open_id list, comma-separated"}, {Name: "room-ids", Desc: "room_id list, comma-separated"}, {Name: "page-token", Desc: "page token for next page"}, {Name: "page-size", Default: "15", Desc: "page size, 1-30 (default 15)"}, }, Validate: func(ctx context.Context, runtime *common.RuntimeContext) error { if _, _, err := parseTimeRange(runtime); err != nil { return err } if q := strings.TrimSpace(runtime.Str("query")); q != "" && utf8.RuneCountInString(q) > maxVCSearchQueryLen { return output.ErrValidation("--query: length must be between 1 and 50 characters") } if _, err := common.ValidatePageSize(runtime, "page-size", defaultVCSearchPageSize, 1, maxVCSearchPageSize); err != nil { return err } for _, flag := range []string{"query", "start", "end", "organizer-ids", "participant-ids", "room-ids"} { if strings.TrimSpace(runtime.Str(flag)) != "" { return nil } } return common.FlagErrorf("specify at least one of --query, --start, --end, --organizer-ids, --participant-ids, or --room-ids") }, DryRun: func(ctx context.Context, runtime *common.RuntimeContext) *common.DryRunAPI { startTime, endTime, err := parseTimeRange(runtime) if err != nil { return common.NewDryRunAPI().Set("error", err.Error()) } params := buildSearchParams(runtime) dryRunParams := map[string]interface{}{} for key, values := range params { if len(values) == 1 { dryRunParams[key] = values[0] } else if len(values) > 1 { vs := make([]string, len(values)) copy(vs, values) dryRunParams[key] = vs } } dryRun := common.NewDryRunAPI(). POST("/open-apis/vc/v1/meetings/search") if len(dryRunParams) > 0 { dryRun.Params(dryRunParams) } return dryRun.Body(buildSearchBody(runtime, startTime, endTime)) }, Execute: func(ctx context.Context, runtime *common.RuntimeContext) error { startTime, endTime, err := parseTimeRange(runtime) if err != nil { return err } data, err := runtime.DoAPIJSON("POST", "/open-apis/vc/v1/meetings/search", buildSearchParams(runtime), buildSearchBody(runtime, startTime, endTime)) if err != nil { return err } if data == nil { data = map[string]interface{}{} } items := common.GetSlice(data, "items") outData := map[string]interface{}{ "items": items, "total": data["total"], "has_more": data["has_more"], "page_token": data["page_token"], } hasMore, _ := data["has_more"].(bool) runtime.OutFormat(outData, &output.Meta{Count: len(items)}, func(w io.Writer) { if len(items) == 0 { fmt.Fprintln(w, "No meetings.") return } var rows []map[string]interface{} common.EachMap(items, func(item map[string]interface{}) { rows = append(rows, map[string]interface{}{ "id": fmt.Sprintf("%v", item["id"]), "display_info": common.TruncateStr(meetingSearchDisplayInfo(item), 40), "meta_data": common.TruncateStr(meetingSearchDescription(item), 80), }) }) output.PrintTable(w, rows) }) if hasMore && runtime.Format != "json" && runtime.Format != "" { pt, _ := data["page_token"].(string) fmt.Fprintf(runtime.IO().Out, "\n(more available, page_token: %s)\n", pt) } return nil }, }
VCSearch searches historical meeting records with filters.
Functions ¶
Types ¶
This section is empty.
Click to show internal directories.
Click to hide internal directories.