Documentation
¶
Index ¶
Constants ¶
This section is empty.
Variables ¶
View Source
var DocMediaDownload = common.Shortcut{ Service: "docs", Command: "+media-download", Description: "Download document media or whiteboard thumbnail (auto-detects extension)", Risk: "read", Scopes: []string{"docs:document.media:download"}, AuthTypes: []string{"user", "bot"}, Flags: []common.Flag{ {Name: "token", Desc: "resource token (file_token or whiteboard_id)", Required: true}, {Name: "output", Desc: "local save path", Required: true}, {Name: "type", Default: "media", Desc: "resource type: media (default) | whiteboard"}, {Name: "overwrite", Type: "bool", Desc: "overwrite existing output file"}, }, DryRun: func(ctx context.Context, runtime *common.RuntimeContext) *common.DryRunAPI { token := runtime.Str("token") outputPath := runtime.Str("output") mediaType := runtime.Str("type") if mediaType == "whiteboard" { return common.NewDryRunAPI(). GET("/open-apis/board/v1/whiteboards/:token/download_as_image"). Desc("(when --type=whiteboard) Download whiteboard as image"). Set("token", token).Set("output", outputPath) } return common.NewDryRunAPI(). GET("/open-apis/drive/v1/medias/:token/download"). Desc("(when --type=media) Download document media file"). Set("token", token).Set("output", outputPath) }, Execute: func(ctx context.Context, runtime *common.RuntimeContext) error { token := runtime.Str("token") outputPath := runtime.Str("output") mediaType := runtime.Str("type") overwrite := runtime.Bool("overwrite") if err := validate.ResourceName(token, "--token"); err != nil { return output.ErrValidation("%s", err) } if _, err := validate.SafeOutputPath(outputPath); err != nil { return output.ErrValidation("unsafe output path: %s", err) } fmt.Fprintf(runtime.IO().ErrOut, "Downloading: %s %s\n", mediaType, common.MaskToken(token)) encodedToken := validate.EncodePathSegment(token) var apiPath string if mediaType == "whiteboard" { apiPath = fmt.Sprintf("/open-apis/board/v1/whiteboards/%s/download_as_image", encodedToken) } else { apiPath = fmt.Sprintf("/open-apis/drive/v1/medias/%s/download", encodedToken) } resp, err := runtime.DoAPIStream(ctx, &larkcore.ApiReq{ HttpMethod: http.MethodGet, ApiPath: apiPath, }) if err != nil { return output.ErrNetwork("download failed: %v", err) } defer resp.Body.Close() finalPath := outputPath currentExt := filepath.Ext(outputPath) if currentExt == "" { contentType := resp.Header.Get("Content-Type") mimeType := strings.Split(contentType, ";")[0] mimeType = strings.TrimSpace(mimeType) if ext, ok := mimeToExt[mimeType]; ok { finalPath = outputPath + ext } else if mediaType == "whiteboard" { finalPath = outputPath + ".png" } } safePath, err := validate.SafeOutputPath(finalPath) if err != nil { return output.ErrValidation("unsafe output path: %s", err) } if err := common.EnsureWritableFile(safePath, overwrite); err != nil { return err } if err := vfs.MkdirAll(filepath.Dir(safePath), 0700); err != nil { return output.Errorf(output.ExitInternal, "io", "cannot create parent directory: %v", err) } sizeBytes, err := validate.AtomicWriteFromReader(safePath, resp.Body, 0600) if err != nil { return output.Errorf(output.ExitInternal, "io", "cannot create file: %v", err) } runtime.Out(map[string]interface{}{ "saved_path": safePath, "size_bytes": sizeBytes, "content_type": resp.Header.Get("Content-Type"), }, nil) return nil }, }
View Source
var DocMediaInsert = common.Shortcut{ Service: "docs", Command: "+media-insert", Description: "Insert a local image or file at the end of a Lark document (4-step orchestration + auto-rollback)", Risk: "write", Scopes: []string{"docs:document.media:upload", "docx:document:write_only", "docx:document:readonly"}, AuthTypes: []string{"user", "bot"}, Flags: []common.Flag{ {Name: "file", Desc: "local file path (max 20MB)", Required: true}, {Name: "doc", Desc: "document URL or document_id", Required: true}, {Name: "type", Default: "image", Desc: "type: image | file"}, {Name: "align", Desc: "alignment: left | center | right"}, {Name: "caption", Desc: "image caption text"}, }, Validate: func(ctx context.Context, runtime *common.RuntimeContext) error { docRef, err := parseDocumentRef(runtime.Str("doc")) if err != nil { return err } if docRef.Kind == "doc" { return output.ErrValidation("docs +media-insert only supports docx documents; use a docx token/URL or a wiki URL that resolves to docx") } return nil }, DryRun: func(ctx context.Context, runtime *common.RuntimeContext) *common.DryRunAPI { docRef, err := parseDocumentRef(runtime.Str("doc")) if err != nil { return common.NewDryRunAPI().Set("error", err.Error()) } documentID := docRef.Token stepBase := 1 filePath := runtime.Str("file") mediaType := runtime.Str("type") caption := runtime.Str("caption") parentType := parentTypeForMediaType(mediaType) createBlockData := buildCreateBlockData(mediaType, 0) createBlockData["index"] = "<children_len>" batchUpdateData := buildBatchUpdateData("<new_block_id>", mediaType, "<file_token>", runtime.Str("align"), caption) d := common.NewDryRunAPI() if docRef.Kind == "wiki" { documentID = "<resolved_docx_token>" stepBase = 2 d.Desc("5-step orchestration: resolve wiki → query root → create block → upload file → bind to block (auto-rollback on failure)"). GET("/open-apis/wiki/v2/spaces/get_node"). Desc("[1] Resolve wiki node to docx document"). Params(map[string]interface{}{"token": docRef.Token}) } else { d.Desc("4-step orchestration: query root → create block → upload file → bind to block (auto-rollback on failure)") } d. GET("/open-apis/docx/v1/documents/:document_id/blocks/:document_id"). Desc(fmt.Sprintf("[%d] Get document root block", stepBase)). POST("/open-apis/docx/v1/documents/:document_id/blocks/:document_id/children"). Desc(fmt.Sprintf("[%d] Create empty block at document end", stepBase+1)). Body(createBlockData). POST("/open-apis/drive/v1/medias/upload_all"). Desc(fmt.Sprintf("[%d] Upload local file (multipart/form-data)", stepBase+2)). Body(map[string]interface{}{ "file_name": filepath.Base(filePath), "parent_type": parentType, "parent_node": "<new_block_id>", "file": "@" + filePath, }). PATCH("/open-apis/docx/v1/documents/:document_id/blocks/batch_update"). Desc(fmt.Sprintf("[%d] Bind uploaded file token to the new block", stepBase+3)). Body(batchUpdateData) return d.Set("document_id", documentID) }, Execute: func(ctx context.Context, runtime *common.RuntimeContext) error { filePath := runtime.Str("file") docInput := runtime.Str("doc") mediaType := runtime.Str("type") alignStr := runtime.Str("align") caption := runtime.Str("caption") safeFilePath, pathErr := validate.SafeInputPath(filePath) if pathErr != nil { return output.ErrValidation("unsafe file path: %s", pathErr) } filePath = safeFilePath documentID, err := resolveDocxDocumentID(runtime, docInput) if err != nil { return err } stat, err := vfs.Stat(filePath) if err != nil { return output.ErrValidation("file not found: %s", filePath) } if stat.Size() > maxFileSize { return output.ErrValidation("file %.1fMB exceeds 20MB limit", float64(stat.Size())/1024/1024) } fileName := filepath.Base(filePath) fmt.Fprintf(runtime.IO().ErrOut, "Inserting: %s -> document %s\n", fileName, common.MaskToken(documentID)) rootData, err := runtime.CallAPI("GET", fmt.Sprintf("/open-apis/docx/v1/documents/%s/blocks/%s", validate.EncodePathSegment(documentID), validate.EncodePathSegment(documentID)), nil, nil) if err != nil { return err } parentBlockID, insertIndex, err := extractAppendTarget(rootData, documentID) if err != nil { return err } fmt.Fprintf(runtime.IO().ErrOut, "Root block ready: %s (%d children)\n", parentBlockID, insertIndex) fmt.Fprintf(runtime.IO().ErrOut, "Creating block at index %d\n", insertIndex) createData, err := runtime.CallAPI("POST", fmt.Sprintf("/open-apis/docx/v1/documents/%s/blocks/%s/children", validate.EncodePathSegment(documentID), validate.EncodePathSegment(parentBlockID)), nil, buildCreateBlockData(mediaType, insertIndex)) if err != nil { return err } blockId, uploadParentNode, replaceBlockID := extractCreatedBlockTargets(createData, mediaType) if blockId == "" { return output.Errorf(output.ExitAPI, "api_error", "failed to create block: no block_id returned") } fmt.Fprintf(runtime.IO().ErrOut, "Block created: %s\n", blockId) if uploadParentNode != blockId || replaceBlockID != blockId { fmt.Fprintf(runtime.IO().ErrOut, "Resolved file block targets: upload=%s replace=%s\n", uploadParentNode, replaceBlockID) } rollback := func() error { fmt.Fprintf(runtime.IO().ErrOut, "Rolling back: deleting block %s\n", blockId) _, err := runtime.CallAPI("DELETE", fmt.Sprintf("/open-apis/docx/v1/documents/%s/blocks/%s/children/batch_delete", validate.EncodePathSegment(documentID), validate.EncodePathSegment(parentBlockID)), nil, buildDeleteBlockData(insertIndex)) return err } withRollbackWarning := func(opErr error) error { rollbackErr := rollback() if rollbackErr == nil { return opErr } warning := fmt.Sprintf("rollback failed for block %s: %v", blockId, rollbackErr) fmt.Fprintf(runtime.IO().ErrOut, "warning: %s\n", warning) return opErr } fileToken, err := uploadMediaFile(ctx, runtime, filePath, fileName, mediaType, uploadParentNode, documentID) if err != nil { return withRollbackWarning(err) } fmt.Fprintf(runtime.IO().ErrOut, "File uploaded: %s\n", fileToken) fmt.Fprintf(runtime.IO().ErrOut, "Binding uploaded media to block %s\n", replaceBlockID) if _, err := runtime.CallAPI("PATCH", fmt.Sprintf("/open-apis/docx/v1/documents/%s/blocks/batch_update", validate.EncodePathSegment(documentID)), nil, buildBatchUpdateData(replaceBlockID, mediaType, fileToken, alignStr, caption)); err != nil { return withRollbackWarning(err) } runtime.Out(map[string]interface{}{ "document_id": documentID, "block_id": blockId, "file_token": fileToken, "type": mediaType, }, nil) return nil }, }
View Source
var DocsCreate = common.Shortcut{ Service: "docs", Command: "+create", Description: "Create a Lark document", Risk: "write", AuthTypes: []string{"user", "bot"}, Scopes: []string{"docx:document:create"}, Flags: []common.Flag{ {Name: "title", Desc: "document title"}, {Name: "markdown", Desc: "Markdown content (Lark-flavored)", Required: true, Input: []string{common.File, common.Stdin}}, {Name: "folder-token", Desc: "parent folder token"}, {Name: "wiki-node", Desc: "wiki node token"}, {Name: "wiki-space", Desc: "wiki space ID (use my_library for personal library)"}, }, Validate: func(ctx context.Context, runtime *common.RuntimeContext) error { count := 0 if runtime.Str("folder-token") != "" { count++ } if runtime.Str("wiki-node") != "" { count++ } if runtime.Str("wiki-space") != "" { count++ } if count > 1 { return common.FlagErrorf("--folder-token, --wiki-node, and --wiki-space are mutually exclusive") } return nil }, DryRun: func(ctx context.Context, runtime *common.RuntimeContext) *common.DryRunAPI { args := map[string]interface{}{ "markdown": runtime.Str("markdown"), } if v := runtime.Str("title"); v != "" { args["title"] = v } if v := runtime.Str("folder-token"); v != "" { args["folder_token"] = v } if v := runtime.Str("wiki-node"); v != "" { args["wiki_node"] = v } if v := runtime.Str("wiki-space"); v != "" { args["wiki_space"] = v } return common.NewDryRunAPI(). POST(common.MCPEndpoint(runtime.Config.Brand)). Desc("MCP tool: create-doc"). Body(map[string]interface{}{"method": "tools/call", "params": map[string]interface{}{"name": "create-doc", "arguments": args}}). Set("mcp_tool", "create-doc").Set("args", args) }, Execute: func(ctx context.Context, runtime *common.RuntimeContext) error { args := map[string]interface{}{ "markdown": runtime.Str("markdown"), } if v := runtime.Str("title"); v != "" { args["title"] = v } if v := runtime.Str("folder-token"); v != "" { args["folder_token"] = v } if v := runtime.Str("wiki-node"); v != "" { args["wiki_node"] = v } if v := runtime.Str("wiki-space"); v != "" { args["wiki_space"] = v } result, err := common.CallMCPTool(runtime, "create-doc", args) if err != nil { return err } runtime.Out(result, nil) return nil }, }
View Source
var DocsFetch = common.Shortcut{ Service: "docs", Command: "+fetch", Description: "Fetch Lark document content", Risk: "read", Scopes: []string{"docx:document:readonly"}, AuthTypes: []string{"user", "bot"}, HasFormat: true, Flags: []common.Flag{ {Name: "doc", Desc: "document URL or token", Required: true}, {Name: "offset", Desc: "pagination offset"}, {Name: "limit", Desc: "pagination limit"}, }, DryRun: func(ctx context.Context, runtime *common.RuntimeContext) *common.DryRunAPI { args := map[string]interface{}{ "doc_id": runtime.Str("doc"), } if v := runtime.Str("offset"); v != "" { n, _ := strconv.Atoi(v) args["offset"] = n } if v := runtime.Str("limit"); v != "" { n, _ := strconv.Atoi(v) args["limit"] = n } return common.NewDryRunAPI(). POST(common.MCPEndpoint(runtime.Config.Brand)). Desc("MCP tool: fetch-doc"). Body(map[string]interface{}{"method": "tools/call", "params": map[string]interface{}{"name": "fetch-doc", "arguments": args}}). Set("mcp_tool", "fetch-doc").Set("args", args) }, Execute: func(ctx context.Context, runtime *common.RuntimeContext) error { args := map[string]interface{}{ "doc_id": runtime.Str("doc"), } if v := runtime.Str("offset"); v != "" { n, _ := strconv.Atoi(v) args["offset"] = n } if v := runtime.Str("limit"); v != "" { n, _ := strconv.Atoi(v) args["limit"] = n } result, err := common.CallMCPTool(runtime, "fetch-doc", args) if err != nil { return err } runtime.OutFormat(result, nil, func(w io.Writer) { if title, ok := result["title"].(string); ok && title != "" { fmt.Fprintf(w, "# %s\n\n", title) } if md, ok := result["markdown"].(string); ok { fmt.Fprintln(w, md) } if hasMore, ok := result["has_more"].(bool); ok && hasMore { fmt.Fprintln(w, "\n--- more content available, use --offset and --limit to paginate ---") } }) return nil }, }
View Source
var DocsSearch = common.Shortcut{ Service: "docs", Command: "+search", Description: "Search Lark docs, Wiki, and spreadsheet files (Search v2: doc_wiki/search)", Risk: "read", Scopes: []string{"search:docs:read"}, AuthTypes: []string{"user"}, HasFormat: true, Flags: []common.Flag{ {Name: "query", Desc: "search keyword"}, {Name: "filter", Desc: "filter conditions (JSON object)"}, {Name: "page-token", Desc: "page token"}, {Name: "page-size", Default: "15", Desc: "page size (default 15, max 20)"}, }, DryRun: func(ctx context.Context, runtime *common.RuntimeContext) *common.DryRunAPI { requestData, err := buildDocsSearchRequest( runtime.Str("query"), runtime.Str("filter"), runtime.Str("page-token"), runtime.Str("page-size"), ) if err != nil { return common.NewDryRunAPI().Set("error", err.Error()) } return common.NewDryRunAPI(). POST("/open-apis/search/v2/doc_wiki/search"). Body(requestData) }, Execute: func(ctx context.Context, runtime *common.RuntimeContext) error { requestData, err := buildDocsSearchRequest( runtime.Str("query"), runtime.Str("filter"), runtime.Str("page-token"), runtime.Str("page-size"), ) if err != nil { return err } data, err := runtime.CallAPI("POST", "/open-apis/search/v2/doc_wiki/search", nil, requestData) if err != nil { return err } items, _ := data["res_units"].([]interface{}) normalizedItems := addIsoTimeFields(items) resultData := map[string]interface{}{ "total": data["total"], "has_more": data["has_more"], "page_token": data["page_token"], "results": normalizedItems, } runtime.OutFormat(resultData, &output.Meta{Count: len(normalizedItems)}, func(w io.Writer) { if len(normalizedItems) == 0 { fmt.Fprintln(w, "No matching results found.") return } htmlTagRe := regexp.MustCompile(`</?h>`) var rows []map[string]interface{} for _, item := range normalizedItems { u, _ := item.(map[string]interface{}) if u == nil { continue } rawTitle := fmt.Sprintf("%v", u["title_highlighted"]) title := htmlTagRe.ReplaceAllString(rawTitle, "") title = common.TruncateStr(title, 50) resultMeta, _ := u["result_meta"].(map[string]interface{}) docTypes := "" if resultMeta != nil { docTypes = fmt.Sprintf("%v", resultMeta["doc_types"]) } entityType := fmt.Sprintf("%v", u["entity_type"]) typeStr := docTypes if typeStr == "" || typeStr == "<nil>" { typeStr = entityType } url := "" editTime := "" if resultMeta != nil { url = fmt.Sprintf("%v", resultMeta["url"]) editTime = fmt.Sprintf("%v", resultMeta["update_time_iso"]) } if len(url) > 80 { url = url[:80] } rows = append(rows, map[string]interface{}{ "type": typeStr, "title": title, "edit_time": editTime, "url": url, }) } output.PrintTable(w, rows) moreHint := "" hasMore, _ := data["has_more"].(bool) if hasMore { moreHint = " (more available, use --format json to get page_token, then --page-token to paginate)" } fmt.Fprintf(w, "\n%d result(s)%s\n", len(rows), moreHint) }) return nil }, }
View Source
var DocsUpdate = common.Shortcut{ Service: "docs", Command: "+update", Description: "Update a Lark document", Risk: "write", Scopes: []string{"docx:document:write_only", "docx:document:readonly"}, AuthTypes: []string{"user", "bot"}, Flags: []common.Flag{ {Name: "doc", Desc: "document URL or token", Required: true}, {Name: "mode", Desc: "update mode: append | overwrite | replace_range | replace_all | insert_before | insert_after | delete_range", Required: true}, {Name: "markdown", Desc: "new content (Lark-flavored Markdown; create blank whiteboards with <whiteboard type=\"blank\"></whiteboard>, repeat to create multiple boards)", Input: []string{common.File, common.Stdin}}, {Name: "selection-with-ellipsis", Desc: "content locator (e.g. 'start...end')"}, {Name: "selection-by-title", Desc: "title locator (e.g. '## Section')"}, {Name: "new-title", Desc: "also update document title"}, }, Validate: func(ctx context.Context, runtime *common.RuntimeContext) error { mode := runtime.Str("mode") if !validModes[mode] { return common.FlagErrorf("invalid --mode %q, valid: append | overwrite | replace_range | replace_all | insert_before | insert_after | delete_range", mode) } if mode != "delete_range" && runtime.Str("markdown") == "" { return common.FlagErrorf("--%s mode requires --markdown", mode) } selEllipsis := runtime.Str("selection-with-ellipsis") selTitle := runtime.Str("selection-by-title") if selEllipsis != "" && selTitle != "" { return common.FlagErrorf("--selection-with-ellipsis and --selection-by-title are mutually exclusive") } if needsSelection[mode] && selEllipsis == "" && selTitle == "" { return common.FlagErrorf("--%s mode requires --selection-with-ellipsis or --selection-by-title", mode) } return nil }, DryRun: func(ctx context.Context, runtime *common.RuntimeContext) *common.DryRunAPI { args := map[string]interface{}{ "doc_id": runtime.Str("doc"), "mode": runtime.Str("mode"), } if v := runtime.Str("markdown"); v != "" { args["markdown"] = v } if v := runtime.Str("selection-with-ellipsis"); v != "" { args["selection_with_ellipsis"] = v } if v := runtime.Str("selection-by-title"); v != "" { args["selection_by_title"] = v } if v := runtime.Str("new-title"); v != "" { args["new_title"] = v } return common.NewDryRunAPI(). POST(common.MCPEndpoint(runtime.Config.Brand)). Desc("MCP tool: update-doc"). Body(map[string]interface{}{"method": "tools/call", "params": map[string]interface{}{"name": "update-doc", "arguments": args}}). Set("mcp_tool", "update-doc").Set("args", args) }, Execute: func(ctx context.Context, runtime *common.RuntimeContext) error { args := map[string]interface{}{ "doc_id": runtime.Str("doc"), "mode": runtime.Str("mode"), } if v := runtime.Str("markdown"); v != "" { args["markdown"] = v } if v := runtime.Str("selection-with-ellipsis"); v != "" { args["selection_with_ellipsis"] = v } if v := runtime.Str("selection-by-title"); v != "" { args["selection_by_title"] = v } if v := runtime.Str("new-title"); v != "" { args["new_title"] = v } result, err := common.CallMCPTool(runtime, "update-doc", args) if err != nil { return err } normalizeDocsUpdateResult(result, runtime.Str("markdown")) runtime.Out(result, nil) return nil }, }
View Source
var MediaUpload = common.Shortcut{ Service: "docs", Command: "+media-upload", Description: "Upload media file (image/attachment) to a document block", Risk: "write", Scopes: []string{"docs:document.media:upload"}, AuthTypes: []string{"user", "bot"}, Flags: []common.Flag{ {Name: "file", Desc: "local file path (max 20MB)", Required: true}, {Name: "parent-type", Desc: "parent type: docx_image | docx_file", Required: true}, {Name: "parent-node", Desc: "parent node ID (block_id)", Required: true}, {Name: "doc-id", Desc: "document ID (for drive_route_token)"}, }, DryRun: func(ctx context.Context, runtime *common.RuntimeContext) *common.DryRunAPI { filePath := runtime.Str("file") parentType := runtime.Str("parent-type") parentNode := runtime.Str("parent-node") docId := runtime.Str("doc-id") body := map[string]interface{}{ "file_name": filepath.Base(filePath), "parent_type": parentType, "parent_node": parentNode, "file": "@" + filePath, } if docId != "" { body["extra"] = fmt.Sprintf(`{"drive_route_token":"%s"}`, docId) } return common.NewDryRunAPI(). Desc("multipart/form-data upload"). POST("/open-apis/drive/v1/medias/upload_all"). Body(body) }, Execute: func(ctx context.Context, runtime *common.RuntimeContext) error { filePath := runtime.Str("file") parentType := runtime.Str("parent-type") parentNode := runtime.Str("parent-node") docId := runtime.Str("doc-id") safeFilePath, pathErr := validate.SafeInputPath(filePath) if pathErr != nil { return output.ErrValidation("unsafe file path: %s", pathErr) } filePath = safeFilePath stat, err := vfs.Stat(filePath) if err != nil { return output.ErrValidation("file not found: %s", filePath) } if stat.Size() > maxFileSize { return output.ErrValidation("file %.1fMB exceeds 20MB limit", float64(stat.Size())/1024/1024) } fileName := filepath.Base(filePath) fmt.Fprintf(runtime.IO().ErrOut, "Uploading: %s (%d bytes)\n", fileName, stat.Size()) f, err := vfs.Open(filePath) if err != nil { return output.ErrValidation("cannot open file: %v", err) } defer f.Close() fd := larkcore.NewFormdata() fd.AddField("file_name", fileName) fd.AddField("parent_type", parentType) fd.AddField("parent_node", parentNode) fd.AddField("size", fmt.Sprintf("%d", stat.Size())) if docId != "" { extra, err := buildDriveRouteExtra(docId) if err != nil { return err } fd.AddField("extra", extra) } fd.AddFile("file", f) apiResp, err := runtime.DoAPI(&larkcore.ApiReq{ HttpMethod: http.MethodPost, ApiPath: "/open-apis/drive/v1/medias/upload_all", Body: fd, }, larkcore.WithFileUpload()) if err != nil { var exitErr *output.ExitError if errors.As(err, &exitErr) { return err } return output.ErrNetwork("upload failed: %v", err) } var result map[string]interface{} if err := json.Unmarshal(apiResp.RawBody, &result); err != nil { return output.Errorf(output.ExitAPI, "api_error", "upload failed: invalid response JSON: %v", err) } code, _ := util.ToFloat64(result["code"]) if code != 0 { msg, _ := result["msg"].(string) return output.ErrAPI(int(code), fmt.Sprintf("upload failed: [%d] %s", int(code), msg), result["error"]) } data, _ := result["data"].(map[string]interface{}) fileToken, _ := data["file_token"].(string) if fileToken == "" { return output.Errorf(output.ExitAPI, "api_error", "upload failed: no file_token returned") } runtime.Out(map[string]interface{}{ "file_token": fileToken, "file_name": fileName, "size": stat.Size(), }, nil) return nil }, }
Functions ¶
Types ¶
This section is empty.
Click to show internal directories.
Click to hide internal directories.