55//
66// Three mutually exclusive input modes (only one allowed per invocation):
77// meeting-ids: meeting.get → note_id → note detail API
8- // minute-tokens: minutes API → note detail + AI artifacts + transcript
8+ // minute-tokens: minutes API → note detail + AI artifacts ( transcript inlined)
99// calendar-event-ids: primary calendar → mget_instance_relation_info → meeting_id → meeting.get → note_id
1010
1111package vc
4444 scopesMinuteTokens = []string {
4545 "minutes:minutes:readonly" ,
4646 "minutes:minutes.artifacts:read" ,
47- "minutes:minutes.transcript:export" ,
4847 }
4948 scopesCalendarEventIDs = []string {
5049 "calendar:calendar:read" ,
@@ -436,13 +435,9 @@ func fetchNoteByMinuteToken(ctx context.Context, runtime *common.RuntimeContext,
436435 }
437436 }
438437
439- // path 2 & 3: AI artifacts are collected under the artifacts field .
438+ // AI artifacts + transcript come from the same / artifacts endpoint .
440439 artifacts := map [string ]any {}
441- fetchInlineArtifacts (runtime , minuteToken , artifacts )
442- transcriptPath := downloadTranscriptFile (runtime , minuteToken , title )
443- if transcriptPath != "" {
444- artifacts ["transcript_file" ] = transcriptPath
445- }
440+ fetchInlineArtifacts (runtime , minuteToken , title , artifacts )
446441 if len (artifacts ) > 0 {
447442 result ["artifacts" ] = artifacts
448443 }
@@ -469,8 +464,39 @@ func sanitizeDirName(title, minuteToken string) string {
469464 return fmt .Sprintf ("artifact-%s-%s" , safe , minuteToken )
470465}
471466
472- // downloadTranscriptFile downloads transcript to a local file and returns the file path (empty on failure).
473- func downloadTranscriptFile (runtime * common.RuntimeContext , minuteToken string , title string ) string {
467+ // fetchInlineArtifacts fetches summary/todos/chapters/keywords and transcript from the
468+ // /artifacts API, persists transcript to disk, and exposes the path as transcript_file.
469+ func fetchInlineArtifacts (runtime * common.RuntimeContext , minuteToken string , title string , result map [string ]any ) {
470+ errOut := runtime .IO ().ErrOut
471+ fmt .Fprintf (errOut , "%s fetching AI artifacts...\n " , logPrefix )
472+ data , err := runtime .DoAPIJSON (http .MethodGet , fmt .Sprintf ("/open-apis/minutes/v1/minutes/%s/artifacts" , validate .EncodePathSegment (minuteToken )), nil , nil )
473+ if err != nil {
474+ fmt .Fprintf (errOut , "%s failed to fetch AI artifacts: %v\n " , logPrefix , err )
475+ return
476+ }
477+ if summary , ok := data ["summary" ].(string ); ok && summary != "" {
478+ result ["summary" ] = summary
479+ }
480+ if todos , ok := data ["minute_todos" ].([]any ); ok && len (todos ) > 0 {
481+ result ["todos" ] = todos
482+ }
483+ if chapters , ok := data ["minute_chapters" ].([]any ); ok && len (chapters ) > 0 {
484+ result ["chapters" ] = chapters
485+ }
486+ if keywords , ok := data ["keywords" ].([]any ); ok && len (keywords ) > 0 {
487+ result ["keywords" ] = keywords
488+ }
489+ if transcript , ok := data ["transcript" ].(string ); ok && transcript != "" {
490+ if path := saveTranscriptToFile (runtime , minuteToken , title , []byte (transcript )); path != "" {
491+ result ["transcript_file" ] = path
492+ }
493+ }
494+ }
495+
496+ // saveTranscriptToFile persists transcript bytes to the canonical artifact path
497+ // for the given minute_token. Returns the file path on success (or when the
498+ // file already exists and --overwrite is not set), empty string on any failure.
499+ func saveTranscriptToFile (runtime * common.RuntimeContext , minuteToken , title string , content []byte ) string {
474500 errOut := runtime .IO ().ErrOut
475501
476502 // With no --output-dir the default layout shares the directory with
@@ -483,37 +509,15 @@ func downloadTranscriptFile(runtime *common.RuntimeContext, minuteToken string,
483509 }
484510 transcriptPath := filepath .Join (dirName , common .DefaultTranscriptFileName )
485511
486- // Overwrite check via FileIO.Stat
487512 if ! runtime .Bool ("overwrite" ) {
488513 if _ , statErr := runtime .FileIO ().Stat (transcriptPath ); statErr == nil {
489514 fmt .Fprintf (errOut , "%s transcript already exists: %s (use --overwrite to replace)\n " , logPrefix , transcriptPath )
490515 return transcriptPath
491516 }
492517 }
493518
494- fmt .Fprintf (errOut , "%s downloading transcript: %s\n " , logPrefix , transcriptPath )
495- apiResp , err := runtime .DoAPI (& larkcore.ApiReq {
496- HttpMethod : http .MethodGet ,
497- ApiPath : fmt .Sprintf ("/open-apis/minutes/v1/minutes/%s/transcript" , validate .EncodePathSegment (minuteToken )),
498- QueryParams : larkcore.QueryParams {
499- "need_speaker" : []string {"true" },
500- "need_timestamp" : []string {"true" },
501- "file_format" : []string {"txt" },
502- },
503- }, larkcore .WithFileDownload ())
504- if err != nil {
505- fmt .Fprintf (errOut , "%s failed to download transcript: %v\n " , logPrefix , err )
506- return ""
507- }
508- if apiResp .StatusCode >= 400 {
509- fmt .Fprintf (errOut , "%s failed to download transcript: HTTP %d\n " , logPrefix , apiResp .StatusCode )
510- return ""
511- }
512- if len (apiResp .RawBody ) == 0 {
513- fmt .Fprintf (errOut , "%s transcript is empty (not available for this minute)\n " , logPrefix )
514- return ""
515- }
516- if _ , err := runtime .FileIO ().Save (transcriptPath , fileio.SaveOptions {}, bytes .NewReader (apiResp .RawBody )); err != nil {
519+ fmt .Fprintf (errOut , "%s writing transcript: %s\n " , logPrefix , transcriptPath )
520+ if _ , err := runtime .FileIO ().Save (transcriptPath , fileio.SaveOptions {}, bytes .NewReader (content )); err != nil {
517521 var me * fileio.MkdirError
518522 switch {
519523 case errors .Is (err , fileio .ErrPathValidation ):
@@ -528,29 +532,6 @@ func downloadTranscriptFile(runtime *common.RuntimeContext, minuteToken string,
528532 return transcriptPath
529533}
530534
531- // fetchInlineArtifacts fetches summary/todos/chapters from artifacts API and writes them inline into result map.
532- func fetchInlineArtifacts (runtime * common.RuntimeContext , minuteToken string , result map [string ]any ) {
533- errOut := runtime .IO ().ErrOut
534- fmt .Fprintf (errOut , "%s fetching AI artifacts...\n " , logPrefix )
535- data , err := runtime .DoAPIJSON (http .MethodGet , fmt .Sprintf ("/open-apis/minutes/v1/minutes/%s/artifacts" , validate .EncodePathSegment (minuteToken )), nil , nil )
536- if err != nil {
537- fmt .Fprintf (errOut , "%s failed to fetch AI artifacts: %v\n " , logPrefix , err )
538- return
539- }
540- if summary , ok := data ["summary" ].(string ); ok && summary != "" {
541- result ["summary" ] = summary
542- }
543- if todos , ok := data ["minute_todos" ].([]any ); ok && len (todos ) > 0 {
544- result ["todos" ] = todos
545- }
546- if chapters , ok := data ["minute_chapters" ].([]any ); ok && len (chapters ) > 0 {
547- result ["chapters" ] = chapters
548- }
549- if keywords , ok := data ["keywords" ].([]any ); ok && len (keywords ) > 0 {
550- result ["keywords" ] = keywords
551- }
552- }
553-
554535// parseArtifactType extracts artifact_type as int from varying JSON number representations.
555536func parseArtifactType (v any ) int {
556537 switch n := v .(type ) {
@@ -712,9 +693,8 @@ var VCNotes = common.Shortcut{
712693 GET ("/open-apis/minutes/v1/minutes/{minute_token}" ).
713694 GET ("/open-apis/vc/v1/notes/{note_id}" ).
714695 GET ("/open-apis/minutes/v1/minutes/{minute_token}/artifacts" ).
715- GET ("/open-apis/minutes/v1/minutes/{minute_token}/transcript" ).
716696 Set ("minute_tokens" , common .SplitCSV (tokens )).
717- Set ("steps" , "minutes API → note detail + AI artifacts + transcript" )
697+ Set ("steps" , "minutes API → note detail + AI artifacts (incl. transcript) " )
718698 }
719699 ids := runtime .Str ("calendar-event-ids" )
720700 return common .NewDryRunAPI ().
0 commit comments