Skip to content

feat(slides): add image upload via +media-upload and @path placeholders in +create#450

Open
ViperCai wants to merge 1 commit intomainfrom
feat/slides-image-upload
Open

feat(slides): add image upload via +media-upload and @path placeholders in +create#450
ViperCai wants to merge 1 commit intomainfrom
feat/slides-image-upload

Conversation

@ViperCai
Copy link
Copy Markdown
Collaborator

@ViperCai ViperCai commented Apr 13, 2026

Summary

Adds first-class image support to the slides skill. Users (and AI agents) can now upload local images into a presentation and embed them via XML, either by reusing a returned file_token or by writing <img src=\"@./path\"> placeholders inside +create --slides and letting the CLI auto-upload + rewrite.

Changes

  • New slides +media-upload shortcut: uploads a local image to a presentation and returns its file_token. Validates --file is a CWD-relative safe path; uses slide_file as parent_type (the only value the slides upload endpoint accepts).
  • slides +create --slides now scans each slide XML for <img src=\"@./...\"> placeholders, uploads the referenced files via the same code path, and rewrites src to the resulting file_token before creating slides.
  • Skill docs (skills/lark-slides/...) expanded:
    • Surfaces the upload flow up-front in SKILL.md so it is discoverable.
    • Documents image crop behavior (width:height must match original aspect or the image is cropped).
    • Adds reusable image layout templates in references/slide-templates.md.
    • Documents the whole-page-replacement recipe for adding an image to an existing page (no element-level edit API), inline in SKILL.md.
    • Explicitly warns that before_slide_id belongs in --data body — putting it in --params is silently forwarded as an unknown query param and the new slide ends up appended to the end instead of inserted at the requested position.

Test Plan

  • Unit tests pass: go test ./shortcuts/slides/... ./cmd/service/...
  • Manual local verification:
    • lark-cli slides +create --slides '[...<img src=\"@./pic.png\".../>...]' uploads and embeds the image
    • lark-cli slides +media-upload --file ./pic.png --presentation $PID returns a file_token
    • Real API insert with before_slide_id in --data lands the new slide at the requested position; placing it in --params (the documented anti-pattern) silently appends to the end, confirming the warning is needed
  • node scripts/skill-format-check/index.js passes

Related Issues

  • None

Summary by CodeRabbit

Release Notes

  • New Features

    • Added image upload support for slides with automatic placeholder replacement in slide creation.
    • New media upload command enables uploading images to existing presentations.
  • Documentation

    • Added comprehensive guides for image workflows and media upload usage.
    • Added image-based slide layout templates with examples.
    • Updated slide creation documentation with path validation and image handling constraints.

@github-actions github-actions bot added the size/L Large or sensitive change across domains or core paths label Apr 13, 2026
@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Apr 13, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

This PR introduces image media upload functionality for Feishu slides. It adds helper functions for parsing and resolving presentation references (supporting raw tokens, slides URLs, and wiki URLs), image placeholder detection and replacement in XML, a new SlidesMediaUpload shortcut for uploading individual images, and enhances SlidesCreate to automatically upload images referenced via @./path placeholders in slide XML. Comprehensive tests and documentation accompany the implementation.

Changes

Cohort / File(s) Summary
Slide Helpers & Testing
shortcuts/slides/helpers.go, shortcuts/slides/helpers_test.go
Core utilities for parsing presentation references (--presentation input supporting raw tokens, /slides/, /wiki/ URLs), resolving wiki tokens to presentation IDs via API, and extracting/replacing image placeholders (@<path>) in slide XML with file tokens.
SlidesCreate Enhancement
shortcuts/slides/slides_create.go, shortcuts/slides/slides_create_test.go
Extended SlidesCreate to detect, validate, and auto-upload local images referenced as @<path> placeholders in slide XML before creation. Adds uploadSlidesPlaceholders helper, updates Validate/DryRun/Execute to handle uploads, records images_uploaded in output, and includes regression tests for placeholder workflow.
SlidesMediaUpload Shortcut
shortcuts/slides/slides_media_upload.go, shortcuts/slides/slides_media_upload_test.go
New shortcut (+media-upload) that uploads a local image file to a presentation (specified via token, slides URL, or wiki URL), handles single-part vs. multipart upload based on file size, resolves wiki references to actual presentation IDs, and outputs file_token for embedding in slide XML. Includes comprehensive tests for URL parsing, multipart encoding, wiki resolution, and error handling.
Shortcuts Registration
shortcuts/slides/shortcuts.go
Adds SlidesMediaUpload to the exported shortcuts list returned by Shortcuts().
Documentation
skills/lark-slides/SKILL.md, skills/lark-slides/references/lark-slides-media-upload.md, skills/lark-slides/references/lark-slides-create.md, skills/lark-slides/references/lark-slides-xml-presentation-slide-create.md, skills/lark-slides/references/slide-templates.md, skills/lark-slides/references/xml-format-guide.md, skills/lark-slides/references/xml-schema-quick-ref.md
Documents new +media-upload shortcut, image placeholder syntax (@<path>), path safety constraints, multi-step workflows for adding images to existing presentations, template examples with images, and clarifies that external HTTP(s) URLs are disallowed in <img src>.

Sequence Diagram

sequenceDiagram
    actor User
    participant CLI as lark-cli<br/>(SlidesCreate)
    participant SlidesAPI as Slides API
    participant WikiAPI as Wiki API
    participant DriveAPI as Drive API

    User->>CLI: +create --slides '[...]' with @./img.png
    
    CLI->>CLI: Parse presentation ref & extract placeholders
    
    alt Wiki reference
        CLI->>WikiAPI: GET /wiki/v2/spaces/get_node
        WikiAPI-->>CLI: obj_token (resolve to presentation ID)
    end
    
    CLI->>CLI: Validate local file paths exist
    
    CLI->>DriveAPI: POST /drive/v1/medias/upload_all<br/>(for each unique placeholder)
    DriveAPI-->>CLI: file_token
    
    CLI->>CLI: Replace @./img.png with file_token in XML
    
    CLI->>SlidesAPI: POST .../slides (create presentation)
    SlidesAPI-->>CLI: presentation_id
    
    CLI->>SlidesAPI: POST .../slides/.../slide (add slides with tokens)
    SlidesAPI-->>CLI: slide_id
    
    CLI-->>User: JSON output with images_uploaded, file_tokens
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~50 minutes

Possibly related PRs

Suggested labels

domain/ccm, size/XL

Suggested reviewers

  • fangshuyu-768

Poem

🐰 A rabbit hops through slides with glee,
With pictures placed where they should be,
Upload and parse, replace with care,
@./pathfile_token fair,
Wiki flows and placeholders too,
Feishu slides painted fresh and new! 🎨✨

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 75.76% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and concisely describes the main feature additions: image upload via a new +media-upload shortcut and @path placeholder support in +create.
Description check ✅ Passed The description follows the template structure with Summary, Changes, and Test Plan sections, providing clear context on the new image support feature and verification methods.

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

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/slides-image-upload

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.

@ViperCai ViperCai force-pushed the feat/slides-image-upload branch from 7aaaf28 to 0b8e956 Compare April 13, 2026 11:50
@github-actions
Copy link
Copy Markdown

github-actions bot commented Apr 13, 2026

🚀 PR Preview Install Guide

🧰 CLI update

npm i -g https://pkg.pr.new/larksuite/cli/@larksuite/cli@001d597adc153b33e9ecbafc105dfe9aaccc919d

🧩 Skill update

npx skills add larksuite/cli#feat/slides-image-upload -y -g

@ViperCai ViperCai force-pushed the feat/slides-image-upload branch from 0b8e956 to aa3bbcb Compare April 13, 2026 11:52
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: 4

🧹 Nitpick comments (1)
shortcuts/slides/slides_create_test.go (1)

737-750: 补一条 tok_b 断言,避免替换映射漏检

当前只强校验了 tok_a,建议再校验至少一页包含 tok_b,防止 @b.png 被误替换成 tok_a 仍然过测。

✅ 建议补充断言
 	// Assert each slide.create body uses tokens (not `@path` placeholders).
+	hasTokB := false
 	for _, stub := range []*httpmock.Stub{slideStub1, slideStub2} {
 		var body map[string]interface{}
 		if err := json.Unmarshal(stub.CapturedBody, &body); err != nil {
 			t.Fatalf("decode slide body: %v", err)
 		}
 		slide, _ := body["slide"].(map[string]interface{})
 		content, _ := slide["content"].(string)
 		if strings.Contains(content, "@a.png") || strings.Contains(content, "@b.png") {
 			t.Fatalf("slide content still contains placeholder: %s", content)
 		}
 		if !strings.Contains(content, "tok_a") {
 			t.Fatalf("slide content missing tok_a: %s", content)
 		}
+		if strings.Contains(content, "tok_b") {
+			hasTokB = true
+		}
 	}
+	if !hasTokB {
+		t.Fatal("expected at least one slide body to contain tok_b")
+	}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@shortcuts/slides/slides_create_test.go` around lines 737 - 750, The test
currently only asserts presence of "tok_a" in each slide body, which can miss
cases where both placeholders were replaced with the same token; update the test
around the loop that iterates over slideStub1 and slideStub2 (variables
slideStub1, slideStub2, body, slide, content) to also verify that at least one
slide contains "tok_b" (e.g., set a foundTokB flag when
strings.Contains(content, "tok_b") is true) and fail the test if foundTokB is
false after the loop, while keeping the existing checks that no
"@a.png"/"@b.png" remain and that "tok_a" is present.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@shortcuts/slides/helpers.go`:
- Around line 34-45: The code currently uses extractRefToken(raw, "/slides/")
and extractRefToken(raw, "/wiki/") which match those substrings anywhere; change
the logic to first try parsing raw as a URL (using net/url.Parse) and only
accept the ref if the parsed URL's Path has the prefix "/slides/" or "/wiki/"
respectively, extracting the token from u.Path; for non-URL inputs, only accept
raw forms that start with "/slides/" or "/wiki/" (use strings.HasPrefix) and
reject cases where those markers appear in queries/fragments or mid-path; update
the same pattern in the later block (lines around 49-63) and keep returning
presentationRef{Kind: "...", Token: token} or the same validation error via
output.ErrValidation when not matched.

In `@shortcuts/slides/slides_create.go`:
- Around line 45-55: The loop that validates placeholder paths (using
extractImagePlaceholderPaths and runtime.FileIO().Stat) performs filesystem Stat
on user-supplied paths without sanitizing them; update the loop to call
validate.SafeInputPath(path) first and handle its error (returning a FlagErrorf
or wrapped input error consistent with existing patterns) before calling
runtime.FileIO().Stat, so only safe, CWD-relative paths are allowed; ensure you
reference the same variables and preserve the existing error-return behavior in
slides_create.go.

In `@shortcuts/slides/slides_media_upload.go`:
- Around line 41-46: The Validate function currently only checks
parsePresentationRef(runtime.Str("presentation")) but does not validate the
user-controlled --file input; update the Validate block to call
validate.SafeInputPath(ctx, runtime.Str("file")) and return any error so
absolute paths or ".." are rejected before Stat/upload planning. Keep the
existing presentation check (parsePresentationRef) and ensure the error from
validate.SafeInputPath is propagated from Validate.

In `@skills/lark-slides/references/xml-schema-quick-ref.md`:
- Line 135: 将句子中的笔误“本地图详见”替换为更通顺明确的表述,例如“本地路径详见”或“本地图片详见”;在该文件中查找包含文本片段“本地图详见
[lark-slides-create.md] / [lark-slides-media-upload.md]”的段落并用“本地路径详见
[lark-slides-create.md](lark-slides-create.md#本地图片path-占位符) / 本地图片详见
[lark-slides-media-upload.md](lark-slides-media-upload.md)”之类的明确表述替换原文以消除歧义。

---

Nitpick comments:
In `@shortcuts/slides/slides_create_test.go`:
- Around line 737-750: The test currently only asserts presence of "tok_a" in
each slide body, which can miss cases where both placeholders were replaced with
the same token; update the test around the loop that iterates over slideStub1
and slideStub2 (variables slideStub1, slideStub2, body, slide, content) to also
verify that at least one slide contains "tok_b" (e.g., set a foundTokB flag when
strings.Contains(content, "tok_b") is true) and fail the test if foundTokB is
false after the loop, while keeping the existing checks that no
"@a.png"/"@b.png" remain and that "tok_a" is 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: c18fb66d-bf05-443c-b3e5-1e414277e66e

📥 Commits

Reviewing files that changed from the base of the PR and between 88fd3bd and 7aaaf28.

📒 Files selected for processing (14)
  • shortcuts/slides/helpers.go
  • shortcuts/slides/helpers_test.go
  • shortcuts/slides/shortcuts.go
  • shortcuts/slides/slides_create.go
  • shortcuts/slides/slides_create_test.go
  • shortcuts/slides/slides_media_upload.go
  • shortcuts/slides/slides_media_upload_test.go
  • skills/lark-slides/SKILL.md
  • skills/lark-slides/references/lark-slides-create.md
  • skills/lark-slides/references/lark-slides-media-upload.md
  • skills/lark-slides/references/lark-slides-xml-presentation-slide-create.md
  • skills/lark-slides/references/slide-templates.md
  • skills/lark-slides/references/xml-format-guide.md
  • skills/lark-slides/references/xml-schema-quick-ref.md

@greptile-apps
Copy link
Copy Markdown

greptile-apps bot commented Apr 13, 2026

Greptile Summary

This PR adds first-class image support to the slides skill: a new +media-upload shortcut that uploads a local file and returns a file_token, and @<path> placeholder auto-upload in +create --slides. The implementation is clean and well-tested, with good documentation across the reference files.

  • P1 — wiki:node:read declared unconditionally in SlidesMediaUpload.Scopes: users whose credential carries scope metadata but does not include wiki:node:read will hit a pre-flight "missing scope" error even when passing a raw xml_presentation_id or a /slides/ URL. This is the same pattern as the docs:document.media:upload issue already flagged for +create.

Confidence Score: 4/5

  • Safe to merge after resolving the unconditional wiki:node:read scope in +media-upload, which will pre-flight-block users who only use raw tokens or slides URLs.
  • One P1 remains in slides_media_upload.go (wiki:node:read declared unconditionally) and a P2 dry-run inconsistency for wiki refs. The +create scope issue was already flagged in prior review rounds. All other logic, tests, and documentation look solid.
  • shortcuts/slides/slides_media_upload.go — Scopes field and DryRun's presentation_id for wiki refs.

Important Files Changed

Filename Overview
shortcuts/slides/slides_media_upload.go New +media-upload shortcut. Has two issues: wiki:node:read is declared unconditionally (will block non-wiki users with explicit scope configs), and dry-run presentation_id shows the unresolved wiki token for wiki refs instead of a placeholder.
shortcuts/slides/helpers.go New helper file: presentation ref parsing, wiki URL resolution, and image placeholder regex/extract/replace logic. Well-implemented; mismatched-quote handling (addressed via post-match filter since Go RE2 has no backrefs) is correct and documented.
shortcuts/slides/slides_create.go Adds @-placeholder upload pipeline to +create. Logic is sound; the unconditional docs:document.media:upload scope issue was already flagged in the prior review thread.
shortcuts/slides/helpers_test.go Thorough unit tests for parsePresentationRef, extractImagePlaceholderPaths, and replaceImagePlaceholders, including regression cases for mismatched quotes and whitespace around =.
shortcuts/slides/slides_media_upload_test.go Good coverage of happy path, URL formats, wiki resolution, wrong-type rejection, missing-file error, and dry-run output. No test for wiki dry-run's presentation_id inconsistency.

Sequence Diagram

sequenceDiagram
    participant User
    participant CLI
    participant WikiAPI as wiki get_node
    participant DriveAPI as drive upload_all
    participant SlidesAPI as slides_ai xml_presentations

    Note over CLI: +create --slides with @path placeholders
    CLI->>SlidesAPI: POST create blank presentation
    SlidesAPI-->>CLI: presentation id
    loop Each unique placeholder path
        CLI->>DriveAPI: "POST upload_all (parent_type=slide_file)"
        DriveAPI-->>CLI: file token
    end
    Note over CLI: replaceImagePlaceholders
    loop Each slide
        CLI->>SlidesAPI: POST add slide (rewritten XML)
        SlidesAPI-->>CLI: slide id
    end
    CLI-->>User: result with images_uploaded count

    Note over CLI: +media-upload with wiki URL
    CLI->>WikiAPI: GET get_node with wiki token
    WikiAPI-->>CLI: "obj_type=slides, obj_token"
    CLI->>DriveAPI: "POST upload_all (parent_type=slide_file)"
    DriveAPI-->>CLI: file token
    CLI-->>User: file_token and presentation_id
Loading

Reviews (5): Last reviewed commit: "feat(slides): add image upload via +medi..." | Re-trigger Greptile

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.

♻️ Duplicate comments (2)
shortcuts/slides/helpers.go (1)

34-38: ⚠️ Potential issue | 🟠 Major

Anchor presentation token parsing to URL/raw path prefixes, not substring matches.

Current parsing accepts /slides/ or /wiki/ anywhere in the input, so query/fragment/mid-string matches can be treated as valid references and target the wrong token.

Suggested fix
 import (
 	"fmt"
+	"net/url"
 	"regexp"
 	"strings"
@@
 func parsePresentationRef(input string) (presentationRef, error) {
 	raw := strings.TrimSpace(input)
 	if raw == "" {
 		return presentationRef{}, output.ErrValidation("--presentation cannot be empty")
 	}
-	if token, ok := extractRefToken(raw, "/slides/"); ok {
-		return presentationRef{Kind: "slides", Token: token}, nil
-	}
-	if token, ok := extractRefToken(raw, "/wiki/"); ok {
-		return presentationRef{Kind: "wiki", Token: token}, nil
+
+	if u, err := url.Parse(raw); err == nil && u.Scheme != "" && u.Host != "" {
+		if token, ok := extractRefTokenFromPath(u.Path, "/slides/"); ok {
+			return presentationRef{Kind: "slides", Token: token}, nil
+		}
+		if token, ok := extractRefTokenFromPath(u.Path, "/wiki/"); ok {
+			return presentationRef{Kind: "wiki", Token: token}, nil
+		}
+		return presentationRef{}, output.ErrValidation("unsupported --presentation input %q: use an xml_presentation_id, a /slides/ URL, or a /wiki/ URL", raw)
+	}
+
+	if token, ok := extractRefTokenFromPath(raw, "/slides/"); ok {
+		return presentationRef{Kind: "slides", Token: token}, nil
+	}
+	if token, ok := extractRefTokenFromPath(raw, "/wiki/"); ok {
+		return presentationRef{Kind: "wiki", Token: token}, nil
 	}
@@
-func extractRefToken(raw, marker string) (string, bool) {
-	idx := strings.Index(raw, marker)
-	if idx < 0 {
+func extractRefTokenFromPath(path, marker string) (string, bool) {
+	if !strings.HasPrefix(path, marker) {
 		return "", false
 	}
-	token := raw[idx+len(marker):]
+	token := path[len(marker):]
 	if end := strings.IndexAny(token, "/?#"); end >= 0 {
 		token = token[:end]
 	}

Also applies to: 49-56

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@shortcuts/slides/helpers.go` around lines 34 - 38, The code currently treats
any substring match of "/slides/" or "/wiki/" in raw as a valid token (via
extractRefToken), which allows matches in queries/fragments; change the logic to
anchor to the URL/raw path prefix: parse raw with net/url.Parse (or otherwise
extract the path component) and then use a prefix check (e.g., strings.HasPrefix
on u.Path) when calling extractRefToken or replace extractRefToken to accept a
path and only return a token when the path begins with "/slides/" or "/wiki/".
Update both the block creating presentationRef for slides/wiki (the existing ifs
using extractRefToken) and the similar logic around lines 49-56 so they operate
on the URL path, not a substring of the whole raw input.
shortcuts/slides/slides_media_upload.go (1)

41-46: ⚠️ Potential issue | 🔴 Critical

Validate --file with validate.SafeInputPath before any file I/O.

--file is user-controlled and reaches Stat without safety validation. Add the guard in Validate so absolute/traversal paths are rejected early.

Suggested fix
 import (
 	"context"
 	"fmt"
 	"path/filepath"
 
 	"github.com/larksuite/cli/extension/fileio"
 	"github.com/larksuite/cli/internal/output"
+	"github.com/larksuite/cli/internal/validate"
 	"github.com/larksuite/cli/shortcuts/common"
 )
@@
 	Validate: func(ctx context.Context, runtime *common.RuntimeContext) error {
 		if _, err := parsePresentationRef(runtime.Str("presentation")); err != nil {
 			return err
 		}
+		if err := validate.SafeInputPath(runtime.Str("file")); err != nil {
+			return err
+		}
 		return nil
 	},

As per coding guidelines, **/*.go: “Validate paths using validate.SafeInputPath before any file I/O operations”.

Also applies to: 81-87

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@shortcuts/slides/slides_media_upload.go` around lines 41 - 46, The Validate
function currently parses the presentation ref but does not validate the
user-supplied file path; call validate.SafeInputPath on runtime.Str("file")
inside the same Validate (and the other Validate block around lines 81-87)
before any file I/O and return its error if non-nil so absolute/traversal paths
are rejected early; keep the parsePresentationRef check as-is and perform the
SafeInputPath check first (or alongside) using the same runtime.Str("file")
input.
🧹 Nitpick comments (1)
shortcuts/slides/slides_media_upload_test.go (1)

329-332: Handle non-EOF read errors in multipart test helpers.

Current helpers break on any error, which can hide malformed multipart bodies and produce partial parses.

Suggested fix
 import (
 	"bytes"
 	"encoding/json"
+	"io"
 	"mime"
 	"mime/multipart"
@@
 	for {
 		part, err := reader.NextPart()
 		if err != nil {
-			break
+			if err == io.EOF {
+				break
+			}
+			t.Fatalf("read multipart part: %v", err)
 		}
 		data := readAll(t, part)
@@
 func readAll(t *testing.T, r interface {
 	Read(p []byte) (n int, err error)
 }) []byte {
 	t.Helper()
-	var buf bytes.Buffer
-	tmp := make([]byte, 4096)
-	for {
-		n, err := r.Read(tmp)
-		if n > 0 {
-			buf.Write(tmp[:n])
-		}
-		if err != nil {
-			break
-		}
-	}
-	return buf.Bytes()
+	b, err := io.ReadAll(r)
+	if err != nil {
+		t.Fatalf("read body: %v", err)
+	}
+	return b
 }

Also applies to: 350-356

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@shortcuts/slides/slides_media_upload_test.go` around lines 329 - 332, The
helper currently treats any error from reader.NextPart() as end-of-input and
simply breaks, which hides malformed multipart bodies; change the NextPart()
error handling in the helper used around reader.NextPart() (and the similar
block at the other occurrence) to distinguish io.EOF from other errors: if err
== io.EOF then break, otherwise fail the test (e.g., t.Fatalf or return the
error) and include the error message so tests surface malformed multipart bodies
instead of silently truncating parsing.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Duplicate comments:
In `@shortcuts/slides/helpers.go`:
- Around line 34-38: The code currently treats any substring match of "/slides/"
or "/wiki/" in raw as a valid token (via extractRefToken), which allows matches
in queries/fragments; change the logic to anchor to the URL/raw path prefix:
parse raw with net/url.Parse (or otherwise extract the path component) and then
use a prefix check (e.g., strings.HasPrefix on u.Path) when calling
extractRefToken or replace extractRefToken to accept a path and only return a
token when the path begins with "/slides/" or "/wiki/". Update both the block
creating presentationRef for slides/wiki (the existing ifs using
extractRefToken) and the similar logic around lines 49-56 so they operate on the
URL path, not a substring of the whole raw input.

In `@shortcuts/slides/slides_media_upload.go`:
- Around line 41-46: The Validate function currently parses the presentation ref
but does not validate the user-supplied file path; call validate.SafeInputPath
on runtime.Str("file") inside the same Validate (and the other Validate block
around lines 81-87) before any file I/O and return its error if non-nil so
absolute/traversal paths are rejected early; keep the parsePresentationRef check
as-is and perform the SafeInputPath check first (or alongside) using the same
runtime.Str("file") input.

---

Nitpick comments:
In `@shortcuts/slides/slides_media_upload_test.go`:
- Around line 329-332: The helper currently treats any error from
reader.NextPart() as end-of-input and simply breaks, which hides malformed
multipart bodies; change the NextPart() error handling in the helper used around
reader.NextPart() (and the similar block at the other occurrence) to distinguish
io.EOF from other errors: if err == io.EOF then break, otherwise fail the test
(e.g., t.Fatalf or return the error) and include the error message so tests
surface malformed multipart bodies instead of silently truncating parsing.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 06cf0bcd-6d9e-4740-b846-179eaf9f6e0f

📥 Commits

Reviewing files that changed from the base of the PR and between 7aaaf28 and aa3bbcb.

📒 Files selected for processing (14)
  • shortcuts/slides/helpers.go
  • shortcuts/slides/helpers_test.go
  • shortcuts/slides/shortcuts.go
  • shortcuts/slides/slides_create.go
  • shortcuts/slides/slides_create_test.go
  • shortcuts/slides/slides_media_upload.go
  • shortcuts/slides/slides_media_upload_test.go
  • skills/lark-slides/SKILL.md
  • skills/lark-slides/references/lark-slides-create.md
  • skills/lark-slides/references/lark-slides-media-upload.md
  • skills/lark-slides/references/lark-slides-xml-presentation-slide-create.md
  • skills/lark-slides/references/slide-templates.md
  • skills/lark-slides/references/xml-format-guide.md
  • skills/lark-slides/references/xml-schema-quick-ref.md
✅ Files skipped from review due to trivial changes (4)
  • skills/lark-slides/references/lark-slides-xml-presentation-slide-create.md
  • skills/lark-slides/references/xml-schema-quick-ref.md
  • skills/lark-slides/references/xml-format-guide.md
  • skills/lark-slides/references/lark-slides-media-upload.md
🚧 Files skipped from review as they are similar to previous changes (4)
  • shortcuts/slides/helpers_test.go
  • shortcuts/slides/shortcuts.go
  • shortcuts/slides/slides_create.go
  • shortcuts/slides/slides_create_test.go

@ViperCai ViperCai force-pushed the feat/slides-image-upload branch 2 times, most recently from 5c95ce8 to 4c118ac Compare April 13, 2026 12:11
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: 1

♻️ Duplicate comments (1)
shortcuts/slides/slides_create.go (1)

48-58: ⚠️ Potential issue | 🟠 Major

Validate @path placeholders before touching the filesystem.

Both code paths still Stat XML-supplied placeholder paths before calling validate.SafeInputPath(path). That keeps the new feature out of compliance with the repo’s safe-path rule and duplicates the same gap in validation and execution.

Suggested fix
for _, path := range extractImagePlaceholderPaths(slides) {
+	if err := validate.SafeInputPath(path); err != nil {
+		return output.ErrValidation("unsafe file path: %s", err)
+	}
 	stat, err := runtime.FileIO().Stat(path)
 	if err != nil {
 		return common.WrapInputStatError(err, fmt.Sprintf("--slides @%s: file not found", path))
 	}
for i, path := range paths {
+	if err := validate.SafeInputPath(path); err != nil {
+		return tokens, i, output.ErrValidation("unsafe file path: %s", err)
+	}
 	stat, err := runtime.FileIO().Stat(path)
 	if err != nil {
 		return tokens, i, common.WrapInputStatError(err, fmt.Sprintf("@%s: file not found", path))
 	}

As per coding guidelines, **/*.go: Validate paths using validate.SafeInputPath before any file I/O operations.

Also applies to: 260-280

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@shortcuts/slides/slides_create.go` around lines 48 - 58, The loop that
validates image placeholder paths currently calls runtime.FileIO().Stat(path)
before ensuring the path is allowed; change the order to call
validate.SafeInputPath(path) first (using the same path from
extractImagePlaceholderPaths(slides)) and return an appropriate wrapped error if
SafeInputPath fails, then proceed to runtime.FileIO().Stat(path) and the
existing checks; update the error returns (used in this block with
common.WrapInputStatError and common.FlagErrorf) to reflect failures from
validate.SafeInputPath where applicable so no filesystem access occurs before
SafeInputPath approval.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@shortcuts/slides/slides_create.go`:
- Around line 31-34: SlidesCreate currently unconditionally includes
"docs:document.media:upload" in its Scopes which breaks text-only flows; modify
the constructor or setup where SlidesCreate.Scopes is assigned so you first call
extractImagePlaceholderPaths(slides) (or equivalent) and only append
"docs:document.media:upload" to the Scopes slice when that function returns at
least one placeholder; leave "slides:presentation:create" and
"slides:presentation:write_only" unchanged so preflight still catches other
permission issues.

---

Duplicate comments:
In `@shortcuts/slides/slides_create.go`:
- Around line 48-58: The loop that validates image placeholder paths currently
calls runtime.FileIO().Stat(path) before ensuring the path is allowed; change
the order to call validate.SafeInputPath(path) first (using the same path from
extractImagePlaceholderPaths(slides)) and return an appropriate wrapped error if
SafeInputPath fails, then proceed to runtime.FileIO().Stat(path) and the
existing checks; update the error returns (used in this block with
common.WrapInputStatError and common.FlagErrorf) to reflect failures from
validate.SafeInputPath where applicable so no filesystem access occurs before
SafeInputPath approval.
🪄 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: 7001e2c6-fa18-43cd-8744-1929f5ba0e1b

📥 Commits

Reviewing files that changed from the base of the PR and between aa3bbcb and 4c118ac.

📒 Files selected for processing (14)
  • shortcuts/slides/helpers.go
  • shortcuts/slides/helpers_test.go
  • shortcuts/slides/shortcuts.go
  • shortcuts/slides/slides_create.go
  • shortcuts/slides/slides_create_test.go
  • shortcuts/slides/slides_media_upload.go
  • shortcuts/slides/slides_media_upload_test.go
  • skills/lark-slides/SKILL.md
  • skills/lark-slides/references/lark-slides-create.md
  • skills/lark-slides/references/lark-slides-media-upload.md
  • skills/lark-slides/references/lark-slides-xml-presentation-slide-create.md
  • skills/lark-slides/references/slide-templates.md
  • skills/lark-slides/references/xml-format-guide.md
  • skills/lark-slides/references/xml-schema-quick-ref.md
✅ Files skipped from review due to trivial changes (5)
  • skills/lark-slides/references/lark-slides-xml-presentation-slide-create.md
  • shortcuts/slides/helpers_test.go
  • skills/lark-slides/references/lark-slides-create.md
  • shortcuts/slides/helpers.go
  • skills/lark-slides/references/lark-slides-media-upload.md
🚧 Files skipped from review as they are similar to previous changes (6)
  • shortcuts/slides/shortcuts.go
  • skills/lark-slides/references/xml-schema-quick-ref.md
  • skills/lark-slides/references/xml-format-guide.md
  • shortcuts/slides/slides_create_test.go
  • shortcuts/slides/slides_media_upload.go
  • shortcuts/slides/slides_media_upload_test.go

@ViperCai ViperCai force-pushed the feat/slides-image-upload branch from 4c118ac to 3198b6c Compare April 13, 2026 12:23
Copy link
Copy Markdown
Collaborator

@fangshuyu-768 fangshuyu-768 left a comment

Choose a reason for hiding this comment

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

A few inline follow-ups on scopes and placeholder matching.

@fangshuyu-768 fangshuyu-768 dismissed their stale review April 13, 2026 12:40

Superseded by the newer inline review.

@ViperCai ViperCai force-pushed the feat/slides-image-upload branch from 3198b6c to ee3a336 Compare April 13, 2026 12:52
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.

♻️ Duplicate comments (2)
shortcuts/slides/slides_create.go (1)

53-67: ⚠️ Potential issue | 🟠 Major

Validate @path placeholders with SafeInputPath before filesystem access.

The paths extracted from <img src="@..."> placeholders in user-supplied XML are validated with FileIO().Stat but not with validate.SafeInputPath first. This allows <img src="@../secret.png"> or absolute paths to bypass the CWD-relative restriction documented for this feature.

Suggested fix
+import "github.com/larksuite/cli/internal/validate"
+
 			for _, path := range placeholders {
+				if err := validate.SafeInputPath(path); err != nil {
+					return output.ErrValidation("--slides @%s: unsafe path: %s", path, err)
+				}
 				stat, err := runtime.FileIO().Stat(path)
 				if err != nil {
 					return common.WrapInputStatError(err, fmt.Sprintf("--slides @%s: file not found", path))

As per coding guidelines, **/*.go: Validate paths using validate.SafeInputPath before any file I/O operations.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@shortcuts/slides/slides_create.go` around lines 53 - 67, The extracted
placeholder paths from extractImagePlaceholderPaths must be validated with
validate.SafeInputPath before any filesystem access; update the loop over
placeholders to call validate.SafeInputPath(path) for each path and return a
wrapped error if it fails, then proceed to runtime.FileIO().Stat(path) and the
existing regular-file check; ensure you reference the same variables and
functions (placeholders, path, validate.SafeInputPath, runtime.FileIO().Stat,
common.WrapInputStatError/common.FlagErrorf) so the validation happens prior to
Stat.
shortcuts/slides/slides_media_upload.go (1)

44-55: ⚠️ Potential issue | 🟠 Major

Guard --file with validate.SafeInputPath in validation.

The --file flag is user-controlled, but this shortcut never runs it through validate.SafeInputPath before the Stat/upload in Execute. While the documentation states only CWD-relative paths are accepted, absolute paths and .. traversal can survive to execution without explicit validation.

Suggested fix
+import "github.com/larksuite/cli/internal/validate"
+
 	Validate: func(ctx context.Context, runtime *common.RuntimeContext) error {
 		ref, err := parsePresentationRef(runtime.Str("presentation"))
 		if err != nil {
 			return err
 		}
+		if err := validate.SafeInputPath(runtime.Str("file")); err != nil {
+			return output.ErrValidation("unsafe file path: %s", err)
+		}
 		if ref.Kind == "wiki" {
 			if err := runtime.RequireScopes("wiki:node:read"); err != nil {
 				return err
 			}
 		}
 		return nil
 	},

As per coding guidelines, **/*.go: Validate paths using validate.SafeInputPath before any file I/O operations.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@shortcuts/slides/slides_media_upload.go` around lines 44 - 55, The Validate
block for the shortcut currently parses the presentation ref but does not
validate the user-controlled --file path; update the Validate function to call
validate.SafeInputPath on runtime.Str("file") (and return its error) before
returning nil so any absolute paths or .. traversal are rejected early; ensure
you import/use the validate package and keep this check alongside the existing
parsePresentationRef and wiki scope guard in the same Validate closure.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Duplicate comments:
In `@shortcuts/slides/slides_create.go`:
- Around line 53-67: The extracted placeholder paths from
extractImagePlaceholderPaths must be validated with validate.SafeInputPath
before any filesystem access; update the loop over placeholders to call
validate.SafeInputPath(path) for each path and return a wrapped error if it
fails, then proceed to runtime.FileIO().Stat(path) and the existing regular-file
check; ensure you reference the same variables and functions (placeholders,
path, validate.SafeInputPath, runtime.FileIO().Stat,
common.WrapInputStatError/common.FlagErrorf) so the validation happens prior to
Stat.

In `@shortcuts/slides/slides_media_upload.go`:
- Around line 44-55: The Validate block for the shortcut currently parses the
presentation ref but does not validate the user-controlled --file path; update
the Validate function to call validate.SafeInputPath on runtime.Str("file") (and
return its error) before returning nil so any absolute paths or .. traversal are
rejected early; ensure you import/use the validate package and keep this check
alongside the existing parsePresentationRef and wiki scope guard in the same
Validate closure.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 7086a7b7-945c-47a5-8c39-4f6da63c1f5f

📥 Commits

Reviewing files that changed from the base of the PR and between 4c118ac and ee3a336.

📒 Files selected for processing (15)
  • shortcuts/common/runner.go
  • shortcuts/slides/helpers.go
  • shortcuts/slides/helpers_test.go
  • shortcuts/slides/shortcuts.go
  • shortcuts/slides/slides_create.go
  • shortcuts/slides/slides_create_test.go
  • shortcuts/slides/slides_media_upload.go
  • shortcuts/slides/slides_media_upload_test.go
  • skills/lark-slides/SKILL.md
  • skills/lark-slides/references/lark-slides-create.md
  • skills/lark-slides/references/lark-slides-media-upload.md
  • skills/lark-slides/references/lark-slides-xml-presentation-slide-create.md
  • skills/lark-slides/references/slide-templates.md
  • skills/lark-slides/references/xml-format-guide.md
  • skills/lark-slides/references/xml-schema-quick-ref.md
✅ Files skipped from review due to trivial changes (6)
  • skills/lark-slides/references/xml-format-guide.md
  • skills/lark-slides/references/xml-schema-quick-ref.md
  • skills/lark-slides/references/lark-slides-xml-presentation-slide-create.md
  • skills/lark-slides/references/lark-slides-create.md
  • shortcuts/slides/helpers_test.go
  • skills/lark-slides/references/lark-slides-media-upload.md
🚧 Files skipped from review as they are similar to previous changes (2)
  • shortcuts/slides/shortcuts.go
  • shortcuts/slides/slides_create_test.go

@ViperCai ViperCai force-pushed the feat/slides-image-upload branch from ee3a336 to 001d597 Compare April 14, 2026 03:02
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.

🧹 Nitpick comments (1)
shortcuts/slides/slides_media_upload.go (1)

44-48: Consider validating --file exists in Validate for faster feedback.

Currently, Validate only checks --presentation but not --file. While Execute does validate the file via FileIO().Stat (which internally calls SafeInputPath), moving this check to Validate would fail faster with a clearer error context. This matches the pattern in SlidesCreate.Validate (lines 52-60 in slides_create.go).

However, since FileIO().Stat in Execute already enforces path safety, this is a UX improvement rather than a security issue.

♻️ Optional: Add file validation in Validate
 	Validate: func(ctx context.Context, runtime *common.RuntimeContext) error {
 		if _, err := parsePresentationRef(runtime.Str("presentation")); err != nil {
 			return err
 		}
+		filePath := runtime.Str("file")
+		stat, err := runtime.FileIO().Stat(filePath)
+		if err != nil {
+			return common.WrapInputStatError(err, "file not found")
+		}
+		if !stat.Mode().IsRegular() {
+			return output.ErrValidation("file must be a regular file: %s", filePath)
+		}
 		return nil
 	},
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@shortcuts/slides/slides_media_upload.go` around lines 44 - 48, The current
Validate in slides_media_upload.go only checks presentation; update Validate to
also verify the provided --file exists for faster, clearer feedback: after
parsing presentation via parsePresentationRef(runtime.Str("presentation")), get
the file path with runtime.Str("file") and call runtime.FileIO().Stat (or the
same FileIO().Stat used in Execute) to ensure the path is safe and the file
exists, returning any error encountered; reference the Validate function in
slides_media_upload.go and mirror the approach used in SlidesCreate.Validate to
implement this check.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@shortcuts/slides/slides_media_upload.go`:
- Around line 44-48: The current Validate in slides_media_upload.go only checks
presentation; update Validate to also verify the provided --file exists for
faster, clearer feedback: after parsing presentation via
parsePresentationRef(runtime.Str("presentation")), get the file path with
runtime.Str("file") and call runtime.FileIO().Stat (or the same FileIO().Stat
used in Execute) to ensure the path is safe and the file exists, returning any
error encountered; reference the Validate function in slides_media_upload.go and
mirror the approach used in SlidesCreate.Validate to implement this check.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 5dc948ce-f93e-42ff-bf6f-c75c730c13d3

📥 Commits

Reviewing files that changed from the base of the PR and between ee3a336 and 001d597.

📒 Files selected for processing (14)
  • shortcuts/slides/helpers.go
  • shortcuts/slides/helpers_test.go
  • shortcuts/slides/shortcuts.go
  • shortcuts/slides/slides_create.go
  • shortcuts/slides/slides_create_test.go
  • shortcuts/slides/slides_media_upload.go
  • shortcuts/slides/slides_media_upload_test.go
  • skills/lark-slides/SKILL.md
  • skills/lark-slides/references/lark-slides-create.md
  • skills/lark-slides/references/lark-slides-media-upload.md
  • skills/lark-slides/references/lark-slides-xml-presentation-slide-create.md
  • skills/lark-slides/references/slide-templates.md
  • skills/lark-slides/references/xml-format-guide.md
  • skills/lark-slides/references/xml-schema-quick-ref.md
✅ Files skipped from review due to trivial changes (4)
  • skills/lark-slides/references/lark-slides-xml-presentation-slide-create.md
  • shortcuts/slides/shortcuts.go
  • skills/lark-slides/references/lark-slides-create.md
  • skills/lark-slides/references/lark-slides-media-upload.md
🚧 Files skipped from review as they are similar to previous changes (2)
  • skills/lark-slides/references/xml-schema-quick-ref.md
  • skills/lark-slides/references/xml-format-guide.md

@ViperCai ViperCai force-pushed the feat/slides-image-upload branch from 001d597 to 053b1e4 Compare April 14, 2026 12:23
@github-actions github-actions bot added domain/ccm PR touches the ccm domain domain/im PR touches the im domain domain/task PR touches the task domain size/XL Architecture-level or global-impact change and removed size/L Large or sensitive change across domains or core paths labels Apr 14, 2026
…rs in +create

- New `slides +media-upload` shortcut: upload a local image to a slides
  presentation and return the file_token for use in <img src="...">.
- `slides +create --slides` now supports `@./path.png` placeholders that
  are auto-uploaded and replaced with file_tokens.
- Reject images >20 MB (multipart upload not supported for slide_file).
- Support wiki URL resolution for --presentation flag.
@ViperCai ViperCai force-pushed the feat/slides-image-upload branch from 053b1e4 to a173b29 Compare April 14, 2026 13:29
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

domain/ccm PR touches the ccm domain domain/im PR touches the im domain domain/task PR touches the task domain size/XL Architecture-level or global-impact change

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants