Skip to content

Add mail signature write shortcuts#1217

Open
alphonseiwnl wants to merge 1 commit into
larksuite:mainfrom
alphonseiwnl:feat/c90d83e
Open

Add mail signature write shortcuts#1217
alphonseiwnl wants to merge 1 commit into
larksuite:mainfrom
alphonseiwnl:feat/c90d83e

Conversation

@alphonseiwnl
Copy link
Copy Markdown

@alphonseiwnl alphonseiwnl commented Jun 2, 2026

Generated by the harness-coding skill.

  • Branch: feat/c90d83e
  • Target: main

Sprints

ID Title Status Commit
S6 Synthesize transport contract for larksuite/cli passed 62ff3d6
S4 Add mail signature create update delete shortcuts passed 764d66f

This MR was created autonomously. Quality gates were enforced by the repo's own pre-commit hooks.

Summary by CodeRabbit

Release Notes

  • New Features

    • Added three mail signature management commands: +signature-create for creating personal email signatures, +signature-update for editing existing signatures, and +signature-delete for removing signatures.
    • Signature creation and updates support inline content, file imports, image metadata, and device-specific variants (PC/mobile).
    • All signature operations support --dry-run mode for validation.
  • Documentation

    • Added comprehensive reference guides for signature creation, updating, and deletion workflows.

Add create, update, and delete signature shortcuts with local validation, dry-run coverage, and mail skill references.

sprint: S4
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Jun 2, 2026

Review Change Stack

📝 Walkthrough

Walkthrough

This PR adds complete support for personal email signature management via three new CLI commands: +signature-create, +signature-update, and +signature-delete. The implementation includes core content validation utilities, API contracts and provider functions, full shortcut command handlers, comprehensive test coverage, and detailed user documentation.

Changes

Mail Signature Management Feature

Layer / File(s) Summary
Signature content validation and normalization
shortcuts/mail/signature_content.go
Parsing, validation, and normalization utilities for signature content and image metadata: CID extraction, local image rejection, device normalization, HTML/plain-text handling, and structured JSON parsing of image metadata with field coercion.
API contracts and write provider operations
shortcuts/mail/signature/model.go, shortcuts/mail/signature/provider.go
Request/response DTOs (WriteSignatureRequest, WriteSignatureResponse) and provider functions (Create, Update, Delete, ClearCache, decodeWriteSignature) for backend API integration.
Create signature command
shortcuts/mail/mail_signature_create.go
MailSignatureCreate shortcut with DryRun, Validate, and Execute supporting inline or file-based content, image metadata validation, content preview, and formatted output.
Delete signature command
shortcuts/mail/mail_signature_delete.go
MailSignatureDelete shortcut enforcing USER-only deletion, requiring --yes flag, and providing verify_status via best-effort re-listing after deletion.
Update signature command
shortcuts/mail/mail_signature_update.go
MailSignatureUpdate shortcut supporting --patch-file merging, --print-patch-template inspection, --inspect mode, full field override with --set-* flags, last-write-wins semantics, and changed-field tracking for dry-run payloads.
Unit, integration, and e2e tests
shortcuts/mail/mail_signature_write_test.go, tests/cli_e2e/mail/mail_signature_dryrun_test.go
Happy-path flows for create/update/delete, plain-text HTML wrapping, validation errors (local images, CID mismatches), TENANT signature rejection, patch-file ID conflict detection, and dry-run request verification.
Shortcut registration
shortcuts/mail/shortcuts.go
Registers three new signature shortcuts in the Shortcuts() function.
User documentation and references
skill-template/domains/mail.md, skills/lark-mail/SKILL.md, skills/lark-mail/references/lark-mail-signature-*.md
Domain template and skill documentation covering signature concepts, workflow integration, command usage, parameters, constraints (USER-only, no local images, last-write-wins), and reference pages for each operation.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

  • larksuite/cli#485: Adds signature listing/interpolation/compose insertion plumbing in the shared shortcuts/mail/signature layer.

Suggested labels

domain/mail, size/XL

Suggested reviewers

  • chanthuang
  • infeng

Poem

🐰 Three new signatures bloom with care,
Create, delete, and update fair—
With patches merged and images validated tight,
Personal mail touches, done just right!
Last-write-wins, no locks to fight,
The bunny hops through signatures with delight! 🎉

🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (2 warnings)

Check name Status Explanation Resolution
Description check ⚠️ Warning The PR description is missing critical template sections (Summary, Changes, Test Plan, Related Issues). While it provides branch/sprint metadata, it lacks the required structured information about motivation, scope, main changes, and test verification. Add a proper PR description following the template: include Summary (motivation/scope), Changes (bullet list), Test Plan (with checkboxes), and Related Issues sections.
Docstring Coverage ⚠️ Warning Docstring coverage is 15.79% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The title 'Add mail signature write shortcuts' directly and clearly summarizes the main change: introducing three new mail signature write shortcuts (create, update, delete).
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
⚔️ Resolve merge conflicts
  • Resolve merge conflict in branch feat/c90d83e

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@CLAassistant
Copy link
Copy Markdown

CLA assistant check
Thank you for your submission! We really appreciate it. Like many open source projects, we ask that you sign our Contributor License Agreement before we can accept your contribution.


root seems not to be a GitHub user. You need a GitHub account to be able to sign the CLA. If you have already a GitHub account, please add the email address used for this commit to your account.
You have signed the CLA already but the status is still pending? Let us recheck it.

@github-actions github-actions Bot added domain/mail PR touches the mail domain size/L Large or sensitive change across domains or core paths labels Jun 2, 2026
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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/mail/mail_signature_create.go`:
- Around line 112-115: The follow-up hint is being printed to stdout via
runtime.OutFormat's writer; keep the created signature output on stdout and move
the usage hint to stderr by removing the fmt.Fprintln(w, ...) call inside the
runtime.OutFormat callback and instead write the hint to runtime.IO().ErrOut
(e.g. fmt.Fprintln(runtime.IO().ErrOut, "Use this signature_id with mail +send /
+reply / +forward --signature-id.")) after or outside the OutFormat call; leave
formatSignatureSummary(...) and the OutFormat invocation unchanged so only the
hint is redirected to stderr.

In `@shortcuts/mail/mail_signature_update.go`:
- Around line 241-246: Remove the duplicate concurrency warning from the data
output path: in the block that calls runtime.OutFormat(..., func(w io.Writer) {
formatSignatureSummary(...); fmt.Fprintln(w, "...") }), drop the fmt.Fprintln(w,
"warning: no optimistic locking; concurrent updates are last-write-wins.") so
the warning is not written into the OutFormat envelope; keep the existing
fmt.Fprintln(runtime.IO().ErrOut, "warning: signature endpoints have no
optimistic locking; concurrent updates are last-write-wins.") to ensure the
warning is emitted only to stderr. Ensure calls to formatSignatureSummary(...)
and the OutFormat invocation remain unchanged otherwise.

In `@shortcuts/mail/signature_content.go`:
- Around line 160-175: The current rejectSignatureLocalImages allows any
non-empty URL scheme which lets file:, data:, blob:, etc. through; update
rejectSignatureLocalImages to allow only an explicit whitelist of remote/CID
schemes (http, https, cid) and keep protocol-relative sources
(strings.HasPrefix(src, "//")) allowed, but treat all other schemes (including
file, data, blob) and schemeless local paths as invalid and return
output.ErrValidation; use the existing extractSignatureImageSources and
url.Parse(src) logic to inspect the scheme and enforce this allowlist.

In `@skills/lark-mail/references/lark-mail-signature-create.md`:
- Line 32: The table cell containing the parameter text `--device pc|mobile` is
breaking the Markdown table due to the unescaped `|`; update that cell to escape
the pipe (e.g., change `--device pc|mobile` to `--device pc\|mobile` or
otherwise ensure the pipe is escaped/encoded) so the table renders correctly
while keeping the inline code formatting intact.

In `@skills/lark-mail/references/lark-mail-signature-update.md`:
- Line 37: Update the Markdown table row that currently shows the option
"--set-device pc|mobile" so the pipe character inside the option is escaped:
insert a backslash before the '|' (i.e., pc\|mobile) within the existing inline
code span for --set-device to prevent the '|' from being interpreted as a table
column separator and thus keep the table columns valid.
🪄 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: 5e7b87e9-9f42-440b-81a0-311427b0a431

📥 Commits

Reviewing files that changed from the base of the PR and between 4710a29 and 764d66f.

📒 Files selected for processing (14)
  • shortcuts/mail/mail_signature_create.go
  • shortcuts/mail/mail_signature_delete.go
  • shortcuts/mail/mail_signature_update.go
  • shortcuts/mail/mail_signature_write_test.go
  • shortcuts/mail/shortcuts.go
  • shortcuts/mail/signature/model.go
  • shortcuts/mail/signature/provider.go
  • shortcuts/mail/signature_content.go
  • skill-template/domains/mail.md
  • skills/lark-mail/SKILL.md
  • skills/lark-mail/references/lark-mail-signature-create.md
  • skills/lark-mail/references/lark-mail-signature-delete.md
  • skills/lark-mail/references/lark-mail-signature-update.md
  • tests/cli_e2e/mail/mail_signature_dryrun_test.go

Comment on lines +112 to +115
runtime.OutFormat(out, nil, func(w io.Writer) {
formatSignatureSummary(w, "created", created, resolveLang(runtime))
fmt.Fprintln(w, "Use this signature_id with mail +send / +reply / +forward --signature-id.")
})
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Move the follow-up hint to stderr.

Line 114 writes a usage hint through OutFormat, so non-result text is mixed into the command’s stdout stream. Keep the created signature data on stdout and send the hint to runtime.IO().ErrOut instead.

Proposed fix
 		runtime.OutFormat(out, nil, func(w io.Writer) {
 			formatSignatureSummary(w, "created", created, resolveLang(runtime))
-			fmt.Fprintln(w, "Use this signature_id with mail +send / +reply / +forward --signature-id.")
 		})
+		fmt.Fprintln(runtime.IO().ErrOut, "hint: use this signature_id with mail +send / +reply / +forward --signature-id.")
 		return nil
 	},

As per coding guidelines, stdout is data (JSON envelopes), stderr is everything else (progress, warnings, hints).

📝 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.

Suggested change
runtime.OutFormat(out, nil, func(w io.Writer) {
formatSignatureSummary(w, "created", created, resolveLang(runtime))
fmt.Fprintln(w, "Use this signature_id with mail +send / +reply / +forward --signature-id.")
})
runtime.OutFormat(out, nil, func(w io.Writer) {
formatSignatureSummary(w, "created", created, resolveLang(runtime))
})
fmt.Fprintln(runtime.IO().ErrOut, "hint: use this signature_id with mail +send / +reply / +forward --signature-id.")
🤖 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/mail/mail_signature_create.go` around lines 112 - 115, The
follow-up hint is being printed to stdout via runtime.OutFormat's writer; keep
the created signature output on stdout and move the usage hint to stderr by
removing the fmt.Fprintln(w, ...) call inside the runtime.OutFormat callback and
instead write the hint to runtime.IO().ErrOut (e.g.
fmt.Fprintln(runtime.IO().ErrOut, "Use this signature_id with mail +send /
+reply / +forward --signature-id.")) after or outside the OutFormat call; leave
formatSignatureSummary(...) and the OutFormat invocation unchanged so only the
hint is redirected to stderr.

Comment on lines +241 to +246
runtime.OutFormat(out, nil, func(w io.Writer) {
formatSignatureSummary(w, "updated", updated, resolveLang(runtime))
fmt.Fprintln(w, "warning: no optimistic locking; concurrent updates are last-write-wins.")
})
fmt.Fprintln(runtime.IO().ErrOut,
"warning: signature endpoints have no optimistic locking; concurrent updates are last-write-wins.")
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Send the concurrency warning to stderr only.

Line 243 currently pushes a warning through OutFormat and then lines 245-246 print the same warning to stderr again. Drop the stdout copy so the result stream stays data-only and the warning remains a single stderr message.

Proposed fix
 		runtime.OutFormat(out, nil, func(w io.Writer) {
 			formatSignatureSummary(w, "updated", updated, resolveLang(runtime))
-			fmt.Fprintln(w, "warning: no optimistic locking; concurrent updates are last-write-wins.")
 		})
 		fmt.Fprintln(runtime.IO().ErrOut,
 			"warning: signature endpoints have no optimistic locking; concurrent updates are last-write-wins.")

As per coding guidelines, stdout is data (JSON envelopes), stderr is everything else (progress, warnings, hints).

🤖 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/mail/mail_signature_update.go` around lines 241 - 246, Remove the
duplicate concurrency warning from the data output path: in the block that calls
runtime.OutFormat(..., func(w io.Writer) { formatSignatureSummary(...);
fmt.Fprintln(w, "...") }), drop the fmt.Fprintln(w, "warning: no optimistic
locking; concurrent updates are last-write-wins.") so the warning is not written
into the OutFormat envelope; keep the existing fmt.Fprintln(runtime.IO().ErrOut,
"warning: signature endpoints have no optimistic locking; concurrent updates are
last-write-wins.") to ensure the warning is emitted only to stderr. Ensure calls
to formatSignatureSummary(...) and the OutFormat invocation remain unchanged
otherwise.

Comment on lines +160 to +175
func rejectSignatureLocalImages(content string) error {
for _, src := range extractSignatureImageSources(content) {
if src == "" {
continue
}
if strings.HasPrefix(src, "//") {
continue
}
u, err := url.Parse(src)
if err == nil && u.Scheme != "" {
continue
}
return output.ErrValidation("local image paths are not supported for signature content; use --images-json with cid/file_key")
}
return nil
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

file:/data: image sources bypass local-image rejection.

The guard only rejects schemeless sources, treating any non-empty scheme as remote/allowed. That lets <img src="file:///home/user/secret.png"> and data: URIs through, which are exactly the local/inline references this validator is meant to block in favor of --images-json with cid/file_key. Prefer an explicit allowlist of remote/CID schemes.

🛡️ Proposed fix: allowlist http/https/cid
 func rejectSignatureLocalImages(content string) error {
 	for _, src := range extractSignatureImageSources(content) {
 		if src == "" {
 			continue
 		}
 		if strings.HasPrefix(src, "//") {
 			continue
 		}
-		u, err := url.Parse(src)
-		if err == nil && u.Scheme != "" {
-			continue
-		}
+		if u, err := url.Parse(src); err == nil {
+			switch strings.ToLower(u.Scheme) {
+			case "http", "https", "cid":
+				continue
+			}
+		}
 		return output.ErrValidation("local image paths are not supported for signature content; use --images-json with cid/file_key")
 	}
 	return nil
 }
📝 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.

Suggested change
func rejectSignatureLocalImages(content string) error {
for _, src := range extractSignatureImageSources(content) {
if src == "" {
continue
}
if strings.HasPrefix(src, "//") {
continue
}
u, err := url.Parse(src)
if err == nil && u.Scheme != "" {
continue
}
return output.ErrValidation("local image paths are not supported for signature content; use --images-json with cid/file_key")
}
return nil
}
func rejectSignatureLocalImages(content string) error {
for _, src := range extractSignatureImageSources(content) {
if src == "" {
continue
}
if strings.HasPrefix(src, "//") {
continue
}
if u, err := url.Parse(src); err == nil {
switch strings.ToLower(u.Scheme) {
case "http", "https", "cid":
continue
}
}
return output.ErrValidation("local image paths are not supported for signature content; use --images-json with cid/file_key")
}
return nil
}
🤖 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/mail/signature_content.go` around lines 160 - 175, The current
rejectSignatureLocalImages allows any non-empty URL scheme which lets file:,
data:, blob:, etc. through; update rejectSignatureLocalImages to allow only an
explicit whitelist of remote/CID schemes (http, https, cid) and keep
protocol-relative sources (strings.HasPrefix(src, "//")) allowed, but treat all
other schemes (including file, data, blob) and schemeless local paths as invalid
and return output.ErrValidation; use the existing extractSignatureImageSources
and url.Parse(src) logic to inspect the scheme and enforce this allowlist.

| `--name <text>` | 是 | 签名名称,trim 后非空且 ≤100 字符 |
| `--content <html-or-text>` | 否 | 签名内容;与 `--content-file` 互斥 |
| `--content-file <path>` | 否 | 从相对路径读取签名内容 |
| `--device pc|mobile` | 否 | 签名设备,默认 `pc` |
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Escape pipe in parameter cell to prevent table breakage.

Line 32 uses pc|mobile inside a Markdown table cell; the raw | is parsed as a new column and corrupts rendering.

Suggested fix
-| `--device pc|mobile` | 否 | 签名设备,默认 `pc` |
+| `--device pc\|mobile` | 否 | 签名设备,默认 `pc` |
📝 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.

Suggested change
| `--device pc|mobile` || 签名设备,默认 `pc` |
| `--device pc\|mobile` || 签名设备,默认 `pc` |
🧰 Tools
🪛 markdownlint-cli2 (0.22.1)

[warning] 32-32: Table column count
Expected: 3; Actual: 4; Too many cells, extra data will be missing

(MD056, table-column-count)

🤖 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-mail/references/lark-mail-signature-create.md` at line 32, The
table cell containing the parameter text `--device pc|mobile` is breaking the
Markdown table due to the unescaped `|`; update that cell to escape the pipe
(e.g., change `--device pc|mobile` to `--device pc\|mobile` or otherwise ensure
the pipe is escaped/encoded) so the table renders correctly while keeping the
inline code formatting intact.

| `--set-name <text>` | 否 | 替换名称,trim 后非空且 ≤100 字符 |
| `--set-content <html-or-text>` | 否 | 替换内容;与 `--set-content-file` 互斥 |
| `--set-content-file <path>` | 否 | 从相对路径读取替换内容 |
| `--set-device pc|mobile` | 否 | 替换设备 |
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Escape | in option syntax to keep table columns valid.

Line 37 currently creates an extra Markdown table column due to pc|mobile.

Suggested fix
-| `--set-device pc|mobile` | 否 | 替换设备 |
+| `--set-device pc\|mobile` | 否 | 替换设备 |
📝 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.

Suggested change
| `--set-device pc|mobile` || 替换设备 |
| `--set-device pc\|mobile` || 替换设备 |
🧰 Tools
🪛 markdownlint-cli2 (0.22.1)

[warning] 37-37: Table column count
Expected: 3; Actual: 4; Too many cells, extra data will be missing

(MD056, table-column-count)

🤖 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-mail/references/lark-mail-signature-update.md` at line 37, Update
the Markdown table row that currently shows the option "--set-device pc|mobile"
so the pipe character inside the option is escaped: insert a backslash before
the '|' (i.e., pc\|mobile) within the existing inline code span for --set-device
to prevent the '|' from being interpreted as a table column separator and thus
keep the table columns valid.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

domain/mail PR touches the mail domain size/L Large or sensitive change across domains or core paths

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants