Skip to content

feat(slides):slide screenshot#1358

Open
ethan-zhx wants to merge 1 commit into
mainfrom
feat/slide_image
Open

feat(slides):slide screenshot#1358
ethan-zhx wants to merge 1 commit into
mainfrom
feat/slide_image

Conversation

@ethan-zhx

@ethan-zhx ethan-zhx commented Jun 9, 2026

Copy link
Copy Markdown
Collaborator

Summary

Add a new slides +screenshot shortcut to fetch slide screenshots and save them as local image files instead of printing Base64 payloads to stdout.

What changed

  • Added slides +screenshot for existing slide pages:
    • supports --presentation
    • supports selecting slides by --slide-id or --slide-number
    • resolves wiki URLs to slides presentations when needed
  • Added render mode for raw slide XML:
    • supports --content @file / stdin
    • saves rendered output to local files
  • Writes screenshots into .lark-slides/screenshots by default
  • Suppresses Base64 image data in stdout and returns file metadata instead
  • Avoids overwriting existing files by deduplicating output names
  • Added shortcut docs and screenshot reference material
  • Added int_array flag parsing coverage needed by --slide-number
  • Registered slides:presentation:screenshot scope priority

Tests

  • Added unit tests covering:
    • scope declaration
    • file writing and Base64 suppression
    • slide-number requests
    • duplicate filename handling
    • required selector validation
    • content render flow
    • int_array flag parsing

Summary by CodeRabbit

  • New Features

    • Added slide screenshot shortcut with two modes: render from XML or fetch existing slide images; writes decoded image files locally and suppresses Base64 on stdout
    • Added multi-value integer flag support for selecting multiple slides
  • Documentation

    • New reference and quick‑reference entries describing usage, modes, naming, and overwrite-avoidance behavior
  • Tests

    • Expanded end-to-end tests for screenshot flows, filename rules, validation, dry-run, and flag handling

@github-actions github-actions Bot added the size/L Large or sensitive change across domains or core paths label Jun 9, 2026
@coderabbitai

coderabbitai Bot commented Jun 9, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 10c3a42a-37c7-468f-ac15-6c1156f6c193

📥 Commits

Reviewing files that changed from the base of the PR and between 0dda6f5 and ed5ad8b.

📒 Files selected for processing (9)
  • internal/registry/scope_priorities.json
  • shortcuts/common/runner.go
  • shortcuts/common/runner_args_test.go
  • shortcuts/common/types.go
  • shortcuts/slides/shortcuts.go
  • shortcuts/slides/slides_screenshot.go
  • shortcuts/slides/slides_screenshot_test.go
  • skills/lark-slides/SKILL.md
  • skills/lark-slides/references/lark-slides-screenshot.md
✅ Files skipped from review due to trivial changes (2)
  • skills/lark-slides/references/lark-slides-screenshot.md
  • skills/lark-slides/SKILL.md
🚧 Files skipped from review as they are similar to previous changes (4)
  • shortcuts/slides/shortcuts.go
  • shortcuts/common/runner_args_test.go
  • internal/registry/scope_priorities.json
  • shortcuts/common/runner.go

📝 Walkthrough

Walkthrough

This PR introduces a new slides +screenshot shortcut that fetches server-rendered slide images as Base64, decodes them, and saves them locally. It supports list mode (--presentation + --slide-id/--slide-number) and render mode (--content XML). Adds int_array flag support, scope registration, file-safety/dedup logic, tests, and docs.

Changes

Slides Screenshot Feature

Layer / File(s) Summary
int_array flag type infrastructure
shortcuts/common/types.go, shortcuts/common/runner.go, shortcuts/common/runner_args_test.go
New int_array flag type enables repeated integer arguments (e.g., --slide-number 1 --slide-number 2,3). Includes type doc update, RuntimeContext.IntArray() accessor, cobra flag registration, and tests validating parsing.
Scope registration and shortcut wiring
internal/registry/scope_priorities.json, shortcuts/slides/shortcuts.go
New scope slides:presentation:screenshot added to registry and SlidesScreenshot shortcut registered in the slides shortcuts list.
Shortcut definition, validation, and mode routing
shortcuts/slides/slides_screenshot.go (lines 1–279)
Defines shortcut metadata and flags, implements Validate(), DryRun(), and Execute() with routing between render (--content) and list (--presentation + selectors), plus input normalization and output-dir probing.
API response processing and safe file persistence
shortcuts/slides/slides_screenshot.go (lines 281–490)
Parses responses (list vs render), validates shapes, decodes Base64 image data, maps format codes to file extensions, derives and sanitizes deterministic filename stems, writes files with collision-avoidance (up to 1000 candidates), and produces structured API/filesystem errors with summarized raw payloads.
Comprehensive test coverage
shortcuts/slides/slides_screenshot_test.go, shortcuts/common/runner_args_test.go
Tests declared scopes, list-mode and render-mode behaviors, filename conventions and deduplication, flag validation errors, output-dir safety checks, dry-run messaging, and API error summarization including log_id.
User documentation
skills/lark-slides/SKILL.md, skills/lark-slides/references/lark-slides-screenshot.md
Quick reference entry and full reference guide describing list vs render modes, parameters, output naming/auto-suffix, permission notes, and usage examples.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

  • larksuite/cli#450: Both PRs extend shortcuts/slides/shortcuts.go's slide shortcut registration by adding new shortcuts to the same list.
  • larksuite/cli#389: Both PRs wire new slide shortcuts into the slides command registration in shortcuts/slides/shortcuts.go.

Suggested labels

documentation

Suggested reviewers

  • fangshuyu-768

Poem

🐰 A rabbit hops through slides with glee,
Captures screenshots, one, two, three,
Base64 decoded, files placed neat,
Deduped names so runs repeat,
CLI brightens slides — a photo treat!

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 3.03% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title 'feat(slides):slide screenshot' is directly related to the main change: adding a new slides screenshot shortcut feature.
Description check ✅ Passed The pull request description covers all required template sections with sufficient detail about summary, changes, and tests.
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 docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/slide_image

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.

@github-actions

github-actions Bot commented Jun 9, 2026

Copy link
Copy Markdown

🚀 PR Preview Install Guide

🧰 CLI update

npm i -g https://pkg.pr.new/larksuite/cli/@larksuite/cli@0dda6f5db918da7705cfbbac047cb9c80704d252

🧩 Skill update

npx skills add larksuite/cli#feat/slide_image -y -g

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 9

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

406-422: ⚖️ Poor tradeoff

Avoid hand-building API error envelopes.

Per coding guidelines: "Use runtime.CallAPITyped (shortcuts) or errclass.BuildAPIError (raw responses) for Lark API errors — never hand-build error envelopes". While this handles malformed response data rather than initial API failures, consider using errclass.BuildAPIError or errs.NewInternalError(errs.SubtypeUnknown, ...).WithCause(err) for unclassified errors to maintain consistent error typing.

🤖 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/slides/slides_screenshot.go` around lines 406 - 422, The
slidesScreenshotAPIDataError function is hand-building an API error envelope;
replace this with the project's standardized error constructors (use
errclass.BuildAPIError for raw API-response style errors or
errs.NewInternalError(errs.SubtypeUnknown, "...").WithCause(err) for
unclassified/internal issues) so error typing stays consistent—update
slidesScreenshotAPIDataError to construct and return one of those standardized
error types (referencing slidesScreenshotAPIDataError,
summarizeScreenshotAPIData, and the ErrDetail fields currently used) and include
the same raw_data/log_id details as the cause or metadata on the returned error.

Source: Coding guidelines

🤖 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/slides/slides_screenshot_test.go`:
- Around line 402-418: Update TestSlidesScreenshotRejectsBadOutputDir to assert
typed error metadata using errs.ProblemOf instead of checking error message
substrings: call errs.ProblemOf(err) and verify the returned Problem's Category
and Subtype match the expected validation error and that Problem.Param equals
the flag name (e.g., "output-dir" or "--output-dir") for the runSlidesShortcut
invocation (SlidesScreenshot). Also assert cause preservation if applicable
(e.g., ErrInvalidPath) rather than relying on strings.Contains.
- Around line 342-357: Update TestSlidesScreenshotRenderRejectsListOnlyFlags to
assert typed error metadata using errs.ProblemOf instead of substring matching:
after runSlidesShortcut returns a non-nil err, call p := errs.ProblemOf(err) and
assert p != nil, then check p.Category, p.Subtype (the validation subtype for
presentation/content conflict) and p.Param equals the conflicting flag (e.g.,
"--presentation" or "--content") as appropriate; keep the existing
runSlidesShortcut and SlidesScreenshot references and preserve the existing
fatal checks when the problem metadata is missing or mismatched.
- Around line 238-252: The test TestSlidesScreenshotListRequiresSelector
currently asserts the missing-selector error by checking the error message
substring; update it to use errs.ProblemOf to assert the typed error metadata
instead: call errs.ProblemOf(err) (or equivalent helper) and assert the expected
Problem.Category, Problem.Subtype and Param (e.g., the selector param name) and
that the original cause is preserved for the error returned from
runSlidesShortcut when invoking SlidesScreenshot; replace the strings.Contains
check with these typed assertions while keeping the same runSlidesShortcut
invocation.
- Around line 325-340: In TestSlidesScreenshotRenderRejectsSlideSelectors
replace the substring-based assertion with a typed assertion using
errs.ProblemOf: call errs.ProblemOf(err, errs.Validation) (or the codebase's
Validation identifier) to get the problem, assert it's non-nil, then check the
problem's Subtype/Kind equals the validation subtype for conflicting arguments
(e.g. "conflicting-args" or the repo's specific subtype) and that the
problem.Param or Params include the "--content" parameter (and/or the slide
selector param like "--slide-id"/"--slide-number"); keep the existing error
generation by runSlidesShortcut and SlidesScreenshot but verify via
errs.ProblemOf rather than strings.Contains.
- Around line 420-470: The test must assert typed error metadata via
errs.ProblemOf for the underlying error; after obtaining exitErr, call
errs.ProblemOf on the underlying cause (e.g. errors.Unwrap(exitErr) or
exitErr.Cause) and assert the returned problem is non-nil and its Category and
Subtype match the expected values for the "no images" error (add these
assertions into TestSlidesScreenshotNoImagesErrorIncludesRawDataAndLogID near
where exitErr is inspected).

In `@shortcuts/slides/slides_screenshot.go`:
- Line 474: Replace the output.Errorf(...) calls used for local file I/O
failures in slides_screenshot.go (inside the function handling screenshots where
validate.SafeInputPath / SafeOutputPath is already used) with typed errors using
errs.NewInternalError(errs.SubtypeFileIO, "<context message>") and attach the
original error via .WithCause(err); update each occurrence (the shown return at
write screenshot, and the other instances at the lines noted) to return
errs.NewInternalError(errs.SubtypeFileIO, fmt.Sprintf("write screenshot %s: %v",
path, err)).WithCause(err) (or similar context-specific messages), and add the
import "github.com/larksuite/cli/pkg/errs" if missing.
- Line 285: The return uses output.Errorf for a local file I/O failure; replace
it with the typed error constructor errs.NewInternalError(errs.SubtypeFileIO,
...) (include the original error and a descriptive message like "create output
directory %s") and add the import "github.com/larksuite/cli/pkg/errs" if
missing; update the return in the function that creates the output directory
(the line returning output.Errorf(...)) to return the errs.NewInternalError(...)
instance instead.
- Line 19: Remove the import of github.com/larksuite/cli/internal/vfs and stop
calling vfs.MkdirAll and vfs.OpenFile; instead use runtime.FileIO() (the
fileio.FileIO instance) and its Save and Stat methods: replace any vfs.MkdirAll
usage with relying on fileIO.Save (which creates parent dirs) and replace
vfs.OpenFile(..., O_EXCL) patterns by implementing a no-overwrite flow that
checks file existence via fileIO.Stat and picks a unique filepath (e.g., append
a suffix or increment a counter) in a loop before calling fileIO.Save; add a
small helper (e.g., makeUniquePath or ensureUniqueSave) near the existing save
logic to encapsulate the Stat+unique-name loop, and remove all references to
vfs.OpenFile/vfs.MkdirAll in the functions that currently use them.

In `@skills/lark-slides/references/lark-slides-screenshot.md`:
- Around line 96-99: Replace the contradictory guidance that reads “不知道页面 ID,传
slide id 即可” with a clear instruction that if slide_id is unknown the user
should pass the slide index using the --slide-number option; update the text
mentioning slide_id to instead instruct using --slide-number (and optionally
give the expected numeric format) so the docs consistently direct users to use
--slide-number when slide_id is unavailable.

---

Nitpick comments:
In `@shortcuts/slides/slides_screenshot.go`:
- Around line 406-422: The slidesScreenshotAPIDataError function is
hand-building an API error envelope; replace this with the project's
standardized error constructors (use errclass.BuildAPIError for raw API-response
style errors or errs.NewInternalError(errs.SubtypeUnknown, "...").WithCause(err)
for unclassified/internal issues) so error typing stays consistent—update
slidesScreenshotAPIDataError to construct and return one of those standardized
error types (referencing slidesScreenshotAPIDataError,
summarizeScreenshotAPIData, and the ErrDetail fields currently used) and include
the same raw_data/log_id details as the cause or metadata on the returned error.
🪄 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: 3ec276fc-8760-4124-b78b-a897eb3abdc0

📥 Commits

Reviewing files that changed from the base of the PR and between eed711b and 0dda6f5.

📒 Files selected for processing (9)
  • internal/registry/scope_priorities.json
  • shortcuts/common/runner.go
  • shortcuts/common/runner_args_test.go
  • shortcuts/common/types.go
  • shortcuts/slides/shortcuts.go
  • shortcuts/slides/slides_screenshot.go
  • shortcuts/slides/slides_screenshot_test.go
  • skills/lark-slides/SKILL.md
  • skills/lark-slides/references/lark-slides-screenshot.md

Comment on lines +238 to +252
func TestSlidesScreenshotListRequiresSelector(t *testing.T) {
f, stdout, _, _ := cmdutil.TestFactory(t, slidesTestConfig(t, ""))

err := runSlidesShortcut(t, f, stdout, SlidesScreenshot, []string{
"+screenshot",
"--presentation", "pres_abc",
"--as", "user",
})
if err == nil {
t.Fatal("expected error")
}
if !strings.Contains(err.Error(), "--slide-id or --slide-number is required") {
t.Fatalf("error = %v, want missing selector error", err)
}
}

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 | 🏗️ Heavy lift

Assert typed error metadata via errs.ProblemOf, not just message substrings.

This validation error test violates the coding guideline for **/*_test.go: error-path tests must assert typed metadata (category/subtype/param) via errs.ProblemOf, not message substrings alone. Checking only the message reduces confidence that the error type, subtype, and parameter metadata are correctly populated.

✅ Recommended pattern using errs.ProblemOf
 	if err == nil {
 		t.Fatal("expected error")
 	}
-	if !strings.Contains(err.Error(), "--slide-id or --slide-number is required") {
-		t.Fatalf("error = %v, want missing selector error", err)
+	problem, ok := errs.ProblemOf(err)
+	if !ok {
+		t.Fatalf("error type = %T, want typed errs.Problem", err)
+	}
+	if problem.Category != errs.CategoryValidation {
+		t.Fatalf("category = %v, want Validation", problem.Category)
+	}
+	if problem.Subtype != errs.SubtypeInvalidArgument {
+		t.Fatalf("subtype = %v, want InvalidArgument", problem.Subtype)
 	}

As per coding guidelines: "Error-path tests must assert typed metadata via errs.ProblemOf (category/subtype/param) and cause preservation, not message substrings alone".

📝 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 TestSlidesScreenshotListRequiresSelector(t *testing.T) {
f, stdout, _, _ := cmdutil.TestFactory(t, slidesTestConfig(t, ""))
err := runSlidesShortcut(t, f, stdout, SlidesScreenshot, []string{
"+screenshot",
"--presentation", "pres_abc",
"--as", "user",
})
if err == nil {
t.Fatal("expected error")
}
if !strings.Contains(err.Error(), "--slide-id or --slide-number is required") {
t.Fatalf("error = %v, want missing selector error", err)
}
}
func TestSlidesScreenshotListRequiresSelector(t *testing.T) {
f, stdout, _, _ := cmdutil.TestFactory(t, slidesTestConfig(t, ""))
err := runSlidesShortcut(t, f, stdout, SlidesScreenshot, []string{
"+screenshot",
"--presentation", "pres_abc",
"--as", "user",
})
if err == nil {
t.Fatal("expected error")
}
problem, ok := errs.ProblemOf(err)
if !ok {
t.Fatalf("error type = %T, want typed errs.Problem", err)
}
if problem.Category != errs.CategoryValidation {
t.Fatalf("category = %v, want Validation", problem.Category)
}
if problem.Subtype != errs.SubtypeInvalidArgument {
t.Fatalf("subtype = %v, want InvalidArgument", problem.Subtype)
}
}
🤖 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/slides/slides_screenshot_test.go` around lines 238 - 252, The test
TestSlidesScreenshotListRequiresSelector currently asserts the missing-selector
error by checking the error message substring; update it to use errs.ProblemOf
to assert the typed error metadata instead: call errs.ProblemOf(err) (or
equivalent helper) and assert the expected Problem.Category, Problem.Subtype and
Param (e.g., the selector param name) and that the original cause is preserved
for the error returned from runSlidesShortcut when invoking SlidesScreenshot;
replace the strings.Contains check with these typed assertions while keeping the
same runSlidesShortcut invocation.

Source: Coding guidelines

Comment on lines +325 to +340
func TestSlidesScreenshotRenderRejectsSlideSelectors(t *testing.T) {
f, stdout, _, _ := cmdutil.TestFactory(t, slidesTestConfig(t, ""))

err := runSlidesShortcut(t, f, stdout, SlidesScreenshot, []string{
"+screenshot",
"--content", `<slide xmlns="http://www.larkoffice.com/sml/2.0"><data></data></slide>`,
"--slide-id", "slide_1",
"--as", "user",
})
if err == nil {
t.Fatal("expected error")
}
if !strings.Contains(err.Error(), "--content cannot be used with --slide-id or --slide-number") {
t.Fatalf("error = %v, want content/slide selector conflict", err)
}
}

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 | 🏗️ Heavy lift

Assert typed error metadata via errs.ProblemOf, not just message substrings.

This validation error test violates the coding guideline: error-path tests must assert typed metadata (category/subtype/param) via errs.ProblemOf. The test should verify that the error is a ValidationError with the appropriate subtype and parameter metadata, not just check the message string.

✅ Recommended pattern
 	if err == nil {
 		t.Fatal("expected error")
 	}
-	if !strings.Contains(err.Error(), "--content cannot be used with --slide-id or --slide-number") {
-		t.Fatalf("error = %v, want content/slide selector conflict", err)
+	problem, ok := errs.ProblemOf(err)
+	if !ok {
+		t.Fatalf("error type = %T, want typed errs.Problem", err)
+	}
+	if problem.Category != errs.CategoryValidation {
+		t.Fatalf("category = %v, want Validation", problem.Category)
+	}
+	if problem.Subtype != errs.SubtypeInvalidArgument {
+		t.Fatalf("subtype = %v, want InvalidArgument", problem.Subtype)
 	}

As per coding guidelines: "Error-path tests must assert typed metadata via errs.ProblemOf (category/subtype/param) and cause preservation, not message substrings alone".

📝 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 TestSlidesScreenshotRenderRejectsSlideSelectors(t *testing.T) {
f, stdout, _, _ := cmdutil.TestFactory(t, slidesTestConfig(t, ""))
err := runSlidesShortcut(t, f, stdout, SlidesScreenshot, []string{
"+screenshot",
"--content", `<slide xmlns="http://www.larkoffice.com/sml/2.0"><data></data></slide>`,
"--slide-id", "slide_1",
"--as", "user",
})
if err == nil {
t.Fatal("expected error")
}
if !strings.Contains(err.Error(), "--content cannot be used with --slide-id or --slide-number") {
t.Fatalf("error = %v, want content/slide selector conflict", err)
}
}
func TestSlidesScreenshotRenderRejectsSlideSelectors(t *testing.T) {
f, stdout, _, _ := cmdutil.TestFactory(t, slidesTestConfig(t, ""))
err := runSlidesShortcut(t, f, stdout, SlidesScreenshot, []string{
"+screenshot",
"--content", `<slide xmlns="http://www.larkoffice.com/sml/2.0"><data></data></slide>`,
"--slide-id", "slide_1",
"--as", "user",
})
if err == nil {
t.Fatal("expected error")
}
problem, ok := errs.ProblemOf(err)
if !ok {
t.Fatalf("error type = %T, want typed errs.Problem", err)
}
if problem.Category != errs.CategoryValidation {
t.Fatalf("category = %v, want Validation", problem.Category)
}
if problem.Subtype != errs.SubtypeInvalidArgument {
t.Fatalf("subtype = %v, want InvalidArgument", problem.Subtype)
}
}
🤖 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/slides/slides_screenshot_test.go` around lines 325 - 340, In
TestSlidesScreenshotRenderRejectsSlideSelectors replace the substring-based
assertion with a typed assertion using errs.ProblemOf: call errs.ProblemOf(err,
errs.Validation) (or the codebase's Validation identifier) to get the problem,
assert it's non-nil, then check the problem's Subtype/Kind equals the validation
subtype for conflicting arguments (e.g. "conflicting-args" or the repo's
specific subtype) and that the problem.Param or Params include the "--content"
parameter (and/or the slide selector param like "--slide-id"/"--slide-number");
keep the existing error generation by runSlidesShortcut and SlidesScreenshot but
verify via errs.ProblemOf rather than strings.Contains.

Source: Coding guidelines

Comment on lines +342 to +357
func TestSlidesScreenshotRenderRejectsListOnlyFlags(t *testing.T) {
f, stdout, _, _ := cmdutil.TestFactory(t, slidesTestConfig(t, ""))

err := runSlidesShortcut(t, f, stdout, SlidesScreenshot, []string{
"+screenshot",
"--content", `<slide xmlns="http://www.larkoffice.com/sml/2.0"><data></data></slide>`,
"--presentation", "pres_abc",
"--as", "user",
})
if err == nil {
t.Fatal("expected error")
}
if !strings.Contains(err.Error(), "--presentation cannot be used with --content") {
t.Fatalf("error = %v, want presentation/content conflict", err)
}
}

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 | 🏗️ Heavy lift

Assert typed error metadata via errs.ProblemOf, not just message substrings.

This validation error test violates the coding guideline: error-path tests must assert typed metadata via errs.ProblemOf. Verify the error category, subtype, and param instead of only checking the message string.

✅ Recommended pattern
 	if err == nil {
 		t.Fatal("expected error")
 	}
-	if !strings.Contains(err.Error(), "--presentation cannot be used with --content") {
-		t.Fatalf("error = %v, want presentation/content conflict", err)
+	problem, ok := errs.ProblemOf(err)
+	if !ok {
+		t.Fatalf("error type = %T, want typed errs.Problem", err)
+	}
+	if problem.Category != errs.CategoryValidation {
+		t.Fatalf("category = %v, want Validation", problem.Category)
+	}
+	if problem.Subtype != errs.SubtypeInvalidArgument {
+		t.Fatalf("subtype = %v, want InvalidArgument", problem.Subtype)
 	}

As per coding guidelines: "Error-path tests must assert typed metadata via errs.ProblemOf (category/subtype/param) and cause preservation, not message substrings alone".

📝 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 TestSlidesScreenshotRenderRejectsListOnlyFlags(t *testing.T) {
f, stdout, _, _ := cmdutil.TestFactory(t, slidesTestConfig(t, ""))
err := runSlidesShortcut(t, f, stdout, SlidesScreenshot, []string{
"+screenshot",
"--content", `<slide xmlns="http://www.larkoffice.com/sml/2.0"><data></data></slide>`,
"--presentation", "pres_abc",
"--as", "user",
})
if err == nil {
t.Fatal("expected error")
}
if !strings.Contains(err.Error(), "--presentation cannot be used with --content") {
t.Fatalf("error = %v, want presentation/content conflict", err)
}
}
func TestSlidesScreenshotRenderRejectsListOnlyFlags(t *testing.T) {
f, stdout, _, _ := cmdutil.TestFactory(t, slidesTestConfig(t, ""))
err := runSlidesShortcut(t, f, stdout, SlidesScreenshot, []string{
"+screenshot",
"--content", `<slide xmlns="http://www.larkoffice.com/sml/2.0"><data></data></slide>`,
"--presentation", "pres_abc",
"--as", "user",
})
if err == nil {
t.Fatal("expected error")
}
problem, ok := errs.ProblemOf(err)
if !ok {
t.Fatalf("error type = %T, want typed errs.Problem", err)
}
if problem.Category != errs.CategoryValidation {
t.Fatalf("category = %v, want Validation", problem.Category)
}
if problem.Subtype != errs.SubtypeInvalidArgument {
t.Fatalf("subtype = %v, want InvalidArgument", problem.Subtype)
}
}
🤖 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/slides/slides_screenshot_test.go` around lines 342 - 357, Update
TestSlidesScreenshotRenderRejectsListOnlyFlags to assert typed error metadata
using errs.ProblemOf instead of substring matching: after runSlidesShortcut
returns a non-nil err, call p := errs.ProblemOf(err) and assert p != nil, then
check p.Category, p.Subtype (the validation subtype for presentation/content
conflict) and p.Param equals the conflicting flag (e.g., "--presentation" or
"--content") as appropriate; keep the existing runSlidesShortcut and
SlidesScreenshot references and preserve the existing fatal checks when the
problem metadata is missing or mismatched.

Source: Coding guidelines

Comment on lines +402 to +418
func TestSlidesScreenshotRejectsBadOutputDir(t *testing.T) {
f, stdout, _, _ := cmdutil.TestFactory(t, slidesTestConfig(t, ""))

err := runSlidesShortcut(t, f, stdout, SlidesScreenshot, []string{
"+screenshot",
"--presentation", "pres_abc",
"--slide-id", "slide_1",
"--output-dir", "../outside",
"--as", "user",
})
if err == nil {
t.Fatal("expected error for unsafe output dir")
}
if !strings.Contains(err.Error(), "--output-dir invalid") {
t.Fatalf("error = %v, want output-dir validation", err)
}
}

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 | 🏗️ Heavy lift

Assert typed error metadata via errs.ProblemOf, not just message substrings.

This validation error test violates the coding guideline: error-path tests must assert typed metadata via errs.ProblemOf. For this --output-dir validation failure, the test should verify the error category, subtype, and especially the param field to confirm it identifies the problematic flag.

✅ Recommended pattern
 	if err == nil {
 		t.Fatal("expected error for unsafe output dir")
 	}
-	if !strings.Contains(err.Error(), "--output-dir invalid") {
-		t.Fatalf("error = %v, want output-dir validation", err)
+	problem, ok := errs.ProblemOf(err)
+	if !ok {
+		t.Fatalf("error type = %T, want typed errs.Problem", err)
+	}
+	if problem.Category != errs.CategoryValidation {
+		t.Fatalf("category = %v, want Validation", problem.Category)
+	}
+	if problem.Subtype != errs.SubtypeInvalidArgument {
+		t.Fatalf("subtype = %v, want InvalidArgument", problem.Subtype)
+	}
+	if problem.Param != "--output-dir" {
+		t.Fatalf("param = %v, want --output-dir", problem.Param)
 	}

As per coding guidelines: "Error-path tests must assert typed metadata via errs.ProblemOf (category/subtype/param) and cause preservation, not message substrings alone".

🤖 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/slides/slides_screenshot_test.go` around lines 402 - 418, Update
TestSlidesScreenshotRejectsBadOutputDir to assert typed error metadata using
errs.ProblemOf instead of checking error message substrings: call
errs.ProblemOf(err) and verify the returned Problem's Category and Subtype match
the expected validation error and that Problem.Param equals the flag name (e.g.,
"output-dir" or "--output-dir") for the runSlidesShortcut invocation
(SlidesScreenshot). Also assert cause preservation if applicable (e.g.,
ErrInvalidPath) rather than relying on strings.Contains.

Source: Coding guidelines

Comment on lines +420 to +470
func TestSlidesScreenshotNoImagesErrorIncludesRawDataAndLogID(t *testing.T) {
dir := t.TempDir()
withSlidesTestWorkingDir(t, dir)

f, stdout, _, reg := cmdutil.TestFactory(t, slidesTestConfig(t, ""))
reg.Register(&httpmock.Stub{
Method: "POST",
URL: "/open-apis/slides_ai/v1/xml_presentations/pres_abc/slide_images",
Headers: map[string][]string{
"Content-Type": []string{"application/json"},
"X-Tt-Logid": []string{"log-123"},
},
Body: map[string]interface{}{
"code": 0,
"data": map[string]interface{}{
"unexpected": "shape",
},
},
})

err := runSlidesShortcut(t, f, stdout, SlidesScreenshot, []string{
"+screenshot",
"--presentation", "pres_abc",
"--slide-id", "pJJ",
"--as", "user",
})
if err == nil {
t.Fatal("expected error")
}
var exitErr *output.ExitError
if !errors.As(err, &exitErr) {
t.Fatalf("error type = %T, want ExitError", err)
}
if exitErr.Detail == nil || exitErr.Detail.Detail == nil {
t.Fatalf("missing error detail: %+v", exitErr)
}
detail, ok := exitErr.Detail.Detail.(map[string]interface{})
if !ok {
t.Fatalf("detail = %#v, want map", exitErr.Detail.Detail)
}
if detail["log_id"] != "log-123" {
t.Fatalf("log_id = %v, want log-123", detail["log_id"])
}
raw, ok := detail["raw_data"].(map[string]interface{})
if !ok {
t.Fatalf("raw_data = %#v, want map", detail["raw_data"])
}
if raw["unexpected"] != "shape" {
t.Fatalf("raw_data.unexpected = %v, want shape", raw["unexpected"])
}
}

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 | 🏗️ Heavy lift

Also assert typed error metadata via errs.ProblemOf for the underlying error.

While this test correctly verifies the ExitError structure and detail fields (log_id, raw_data), it should also assert the underlying typed error metadata to ensure proper error classification. Per coding guidelines, error-path tests must verify typed metadata (category/subtype) via errs.ProblemOf.

✅ Add typed error assertions

After line 452, add:

 	if !errors.As(err, &exitErr) {
 		t.Fatalf("error type = %T, want ExitError", err)
 	}
+	
+	// Verify the underlying typed error
+	problem, ok := errs.ProblemOf(err)
+	if !ok {
+		t.Fatalf("underlying error is not typed, want errs.Problem")
+	}
+	// Verify appropriate category/subtype for "no images in response" scenario
+	if problem.Category != errs.CategoryInternal {
+		t.Fatalf("category = %v, want Internal for unexpected API shape", problem.Category)
+	}
+	
 	if exitErr.Detail == nil || exitErr.Detail.Detail == nil {
 		t.Fatalf("missing error detail: %+v", exitErr)
 	}

As per coding guidelines: "Error-path tests must assert typed metadata via errs.ProblemOf (category/subtype/param) and cause preservation, not message substrings alone".

🤖 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/slides/slides_screenshot_test.go` around lines 420 - 470, The test
must assert typed error metadata via errs.ProblemOf for the underlying error;
after obtaining exitErr, call errs.ProblemOf on the underlying cause (e.g.
errors.Unwrap(exitErr) or exitErr.Cause) and assert the returned problem is
non-nil and its Category and Subtype match the expected values for the "no
images" error (add these assertions into
TestSlidesScreenshotNoImagesErrorIncludesRawDataAndLogID near where exitErr is
inspected).

Source: Coding guidelines

Comment thread shortcuts/slides/slides_screenshot.go Outdated
Comment thread shortcuts/slides/slides_screenshot.go Outdated
return "", err
}
if err := vfs.MkdirAll(safeOutputDir, 0o755); err != nil {
return "", output.Errorf(output.ExitAPI, "io_error", "create output directory %s: %v", outputDir, err)

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

Use typed error for file I/O failures.

Per coding guidelines: "Use errs.NewInternalError(errs.SubtypeFileIO, ...) for local file I/O failures after path validation with validate.SafeInputPath or SafeOutputPath". Replace output.Errorf with the typed error constructor.

🔧 Suggested fix
 	if err := vfs.MkdirAll(safeOutputDir, 0o755); err != nil {
-		return "", output.Errorf(output.ExitAPI, "io_error", "create output directory %s: %v", outputDir, err)
+		return "", errs.NewInternalError(errs.SubtypeFileIO, "create output directory %s", outputDir).WithCause(err)
 	}

Note: Add "github.com/larksuite/cli/pkg/errs" to imports if not already present.

📝 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
return "", output.Errorf(output.ExitAPI, "io_error", "create output directory %s: %v", outputDir, err)
if err := vfs.MkdirAll(safeOutputDir, 0o755); err != nil {
return "", errs.NewInternalError(errs.SubtypeFileIO, "create output directory %s", outputDir).WithCause(err)
}
🤖 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/slides/slides_screenshot.go` at line 285, The return uses
output.Errorf for a local file I/O failure; replace it with the typed error
constructor errs.NewInternalError(errs.SubtypeFileIO, ...) (include the original
error and a descriptive message like "create output directory %s") and add the
import "github.com/larksuite/cli/pkg/errs" if missing; update the return in the
function that creates the output directory (the line returning
output.Errorf(...)) to return the errs.NewInternalError(...) instance instead.

Source: Coding guidelines

Comment thread shortcuts/slides/slides_screenshot.go Outdated
if os.IsExist(err) {
continue
}
return "", output.Errorf(output.ExitAPI, "io_error", "write screenshot %s: %v", path, err)

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

Use typed errors for file I/O failures.

Per coding guidelines: "Use errs.NewInternalError(errs.SubtypeFileIO, ...) for local file I/O failures after path validation with validate.SafeInputPath or SafeOutputPath". Replace all output.Errorf calls in this function with the typed error constructor and preserve underlying errors with .WithCause(err).

🔧 Suggested fix
 		f, err := vfs.OpenFile(path, os.O_WRONLY|os.O_CREATE|os.O_EXCL, 0o644)
 		if err != nil {
 			if os.IsExist(err) {
 				continue
 			}
-			return "", output.Errorf(output.ExitAPI, "io_error", "write screenshot %s: %v", path, err)
+			return "", errs.NewInternalError(errs.SubtypeFileIO, "write screenshot %s", path).WithCause(err)
 		}
 		n, err := f.Write(imageBytes)
 		if err != nil {
 			_ = f.Close()
-			return "", output.Errorf(output.ExitAPI, "io_error", "write screenshot %s: %v", path, err)
+			return "", errs.NewInternalError(errs.SubtypeFileIO, "write screenshot %s", path).WithCause(err)
 		}
 		if n != len(imageBytes) {
 			_ = f.Close()
-			return "", output.Errorf(output.ExitAPI, "io_error", "write screenshot %s: wrote %d of %d bytes", path, n, len(imageBytes))
+			return "", errs.NewInternalError(errs.SubtypeFileIO, "write screenshot %s: wrote %d of %d bytes", path, n, len(imageBytes))
 		}
 		if err := f.Close(); err != nil {
-			return "", output.Errorf(output.ExitAPI, "io_error", "write screenshot %s: %v", path, err)
+			return "", errs.NewInternalError(errs.SubtypeFileIO, "write screenshot %s", path).WithCause(err)
 		}
 		return path, nil
 	}
 	path := filepath.Join(outputDir, base+"."+ext)
-	return "", output.Errorf(output.ExitAPI, "io_error", "write screenshot %s: too many duplicate file names", path)
+	return "", errs.NewInternalError(errs.SubtypeFileIO, "write screenshot %s: too many duplicate file names", path)
 }

Note: Add "github.com/larksuite/cli/pkg/errs" to imports if not already present.

Also applies to: 479-479, 483-483, 486-486, 491-491

🤖 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/slides/slides_screenshot.go` at line 474, Replace the
output.Errorf(...) calls used for local file I/O failures in
slides_screenshot.go (inside the function handling screenshots where
validate.SafeInputPath / SafeOutputPath is already used) with typed errors using
errs.NewInternalError(errs.SubtypeFileIO, "<context message>") and attach the
original error via .WithCause(err); update each occurrence (the shown return at
write screenshot, and the other instances at the lines noted) to return
errs.NewInternalError(errs.SubtypeFileIO, fmt.Sprintf("write screenshot %s: %v",
path, err)).WithCause(err) (or similar context-specific messages), and add the
import "github.com/larksuite/cli/pkg/errs" if missing.

Source: Coding guidelines

Comment on lines +96 to +99
## 获取 slide_id

不知道页面 ID,传 slide 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

Fix the selector guidance when slide_id is unknown.

Line 98 currently says “不知道页面 ID,传 slide id 即可”, which is contradictory and not actionable. It should instruct using --slide-number when slide_id is unavailable.

Suggested doc patch
 ## 获取 slide_id
 
-不知道页面 ID,传 slide id 即可。
+不知道页面 ID 时,可直接使用 `--slide-number` 选择页面截图;已知 short ID 时再用 `--slide-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 `@skills/lark-slides/references/lark-slides-screenshot.md` around lines 96 -
99, Replace the contradictory guidance that reads “不知道页面 ID,传 slide id 即可” with
a clear instruction that if slide_id is unknown the user should pass the slide
index using the --slide-number option; update the text mentioning slide_id to
instead instruct using --slide-number (and optionally give the expected numeric
format) so the docs consistently direct users to use --slide-number when
slide_id is unavailable.

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

Labels

size/L Large or sensitive change across domains or core paths

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant