feat(lingo): add Feishu dictionary shortcuts and skill#855
Conversation
Introduces a new `lingo` service covering the six Feishu Lingo /
百科 dictionary entry endpoints as shortcuts, plus the accompanying
lark-lingo skill with per-verb reference docs.
Shortcuts (shortcuts/lingo/):
+search POST /lingo/v1/entities/search read
+match POST /lingo/v1/entities/match read
+get GET /lingo/v1/entities/:id read
+create POST /lingo/v1/entities write (bot-only)
+update PUT /lingo/v1/entities/:id write (bot-only, full overwrite)
+delete DELETE /lingo/v1/entities/:id high-risk-write (bot-only)
Each shortcut has validate / dry-run / execute hooks and unit tests
covering flag validation, dry-run payload, successful execute, and
API-error paths. Dry-run e2e tests added under tests/cli_e2e/lingo/.
The skill (skills/lark-lingo/) follows the lark-markdown pattern:
concise SKILL.md index + one references/lark-lingo-<verb>.md per
shortcut. References capture the real-world gotchas we hit during
integration:
* write endpoints reject user_access_token (99991668); bot-only
* bot scope is configured in the developer console, not via
`auth login --scope` (which only affects user scope)
* +update is PUT full-body overwrite — read current via +get
and merge before writing
* +create entries enter the review queue unless the app has
baike:entity:exempt_review (self-built apps only)
Also:
* internal/registry/service_descriptions.json: add lingo entry
* shortcuts/register.go: register lingo.Shortcuts()
* README.md / README.zh.md: new Lingo row in both feature and
skill tables
|
|
📝 WalkthroughWalkthroughAdds a Lingo service: registry/docs updates, six CLI shortcuts (search/match/get/create/update/delete) with validation and DryRun/Execute, unit and E2E dry-run tests, skill/reference documentation, and registers shortcuts for CLI mounting. ChangesLingo Dictionary Service & Shortcuts
Sequence Diagram(s)N/A — flow diagrams included in the hidden review stack artifact. Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes Suggested labels
Suggested reviewers
Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 5
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@shortcuts/lingo/lingo_entity_delete.go`:
- Around line 24-26: The shortcut currently lists both "user" and "bot" in the
AuthTypes slice, but this delete operation must be bot-only; locate the shortcut
definition in lingo_entity_delete.go (the struct literal that sets Scopes,
AuthTypes, HasFormat) and remove "user" from the AuthTypes field so it becomes
AuthTypes: []string{"bot"}. Leave Scopes and HasFormat unchanged and run
tests/linters to ensure no other callsites expect user auth for this delete
shortcut.
- Around line 41-42: The code interpolates entityID directly into path (path :=
fmt.Sprintf("/open-apis/lingo/v1/entities/%s", entityID)) and calls
runtime.DoAPIJSON which can break on reserved characters; URL-escape the path
segment before building the path (use a path-escaping function like
url.PathEscape on entityID) and then pass the escaped value into the fmt.Sprintf
so runtime.DoAPIJSON receives a safe path.
In `@shortcuts/lingo/lingo_entity_update.go`:
- Around line 69-70: The code currently injects raw runtime.Str("entity-id")
into the URL path; instead URL-encode that segment before building the path to
avoid malformed endpoints. Replace the direct interpolation in the path
construction for lingo_entity_update (where path :=
fmt.Sprintf("/open-apis/lingo/v1/entities/%s", runtime.Str("entity-id"))) by
first encoding the entity-id with a path-encoding function (e.g.,
net/url.PathEscape or your runtime-provided path-escape utility) and then use
the escaped value in fmt.Sprintf so the PUT request via runtime.DoAPIJSON uses a
safe, encoded path.
- Around line 25-27: The AuthTypes field in the Lingo entity update shortcut
currently includes "user", but this endpoint must be bot-only; edit the shortcut
definition (the struct/variable that sets Scopes, AuthTypes, HasFormat in
lingo_entity_update.go) to remove "user" from AuthTypes so it only contains
"bot" (i.e., AuthTypes: []string{"bot"}), leaving Scopes and HasFormat
unchanged; run tests/linters to ensure no other call sites assume "user" auth
for this shortcut.
In `@skills/lark-lingo/references/lark-lingo-get.md`:
- Around line 22-27: Update the parameter table to clarify that lookup requires
either `--entity-id` OR the pair `--provider` + `--outer-id` instead of marking
`--entity-id` as strictly required; change the `--entity-id` "必填" cell to
indicate conditional/one-of requirement (e.g., "必填(`--entity-id` 或
`--provider`+`--outer-id`)") and adjust the `--provider` and `--outer-id` "必填"
cells to note they must be provided together (e.g., "与 `--outer-id`
同时使用,作为替代查找模式"); also update the `--provider`/`--outer-id` descriptions to
explicitly state they form an alternate lookup mode when used together.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: 0fce0e7e-1faf-43ce-bdbf-83b13b20c6d4
📒 Files selected for processing (26)
README.mdREADME.zh.mdinternal/registry/service_descriptions.jsonshortcuts/lingo/helpers_test.goshortcuts/lingo/lingo_entity_create.goshortcuts/lingo/lingo_entity_create_test.goshortcuts/lingo/lingo_entity_delete.goshortcuts/lingo/lingo_entity_delete_test.goshortcuts/lingo/lingo_entity_get.goshortcuts/lingo/lingo_entity_get_test.goshortcuts/lingo/lingo_entity_match.goshortcuts/lingo/lingo_entity_match_test.goshortcuts/lingo/lingo_entity_search.goshortcuts/lingo/lingo_entity_search_test.goshortcuts/lingo/lingo_entity_update.goshortcuts/lingo/lingo_entity_update_test.goshortcuts/lingo/shortcuts.goshortcuts/register.goskills/lark-lingo/SKILL.mdskills/lark-lingo/references/lark-lingo-create.mdskills/lark-lingo/references/lark-lingo-delete.mdskills/lark-lingo/references/lark-lingo-get.mdskills/lark-lingo/references/lark-lingo-match.mdskills/lark-lingo/references/lark-lingo-search.mdskills/lark-lingo/references/lark-lingo-update.mdtests/cli_e2e/lingo/lingo_dryrun_test.go
| Scopes: []string{"baike:entity"}, | ||
| AuthTypes: []string{"user", "bot"}, | ||
| HasFormat: true, |
There was a problem hiding this comment.
Enforce bot-only auth on this write shortcut.
This high-risk write command currently allows user auth type, but delete is documented/expected as bot-only. Allowing user here permits invalid executions that should be blocked earlier by CLI auth gating.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@shortcuts/lingo/lingo_entity_delete.go` around lines 24 - 26, The shortcut
currently lists both "user" and "bot" in the AuthTypes slice, but this delete
operation must be bot-only; locate the shortcut definition in
lingo_entity_delete.go (the struct literal that sets Scopes, AuthTypes,
HasFormat) and remove "user" from the AuthTypes field so it becomes AuthTypes:
[]string{"bot"}. Leave Scopes and HasFormat unchanged and run tests/linters to
ensure no other callsites expect user auth for this delete shortcut.
| path := fmt.Sprintf("/open-apis/lingo/v1/entities/%s", entityID) | ||
| _, err := runtime.DoAPIJSON("DELETE", path, larkcore.QueryParams{}, nil) |
There was a problem hiding this comment.
Avoid sending raw entity-id directly in the URL path segment.
Interpolating unescaped user input into the endpoint path can break routing for IDs containing reserved characters and may hit unintended paths. Encode the path segment before building the final URL.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@shortcuts/lingo/lingo_entity_delete.go` around lines 41 - 42, The code
interpolates entityID directly into path (path :=
fmt.Sprintf("/open-apis/lingo/v1/entities/%s", entityID)) and calls
runtime.DoAPIJSON which can break on reserved characters; URL-escape the path
segment before building the path (use a path-escaping function like
url.PathEscape on entityID) and then pass the escaped value into the fmt.Sprintf
so runtime.DoAPIJSON receives a safe path.
| path := fmt.Sprintf("/open-apis/lingo/v1/entities/%s", runtime.Str("entity-id")) | ||
| data, err := runtime.DoAPIJSON("PUT", path, larkcore.QueryParams{}, body) |
There was a problem hiding this comment.
Do not interpolate raw entity-id into the API path.
Using raw user input in the path segment can produce malformed or unintended endpoint paths when IDs include reserved URL characters. Encode the segment before constructing the request path.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@shortcuts/lingo/lingo_entity_update.go` around lines 69 - 70, The code
currently injects raw runtime.Str("entity-id") into the URL path; instead
URL-encode that segment before building the path to avoid malformed endpoints.
Replace the direct interpolation in the path construction for
lingo_entity_update (where path :=
fmt.Sprintf("/open-apis/lingo/v1/entities/%s", runtime.Str("entity-id"))) by
first encoding the entity-id with a path-encoding function (e.g.,
net/url.PathEscape or your runtime-provided path-escape utility) and then use
the escaped value in fmt.Sprintf so the PUT request via runtime.DoAPIJSON uses a
safe, encoded path.
| | 参数 | 必填 | 说明 | | ||
| |------|------|------| | ||
| | `--entity-id` | 是 | 词条 ID(通常来自 `+match` / `+search` 返回的 `entity_id`) | | ||
| | `--provider` | 否 | 外部系统名;配合 `--outer-id` 使用,用于按外部系统 ID 反查已绑定的词条 | | ||
| | `--outer-id` | 否 | 外部系统 ID;必须与 `--provider` 同时使用 | | ||
|
|
There was a problem hiding this comment.
Parameter table contradicts the documented alternate lookup flow.
--entity-id is marked “required”, but the examples/constraints also document lookup via --provider + --outer-id. Please change this to a conditional requirement (one of these lookup modes must be provided) to avoid misleading users.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@skills/lark-lingo/references/lark-lingo-get.md` around lines 22 - 27, Update
the parameter table to clarify that lookup requires either `--entity-id` OR the
pair `--provider` + `--outer-id` instead of marking `--entity-id` as strictly
required; change the `--entity-id` "必填" cell to indicate conditional/one-of
requirement (e.g., "必填(`--entity-id` 或 `--provider`+`--outer-id`)") and adjust
the `--provider` and `--outer-id` "必填" cells to note they must be provided
together (e.g., "与 `--outer-id` 同时使用,作为替代查找模式"); also update the
`--provider`/`--outer-id` descriptions to explicitly state they form an
alternate lookup mode when used together.
Feishu Lingo entries carry both a plain-text `description` and an HTML `rich_text` field. The existing shortcuts only surfaced the plain-text path, so users could not control formatting (bold main key, multiple paragraphs, inline spans) when creating / updating entries from the CLI. Adds a new `--rich-text` flag on +create and +update that sends the provided HTML directly as the entity's `rich_text` field. Mutually exclusive with `--description` — passing both returns a validation error so the request shape is unambiguous. Common HTML (observed on entries authored via the Feishu Web UI): <p><b>main key zh_name</b><span>description…</span></p> Feishu automatically back-fills the other field: sending only rich_text populates description (stripped text); sending only description populates rich_text (with the main key auto-bolded). Both flags support @file / - (stdin) input to accommodate long HTML bodies. Updates skills/lark-lingo/SKILL.md and the +create / +update reference docs to describe the HTML format, the mutual-exclusion rule, and the PUT-overwrite interaction (supplying only one of the two fields clears the other — fetch with +get and merge first).
Feishu Lingo entries carry a `related_meta` object alongside the plain-text/rich-text body. It links the entry to other concepts — classifications (taxonomy), abbreviations (cross-linked entries), related users, chats, docs, links, oncalls, and uploaded images. The existing +create / +update shortcuts surfaced none of this, so agents could only manage trivial entries — the moment they needed to attach a classification or cross-link an abbreviation they were forced to drop down to `lark-cli api`. Adds `--related-meta` (string, accepts @file / stdin) on both shortcuts. The value is validated as a JSON object up front so malformed payloads fail fast with a clear message instead of producing an opaque server-side error. The object is passed through verbatim as the `related_meta` field; we deliberately do not split each sub-array into its own flag because four of the eight (`docs`, `links`, plus `users` / `chats` when carrying a title) need two-field structs that don't compress into a single string. `+update` is PUT-overwrite: omitting `--related-meta` clears the existing value, matching the existing `--description` / `--aliases` semantics. Reference docs spell this out and recommend +get-first- then-merge for any incremental change. Supported sub-fields (full schema documented in references/): classifications: [{id, father_id?}] # 二级分类,每个一级下唯一 abbreviations: [{id}] # 关联词条 users: [{id, title?}] # open_id 联系人 chats: [{id, title?}] # 公开群 docs: [{title?, url}] # 云文档 links: [{title?, url}] # 外部链接 oncalls: [{id, title?}] # 值班号 images: [{token}] # 已上传图片,最多 10
There was a problem hiding this comment.
Actionable comments posted: 1
♻️ Duplicate comments (2)
shortcuts/lingo/lingo_entity_update.go (2)
92-93:⚠️ Potential issue | 🟠 Major | ⚡ Quick winEscape
entity-idbefore building the PUT path.Line 92 injects raw user input into the URL path segment. Encode the segment first to avoid malformed endpoint paths.
Suggested fix
import ( "context" "fmt" "io" + "net/url" "strings" @@ - path := fmt.Sprintf("/open-apis/lingo/v1/entities/%s", runtime.Str("entity-id")) + escapedID := url.PathEscape(runtime.Str("entity-id")) + path := fmt.Sprintf("/open-apis/lingo/v1/entities/%s", escapedID) data, err := runtime.DoAPIJSON("PUT", path, larkcore.QueryParams{}, body)🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@shortcuts/lingo/lingo_entity_update.go` around lines 92 - 93, The path construction injects raw user input via runtime.Str("entity-id") into path which may produce malformed or unsafe URLs; before formatting the path in lingo_entity_update.go, URL-escape the entity id (e.g., using url.PathEscape or the project's equivalent) and use that escaped value when building the path variable (the code creating path := fmt.Sprintf("/open-apis/lingo/v1/entities/%s", ...)). Ensure the escape is applied to the same runtime.Str("entity-id") result (or assigned to a local variable like entityIDEscaped) so the subsequent runtime.DoAPIJSON("PUT", path, ...) call uses the safe, encoded segment.
31-33:⚠️ Potential issue | 🟠 Major | ⚡ Quick winEnforce bot-only auth for
+update.Line 32 still includes
user; this write shortcut should remain bot-only to match endpoint constraints and prevent avoidable runtime failures.Suggested fix
- AuthTypes: []string{"user", "bot"}, + AuthTypes: []string{"bot"},🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@shortcuts/lingo/lingo_entity_update.go` around lines 31 - 33, The shortcut definition currently lists both "user" and "bot" in the AuthTypes slice for the +update write shortcut; change AuthTypes to only include "bot" (e.g., AuthTypes: []string{"bot"}) so the +update shortcut is enforced as bot-only, leaving Scopes and HasFormat unchanged and updating any similar AuthTypes declarations for the same +update shortcut if present.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@shortcuts/lingo/lingo_entity_create.go`:
- Around line 32-33: The endpoint registration currently allows both "user" and
"bot" auth but must be bot-only; update the AuthTypes slice in the shortcut
registration (where Scopes: []string{"baike:entity"} and AuthTypes is defined)
to only include "bot" (i.e., change AuthTypes: []string{"user", "bot"} to
AuthTypes: []string{"bot"}) so CLI preflight and routing will block
user-authenticated requests to the +create write endpoint.
---
Duplicate comments:
In `@shortcuts/lingo/lingo_entity_update.go`:
- Around line 92-93: The path construction injects raw user input via
runtime.Str("entity-id") into path which may produce malformed or unsafe URLs;
before formatting the path in lingo_entity_update.go, URL-escape the entity id
(e.g., using url.PathEscape or the project's equivalent) and use that escaped
value when building the path variable (the code creating path :=
fmt.Sprintf("/open-apis/lingo/v1/entities/%s", ...)). Ensure the escape is
applied to the same runtime.Str("entity-id") result (or assigned to a local
variable like entityIDEscaped) so the subsequent runtime.DoAPIJSON("PUT", path,
...) call uses the safe, encoded segment.
- Around line 31-33: The shortcut definition currently lists both "user" and
"bot" in the AuthTypes slice for the +update write shortcut; change AuthTypes to
only include "bot" (e.g., AuthTypes: []string{"bot"}) so the +update shortcut is
enforced as bot-only, leaving Scopes and HasFormat unchanged and updating any
similar AuthTypes declarations for the same +update shortcut if present.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: fce7150f-666e-40df-9195-66c00d57cc8e
📒 Files selected for processing (7)
shortcuts/lingo/lingo_entity_create.goshortcuts/lingo/lingo_entity_create_test.goshortcuts/lingo/lingo_entity_update.goshortcuts/lingo/lingo_entity_update_test.goskills/lark-lingo/SKILL.mdskills/lark-lingo/references/lark-lingo-create.mdskills/lark-lingo/references/lark-lingo-update.md
✅ Files skipped from review due to trivial changes (2)
- skills/lark-lingo/references/lark-lingo-create.md
- skills/lark-lingo/SKILL.md
| Scopes: []string{"baike:entity"}, | ||
| AuthTypes: []string{"user", "bot"}, |
There was a problem hiding this comment.
Restrict +create to bot auth only.
Line 33 currently allows user, but this write endpoint is bot-only in the behavior documented for this feature and should be blocked at CLI preflight.
Suggested fix
- AuthTypes: []string{"user", "bot"},
+ AuthTypes: []string{"bot"},📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| Scopes: []string{"baike:entity"}, | |
| AuthTypes: []string{"user", "bot"}, | |
| Scopes: []string{"baike:entity"}, | |
| AuthTypes: []string{"bot"}, |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@shortcuts/lingo/lingo_entity_create.go` around lines 32 - 33, The endpoint
registration currently allows both "user" and "bot" auth but must be bot-only;
update the AuthTypes slice in the shortcut registration (where Scopes:
[]string{"baike:entity"} and AuthTypes is defined) to only include "bot" (i.e.,
change AuthTypes: []string{"user", "bot"} to AuthTypes: []string{"bot"}) so CLI
preflight and routing will block user-authenticated requests to the +create
write endpoint.
🚀 PR Preview Install Guide🧰 CLI updatenpm i -g https://pkg.pr.new/larksuite/cli/@larksuite/cli@371881661ed7495a6f30d688fe11218f484c89d6🧩 Skill updatenpx skills add emac/lark-cli#feat/lingo -y -g |
Codecov Report❌ Patch coverage is ❌ Your patch check has failed because the patch coverage (57.01%) is below the target coverage (60.00%). You can increase the patch coverage or adjust the target coverage. Additional details and impacted files@@ Coverage Diff @@
## main #855 +/- ##
==========================================
+ Coverage 65.75% 65.79% +0.04%
==========================================
Files 513 524 +11
Lines 47886 49103 +1217
==========================================
+ Hits 31486 32308 +822
- Misses 13682 14002 +320
- Partials 2718 2793 +75 ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
Introduces a new
lingoservice covering the six Feishu Lingo / 百科 dictionary entry endpoints as shortcuts, plus the accompanying lark-lingo skill with per-verb reference docs.Shortcuts (shortcuts/lingo/):
+search POST /lingo/v1/entities/search read
+match POST /lingo/v1/entities/match read
+get GET /lingo/v1/entities/:id read
+create POST /lingo/v1/entities write (bot-only)
+update PUT /lingo/v1/entities/:id write (bot-only, full overwrite)
+delete DELETE /lingo/v1/entities/:id high-risk-write (bot-only)
Each shortcut has validate / dry-run / execute hooks and unit tests covering flag validation, dry-run payload, successful execute, and API-error paths. Dry-run e2e tests added under tests/cli_e2e/lingo/.
The skill (skills/lark-lingo/) follows the lark-markdown pattern: concise SKILL.md index + one references/lark-lingo-.md per shortcut. References capture the real-world gotchas we hit during integration:
auth login --scope(which only affects user scope)Also:
Summary
Changes
Test Plan
lark xxxcommand works as expectedRelated Issues
Summary by CodeRabbit
New Features
Documentation
Tests