Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 4 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,14 @@

[中文版](./README.zh.md) | [English](./README.md)

The official [Lark/Feishu](https://www.larksuite.com/) CLI tool, maintained by the [larksuite](https://github.com/larksuite) team — built for humans and AI Agents. Covers core business domains including Messenger, Docs, Base, Sheets, Slides, Calendar, Mail, Tasks, Meetings, and more, with 200+ commands and 22 AI Agent [Skills](./skills/).
The official [Lark/Feishu](https://www.larksuite.com/) CLI tool, maintained by the [larksuite](https://github.com/larksuite) team — built for humans and AI Agents. Covers core business domains including Messenger, Docs, Base, Sheets, Slides, Calendar, Mail, Tasks, Meetings, and more, with 200+ commands and 23 AI Agent [Skills](./skills/).

[Install](#installation--quick-start) · [AI Agent Skills](#agent-skills) · [Auth](#authentication) · [Commands](#three-layer-command-system) · [Advanced](#advanced-usage) · [Security](#security--risk-warnings-read-before-use) · [Contributing](#contributing)

## Why lark-cli?

- **Agent-Native Design** — 22 structured [Skills](./skills/) out of the box, compatible with popular AI tools — Agents can operate Lark with zero extra setup
- **Wide Coverage** — 14 business domains, 200+ curated commands, 22 AI Agent [Skills](./skills/)
- **Wide Coverage** — 15 business domains, 200+ curated commands, 23 AI Agent [Skills](./skills/)
Comment thread
coderabbitai[bot] marked this conversation as resolved.
- **AI-Friendly & Optimized** — Every command is tested with real Agents, featuring concise parameters, smart defaults, and structured output to maximize Agent call success rates
- **Open Source, Zero Barriers** — MIT license, ready to use, just `npm install`
- **Up and Running in 3 Minutes** — One-click app creation, interactive login, from install to first API call in just 3 steps
Expand All @@ -38,7 +38,7 @@ The official [Lark/Feishu](https://www.larksuite.com/) CLI tool, maintained by t
| 🎥 Meetings | Search meeting records, query meeting minutes & recordings |
| 🕐 Attendance | Query personal attendance check-in records |
| ✍️ Approval | Query approval tasks, approve/reject/transfer tasks, cancel and CC instances |
| 🎯 OKR | Query, create, update OKRs; manage objective & key results, alignments and indicators. |
| 🎯 OKR | Query, create, update OKRs; manage objective & key results, alignments, indicators and progress. |
| 📋 Project | Meegle — manage work items, schedules, and data via the standalone [meegle-cli](https://github.com/larksuite/meegle-cli) (install separately) |

## Installation & Quick Start
Expand Down Expand Up @@ -156,6 +156,7 @@ lark-cli auth status
| `lark-approval` | Query approval tasks, approve/reject/transfer tasks, cancel and CC instances |
| `lark-workflow-meeting-summary` | Workflow: meeting minutes aggregation & structured report |
| `lark-workflow-standup-report` | Workflow: agenda & todo summary |
| `lark-okr` | Query, create, update OKRs; manage objective & key results, alignments and indicators. |
Comment thread
coderabbitai[bot] marked this conversation as resolved.

## Authentication

Expand Down
7 changes: 4 additions & 3 deletions README.zh.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,14 @@

[中文版](./README.zh.md) | [English](./README.md)

飞书官方 CLI 工具,由 [larksuite](https://github.com/larksuite) 团队维护 — 让人类和 AI Agent 都能在终端中操作飞书。覆盖消息、文档、多维表格、电子表格、幻灯片、日历、邮箱、任务、会议等核心业务域,提供 200+ 命令及 22 个 AI Agent [Skills](./skills/)。
飞书官方 CLI 工具,由 [larksuite](https://github.com/larksuite) 团队维护 — 让人类和 AI Agent 都能在终端中操作飞书。覆盖消息、文档、多维表格、电子表格、幻灯片、日历、邮箱、任务、会议等核心业务域,提供 200+ 命令及 23 个 AI Agent [Skills](./skills/)。

[安装](#安装与快速开始) · [AI Agent Skills](#agent-skills) · [认证](#认证) · [命令](#三层命令调用) · [进阶用法](#进阶用法) · [安全](#安全与风险提示使用前必读) · [贡献](#贡献)

## 为什么选 lark-cli?

- **为 Agent 原生设计** — 22 个 [Skills](./skills/) 开箱即用,适配主流 AI 工具,Agent 无需额外适配即可操作飞书
- **覆盖面广** — 14 大业务域、200+ 精选命令、22 个 AI Agent [Skills](./skills/)
- **覆盖面广** — 15 大业务域、200+ 精选命令、23 个 AI Agent [Skills](./skills/)
Comment thread
coderabbitai[bot] marked this conversation as resolved.
- **AI 友好调优** — 每条命令经过 Agent 实测验证,提供更友好的参数、智能默认值和结构化输出,大幅提升 Agent 调用成功率
- **开源零门槛** — MIT 协议,开箱即用,`npm install` 即可使用
- **三分钟上手** — 一键创建应用、交互式登录授权,从安装到第一次 API 调用只需三步
Expand All @@ -38,7 +38,7 @@
| 🎥 视频会议 | 搜索会议记录、查询会议纪要与录制 |
| 🕐 考勤打卡 | 查询个人考勤打卡记录 |
| ✍️ 审批 | 查询审批任务、同意/拒绝/转交审批任务、撤回与抄送审批实例 |
| 🎯 OKR | 查询、创建、更新 OKR,管理目标、关键结果、对齐和指标 |
| 🎯 OKR | 查询、创建、更新 OKR,管理目标、关键结果、对齐、指标和进展记录 |
| 📋 飞书项目 | 管理工作项、排期与数据 — 由独立的 [meegle-cli](https://github.com/larksuite/meegle-cli) 提供(需单独安装) |

## 安装与快速开始
Expand Down Expand Up @@ -157,6 +157,7 @@ lark-cli auth status
| `lark-approval` | 审批任务查询、同意/拒绝/转交审批任务、撤回与抄送审批实例 |
| `lark-workflow-meeting-summary` | 工作流:会议纪要汇总与结构化报告 |
| `lark-workflow-standup-report` | 工作流:日程待办摘要 |
| `lark-okr` | 查询、创建、更新 OKR,管理目标、关键结果、对齐、指标和进展记录 |

## 认证

Expand Down
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ require (
github.com/gofrs/flock v0.8.1
github.com/google/uuid v1.6.0
github.com/itchyny/gojq v0.12.17
github.com/larksuite/oapi-sdk-go/v3 v3.5.3
github.com/larksuite/oapi-sdk-go/v3 v3.5.4
github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e
github.com/smartystreets/goconvey v1.8.1
github.com/spf13/cobra v1.10.2
Expand Down
4 changes: 2 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -71,8 +71,8 @@ github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7
github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
github.com/larksuite/oapi-sdk-go/v3 v3.5.3 h1:xvf8Dv29kBXC5/DNDCLhHkAFW8l/0LlQJimO5Zn+JUk=
github.com/larksuite/oapi-sdk-go/v3 v3.5.3/go.mod h1:ZEplY+kwuIrj/nqw5uSCINNATcH3KdxSN7y+UxYY5fI=
github.com/larksuite/oapi-sdk-go/v3 v3.5.4 h1:U2S9x9LrfH++ZqJ+YAiUlqzCWJmVXhFdS8Z7rIBH8H0=
github.com/larksuite/oapi-sdk-go/v3 v3.5.4/go.mod h1:ZEplY+kwuIrj/nqw5uSCINNATcH3KdxSN7y+UxYY5fI=
github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY=
github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
Expand Down
4 changes: 2 additions & 2 deletions internal/registry/service_descriptions.json
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@
"zh": { "title": "知识库", "description": "知识空间、节点管理" }
},
"okr": {
"en": { "title": "OKR", "description": "Lark OKR objectives, key results, alignments, indicators" },
"zh": { "title": "OKR", "description": "飞书 OKR 目标、关键结果、对齐、量化指标" }
"en": { "title": "OKR", "description": "Lark OKR objectives, key results, alignments, indicators, progresses" },
"zh": { "title": "OKR", "description": "飞书 OKR 目标、关键结果、对齐、量化指标、进展记录" }
}
}
53 changes: 53 additions & 0 deletions shortcuts/okr/okr_cli_resp.go
Original file line number Diff line number Diff line change
Expand Up @@ -99,3 +99,56 @@
OwnerType string `json:"owner_type"`
UserID *string `json:"user_id,omitempty"`
}

// ProgressStatus 进展状态
type ProgressStatus int32

const (
ProgressStatusNormal ProgressStatus = 0 // 正常
ProgressStatusOverdue ProgressStatus = 1 // 逾期
ProgressStatusDone ProgressStatus = 2 // 已完成
)

// ParseProgressStatus parses a progress status string into ProgressStatus.
// Accepts "normal", "overdue", "done" or their numeric values "0", "1", "2".
func ParseProgressStatus(s string) (ProgressStatus, bool) {
switch s {
case "normal", "0":
return ProgressStatusNormal, true
case "overdue", "1":
return ProgressStatusOverdue, true
case "done", "2":
return ProgressStatusDone, true
default:
return 0, false
}
}

// String returns a human-readable name for ProgressStatus.
func (s ProgressStatus) String() string {
switch s {
case ProgressStatusNormal:
return "normal"
case ProgressStatusOverdue:
return "overdue"
case ProgressStatusDone:
return "done"
default:
return ""

Check warning on line 137 in shortcuts/okr/okr_cli_resp.go

View check run for this annotation

Codecov / codecov/patch

shortcuts/okr/okr_cli_resp.go#L136-L137

Added lines #L136 - L137 were not covered by tests
}
}

// RespProgressRate 进度率(面向用户的响应格式,Status 为可读字符串)
type RespProgressRate struct {
Percent *float64 `json:"percent,omitempty"`
Status *string `json:"status,omitempty"`
}

// RespProgress 进展记录
type RespProgress struct {
ID string `json:"progress_id"`
ModifyTime string `json:"modify_time"`
CreateTime *string `json:"create_time,omitempty"`
Content *string `json:"content,omitempty"`
ProgressRate *RespProgressRate `json:"progress_rate,omitempty"`
}
146 changes: 146 additions & 0 deletions shortcuts/okr/okr_image_upload.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
// Copyright (c) 2026 Lark Technologies Pte. Ltd.
// SPDX-License-Identifier: MIT

package okr

import (
"context"
"encoding/json"
"errors"
"fmt"
"path/filepath"
"strconv"
"strings"

larkcore "github.com/larksuite/oapi-sdk-go/v3/core"

"github.com/larksuite/cli/internal/output"
"github.com/larksuite/cli/shortcuts/common"
)

// allowedImageExts lists the file extensions supported by the OKR image upload API.
var allowedImageExts = map[string]bool{
".jpg": true,
".jpeg": true,
".png": true,
".gif": true,
".bmp": true,
}

// OKRUploadImage uploads an image for use in OKR progress rich text.
var OKRUploadImage = common.Shortcut{
Service: "okr",
Command: "+upload-image",
Description: "Upload an image for use in OKR progress rich text",
Risk: "write",
Scopes: []string{"okr:okr.progress.file:upload"},
AuthTypes: []string{"user", "bot"},
Flags: []common.Flag{
{Name: "file", Desc: "local image path (supports JPG, JPEG, PNG, GIF, BMP)", Required: true},
{Name: "target-id", Desc: "target ID (objective or key result ID) for the progress", Required: true},
{Name: "target-type", Desc: "target type: objective | key_result", Required: true, Enum: []string{"objective", "key_result"}},
},
Validate: func(ctx context.Context, runtime *common.RuntimeContext) error {
filePath := runtime.Str("file")
if filePath == "" {
return common.FlagErrorf("--file is required")

Check warning on line 46 in shortcuts/okr/okr_image_upload.go

View check run for this annotation

Codecov / codecov/patch

shortcuts/okr/okr_image_upload.go#L46

Added line #L46 was not covered by tests
}
ext := strings.ToLower(filepath.Ext(filePath))
if !allowedImageExts[ext] {
return common.FlagErrorf("--file must be an image (supported: JPG, JPEG, PNG, GIF, BMP), got %q", ext)
}
Comment thread
syh-cpdsss marked this conversation as resolved.

targetID := runtime.Str("target-id")
if targetID == "" {
return common.FlagErrorf("--target-id is required")

Check warning on line 55 in shortcuts/okr/okr_image_upload.go

View check run for this annotation

Codecov / codecov/patch

shortcuts/okr/okr_image_upload.go#L55

Added line #L55 was not covered by tests
}
if id, err := strconv.ParseInt(targetID, 10, 64); err != nil || id <= 0 {
return common.FlagErrorf("--target-id must be a positive int64")
}

targetType := runtime.Str("target-type")
if _, ok := targetTypeAllowed[targetType]; !ok {
return common.FlagErrorf("--target-type must be one of: objective | key_result")

Check warning on line 63 in shortcuts/okr/okr_image_upload.go

View check run for this annotation

Codecov / codecov/patch

shortcuts/okr/okr_image_upload.go#L63

Added line #L63 was not covered by tests
}
return nil
},
DryRun: func(ctx context.Context, runtime *common.RuntimeContext) *common.DryRunAPI {
filePath := runtime.Str("file")
targetID := runtime.Str("target-id")
targetType := runtime.Str("target-type")
targetTypeVal := targetTypeAllowed[targetType]

return common.NewDryRunAPI().
POST("/open-apis/okr/v1/images/upload").
Body(map[string]interface{}{
"file": "@" + filePath,
"target_id": targetID,
"target_type": targetTypeVal,
}).
Desc(fmt.Sprintf("Upload image for OKR %s %s", targetType, targetID))
},
Execute: func(ctx context.Context, runtime *common.RuntimeContext) error {
filePath := runtime.Str("file")
targetID := runtime.Str("target-id")
targetType := runtime.Str("target-type")
targetTypeVal := targetTypeAllowed[targetType]

info, err := runtime.FileIO().Stat(filePath)
if err != nil {
return common.WrapInputStatError(err)
}

f, err := runtime.FileIO().Open(filePath)
if err != nil {
return common.WrapInputStatError(err)

Check warning on line 95 in shortcuts/okr/okr_image_upload.go

View check run for this annotation

Codecov / codecov/patch

shortcuts/okr/okr_image_upload.go#L95

Added line #L95 was not covered by tests
}
defer f.Close()

fileName := filepath.Base(filePath)
fmt.Fprintf(runtime.IO().ErrOut, "Uploading: %s (%s)\n", fileName, common.FormatSize(info.Size()))

fd := larkcore.NewFormdata()
fd.AddField("target_id", targetID)
fd.AddField("target_type", fmt.Sprintf("%d", targetTypeVal))
fd.AddFile("data", f)

apiResp, err := runtime.DoAPI(&larkcore.ApiReq{
HttpMethod: "POST",
ApiPath: "/open-apis/okr/v1/images/upload",
Body: fd,
}, larkcore.WithFileUpload())
if err != nil {
var exitErr *output.ExitError
if errors.As(err, &exitErr) {
return err

Check warning on line 115 in shortcuts/okr/okr_image_upload.go

View check run for this annotation

Codecov / codecov/patch

shortcuts/okr/okr_image_upload.go#L113-L115

Added lines #L113 - L115 were not covered by tests
}
return output.ErrNetwork("upload failed: %v", err)

Check warning on line 117 in shortcuts/okr/okr_image_upload.go

View check run for this annotation

Codecov / codecov/patch

shortcuts/okr/okr_image_upload.go#L117

Added line #L117 was not covered by tests
}

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)

Check warning on line 122 in shortcuts/okr/okr_image_upload.go

View check run for this annotation

Codecov / codecov/patch

shortcuts/okr/okr_image_upload.go#L122

Added line #L122 was not covered by tests
}

if larkCode := int(common.GetFloat(result, "code")); larkCode != 0 {
msg, _ := result["msg"].(string)
return output.ErrAPI(larkCode, fmt.Sprintf("upload failed: [%d] %s", larkCode, msg), result["error"])
}

data, _ := result["data"].(map[string]interface{})
fileToken, _ := data["file_token"].(string)
url, _ := data["url"].(string)

if fileToken == "" {
return output.Errorf(output.ExitAPI, "api_error", "upload failed: no file_token returned")
}

runtime.Out(map[string]interface{}{
"file_token": fileToken,
"url": url,
"file_name": fileName,
"size": info.Size(),
}, nil)
return nil
},
}
Loading
Loading