Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
62 commits
Select commit Hold shift + click to select a range
e3e5944
feat(sheets): support font_family in cell styles (#1549)
zhengzhijiej-tech Jun 25, 2026
d46e3cc
Merge remote-tracking branch 'origin/main' into feat/lark-sheets-develop
xiongyuanwen-byted Jun 27, 2026
1870348
docs(sheets): inline editing rules into SKILL.md and clarify flag des…
xiongyuanwen-byted Jun 29, 2026
646304a
feat(sheets): add --type bitable to +sheet-create for creating bitabl…
zhengzhijiej-tech Jun 29, 2026
a179900
perf(sheets): cap fan-out cell-matrix materialization to prevent OOM …
zhengzhijiej-tech Jun 29, 2026
9d15b70
feat(pivot): lark-sheets pivot reference 补 +pivot-list info 说明与落点覆盖校验
anunwu-byted Jun 26, 2026
3e3f1bb
feat(sheets): add +formula-verify shortcut for verify_formula tool
wenzhuozhen Jun 29, 2026
4ace5ca
Merge pull request #1626 from larksuite/feat/sheets-formula-verify
wenzhuozhen Jun 29, 2026
5323e8e
Merge pull request #1638 from larksuite/feat/pivot-info
anunwu-byted Jun 29, 2026
69ebac9
feat(sheets): add +history-list / +history-revert / +history-revert-s…
wenzhuozhen Jun 24, 2026
1eb300d
chore(sheets): sync lark_sheet_history skill + flag defs from sheet-s…
wenzhuozhen Jun 24, 2026
d2c010b
fix(sheets): +history-revert-status keys on --transaction-id, not ver…
wenzhuozhen Jun 24, 2026
66d4cf9
fix(sheets): align history flag-defs with inline shortcuts (green Tes…
wenzhuozhen Jun 24, 2026
41101e8
chore(sheets): sync lark-sheets-history reference from spec (BE-2 tra…
wenzhuozhen Jun 29, 2026
983c6e7
feat(sheets): +history-list --end-version for backward pagination
wenzhuozhen Jun 30, 2026
047f067
fix(sheets): make +history-revert --history-version-id cobra-required…
wenzhuozhen Jun 30, 2026
909f78e
fix(sheets): make +history-revert-status --transaction-id cobra-requi…
wenzhuozhen Jun 30, 2026
017d752
docs(sheets): sync history skill reference required badges from spec
wenzhuozhen Jun 30, 2026
aa1a065
Merge pull request #1653 from larksuite/feat/sheet-history-revert
wenzhuozhen Jun 30, 2026
906826d
fix(sheets): lower cells-set --max-cells default to 50000
xiongyuanwen-byted Jul 1, 2026
082625d
docs(sheets): clarify workbook-import over read-then-recreate in skill
xiongyuanwen-byted Jul 1, 2026
4318f57
docs(sheets): bump lark-sheets skill version to 3.0.1
xiongyuanwen-byted Jul 1, 2026
77dda3d
docs(sheets): clarify number-vs-text typing and copy-to-range templat…
xiongyuanwen-byted Jul 1, 2026
f41e6c4
docs(sheets): type by data nature, add pre-write reference column and…
xiongyuanwen-byted Jul 2, 2026
7cca3f3
docs(sheets): tighten number-vs-text guidance and dedupe write-cells …
xiongyuanwen-byted Jul 2, 2026
6857876
Feat/lark sheets develop wzz (#1719)
zhengzhijiej-tech Jul 2, 2026
df1e7c0
docs(sheets): dedupe +changeset-get flag def and skill reference entry
xiongyuanwen-byted Jul 3, 2026
fa7e4ff
feat(sheets): accept local_office_ token prefix for image parent_type
xiongyuanwen-byted Jul 3, 2026
c378dbf
fix(sheets): replace undefined common.FlagErrorf with sheetsValidatio…
xiongyuanwen-byted Jul 3, 2026
f17b5b1
Merge remote-tracking branch 'origin/main' into feat/lark-sheets-develop
xiongyuanwen-byted Jul 3, 2026
a814c8c
fix(sheets): satisfy errs-no-bare-wrap forbidigo and errorlint rules …
xiongyuanwen-byted Jul 3, 2026
f2f0fe2
docs: tighten formula verify workflow guidance
wenzhuozhen Jul 3, 2026
b70ca02
docs: align formula verify refs with file names
wenzhuozhen Jul 3, 2026
3955bbe
feat(sheets): let typed writes style blank cells past the data extent
xiongyuanwen-byted Jul 3, 2026
4d5a05d
docs(sheets): warn that +csv-put silently coerces numeric-looking labels
xiongyuanwen-byted Jul 4, 2026
97ca586
docs(sheets): steer import-vs-append onto sheet-copy for existing wor…
xiongyuanwen-byted Jul 5, 2026
4d79542
docs(sheets): warn that cells-clear --scope all is irreversibly destr…
xiongyuanwen-byted Jul 5, 2026
9a2a437
Merge pull request #1734 from larksuite/feat/sheet-skill-optimize
wenzhuozhen Jul 6, 2026
a0e5671
docs(sheets): sync chart schema and labels guidance (#1716)
zhengzhijiej-tech Jul 6, 2026
0fbdb83
feat(sheets): drop pre-refactor aliases from `sheets --help` listing
xiongyuanwen-byted Jul 6, 2026
1673841
feat(sheets): let +csv-put fall back to piped stdin when --csv is omi…
xiongyuanwen-byted Jul 7, 2026
c9705ad
feat(sheets): rework +rows-resize / +cols-resize to --height / --width
xiongyuanwen-byted Jul 7, 2026
b53d2b5
docs(sheets): 更新 lark-sheets skill 版本至 3.0.2
xiongyuanwen-byted Jul 8, 2026
33e6683
feat(sheets): add --widths / --heights map form for per-column/row sizes
xiongyuanwen-byted Jul 8, 2026
8d2c6c8
fix(sheets): sync flag input/enum fixes from sheet-skill-spec
xiongyuanwen-byted Jul 8, 2026
7ee6705
feat(sheets): add sheets-scoped flag ergonomics via PostMount
xiongyuanwen-byted Jul 9, 2026
c8abdee
feat(sheets): make validation errors prescriptive for hot failure modes
xiongyuanwen-byted Jul 9, 2026
4b2fec1
fix(common): steer rejected @file paths to stdin instead of cd
xiongyuanwen-byted Jul 9, 2026
4dddb0b
chore(sheets): suppress forbidigo on csv-put stdin pipe detection
xiongyuanwen-byted Jul 9, 2026
f057a0e
fix(sheets): pass spreadsheet token to changeset tool (#1839)
zhengzhijiej-tech Jul 9, 2026
97ba2aa
fix(sheets): hide bitable sheet creation (#1843)
zhengzhijiej-tech Jul 10, 2026
d246f72
fix(sheets): resolve revision wiki URLs
zhengzhijiej-tech Jul 10, 2026
a9de6c6
fix(sheets): reject overlapping resize ranges
zhengzhijiej-tech Jul 10, 2026
d8cffa9
fix(sheets): address remaining review feedback
zhengzhijiej-tech Jul 10, 2026
cc5c738
fix(sheets): avoid credential scanner false positive
zhengzhijiej-tech Jul 10, 2026
8708ae4
fix(sheets): import mislabeled .xls workbooks by sniffing content
xiongyuanwen-byted Jul 11, 2026
4d0bdaa
fix(ci): keep semantic waiver fixture active
zhengzhijiej-tech Jul 13, 2026
fb7292c
fix(sheets): close remaining safety gaps
zhengzhijiej-tech Jul 13, 2026
1721a90
fix(sheets): align history shortcuts with generated flags
wenzhuozhen Jul 13, 2026
31a8b47
Merge remote-tracking branch 'origin/main' into feat/lark-sheets-develop
zhengzhijiej-tech Jul 13, 2026
6f41dbd
fix(sheets): require confirmation for history revert
zhengzhijiej-tech Jul 13, 2026
dc5075b
fix(sheets): require explicit csv input
zhengzhijiej-tech Jul 13, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion internal/vfs/localfileio/path.go
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ func safePath(raw, flagName string) (string, error) {
}

if isAbsolutePath(raw) {
return "", fmt.Errorf("%s must be a relative path within the current directory, got %q (hint: cd to the target directory first, or use a relative path like ./filename)", flagName, raw)
return "", fmt.Errorf("%s must be a relative path within the current directory, got %q (hint: use a relative path like ./filename; flags that support stdin can read an out-of-tree file via '-' instead)", flagName, raw)
}

path := filepath.Clean(raw)
Expand Down
11 changes: 9 additions & 2 deletions shortcuts/common/runner.go
Original file line number Diff line number Diff line change
Expand Up @@ -1070,7 +1070,7 @@ func resolveInputFlags(rctx *RuntimeContext, flags []Flag) error {
if rctx.stdinConsumed {
return ValidationErrorf("--%s: stdin (-) can only be used by one flag", fl.Name).
WithParam("--"+fl.Name).
WithHint("a process has a single stdin, so only one flag per call may use '-'; pass the others as @file (e.g. --%s @/path/to/file)", fl.Name)
WithHint("a process has a single stdin, so only one flag per call may use '-'; pass the others inline or as @file with a relative path under the current directory (e.g. --%s @./payload.json)", fl.Name)
}
rctx.stdinConsumed = true
data, err := io.ReadAll(rctx.IO().In)
Expand Down Expand Up @@ -1104,9 +1104,16 @@ func resolveInputFlags(rctx *RuntimeContext, flags []Flag) error {
}
data, err := cmdutil.ReadInputFile(rctx.FileIO(), path)
if err != nil {
return ValidationErrorf("--%s: %v", fl.Name, err).
verr := ValidationErrorf("--%s: %v", fl.Name, err).
WithParam("--" + fl.Name).
WithCause(err)
if slices.Contains(fl.Input, Stdin) {
// Rejected @file paths are usually absolute (temp files under
// /tmp). Steer toward stdin rather than cd / copying the file
// into the project tree.
verr = verr.WithHint("this flag also reads stdin: pipe the file contents into this command and pass --%s -", fl.Name)
}
return verr
}
// strip a leading UTF-8 BOM so it
// can't corrupt the first CSV cell or break JSON parsing downstream.
Expand Down
29 changes: 29 additions & 0 deletions shortcuts/common/runner_input_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -227,6 +227,35 @@ func TestResolveInputFlags_DuplicateStdin(t *testing.T) {
}
}

// TestResolveInputFlags_FileErrorSuggestsStdin pins the recovery hint when
// an @file path is rejected (typically an absolute /tmp path): flags that
// also accept stdin must explain the portable `--flag -` form — never cd'ing
// into the target directory or copying the file into the project tree.
func TestResolveInputFlags_FileErrorSuggestsStdin(t *testing.T) {
rctx := newTestRuntimeWithStdin(map[string]string{"csv": "@/tmp/does-not-exist.csv"}, "")
flags := []Flag{{Name: "csv", Input: []string{File, Stdin}}}

err := resolveInputFlags(rctx, flags)
if err == nil {
t.Fatal("expected error for rejected @file path")
}
vErr := assertValidationParam(t, err, "--csv")
if !strings.Contains(vErr.Hint, "pipe the file contents") || !strings.Contains(vErr.Hint, "--csv -") {
t.Errorf("hint %q should explain the portable stdin form", vErr.Hint)
}

// A flag without stdin support must not get the stdin hint.
rctx = newTestRuntimeWithStdin(map[string]string{"file": "@/tmp/does-not-exist.xlsx"}, "")
err = resolveInputFlags(rctx, []Flag{{Name: "file", Input: []string{File}}})
if err == nil {
t.Fatal("expected error for rejected @file path")
}
vErr = assertValidationParam(t, err, "--file")
if strings.Contains(vErr.Hint, "stdin") {
t.Errorf("hint %q must not suggest stdin for a file-only flag", vErr.Hint)
}
}

func TestStripUTF8BOM(t *testing.T) {
cases := []struct{ name, in, want string }{
{"leading BOM removed", "\uFEFFhello", "hello"},
Expand Down
22 changes: 14 additions & 8 deletions shortcuts/drive/drive_import.go
Original file line number Diff line number Diff line change
Expand Up @@ -54,15 +54,21 @@ type ImportParams struct {
FolderToken string
Name string
TargetToken string
// FileExtension optionally overrides the extension inferred from File's
// name. Leave empty to infer from File (the default). Callers that have
// sniffed the file's real container use this to correct a mislabeled name
// so the backend receives the true format.
FileExtension string
}

func (p ImportParams) spec() driveImportSpec {
return driveImportSpec{
FilePath: p.File,
DocType: strings.ToLower(p.DocType),
FolderToken: p.FolderToken,
Name: p.Name,
TargetToken: p.TargetToken,
FilePath: p.File,
DocType: strings.ToLower(p.DocType),
FolderToken: p.FolderToken,
Name: p.Name,
TargetToken: p.TargetToken,
EffectiveExt: strings.TrimPrefix(strings.ToLower(p.FileExtension), "."),
}
}

Expand Down Expand Up @@ -127,7 +133,7 @@ func RunImport(ctx context.Context, runtime *common.RuntimeContext, p ImportPara
}

// Step 1: Upload file as media
fileToken, uploadErr := uploadMediaForImport(ctx, runtime, spec.FilePath, spec.SourceFileName(), spec.DocType)
fileToken, uploadErr := uploadMediaForImport(ctx, runtime, spec)
if uploadErr != nil {
return uploadErr
}
Expand Down Expand Up @@ -203,14 +209,14 @@ func preflightDriveImportFile(fio fileio.FileIO, spec *driveImportSpec) (int64,
if !info.Mode().IsRegular() {
return 0, errs.NewValidationError(errs.SubtypeInvalidArgument, "file must be a regular file: %s", spec.FilePath).WithParam("--file")
}
if err = validateDriveImportFileSize(spec.FilePath, spec.DocType, info.Size()); err != nil {
if err = validateDriveImportFileSize(spec.FileExtension(), spec.DocType, info.Size()); err != nil {
return 0, err
}
return info.Size(), nil
}

func appendDriveImportUploadDryRun(dry *common.DryRunAPI, spec driveImportSpec, fileSize int64) {
extra, err := buildImportMediaExtra(spec.FilePath, spec.DocType)
extra, err := buildImportMediaExtra(spec.FileExtension(), spec.DocType)
if err != nil {
extra = fmt.Sprintf(`{"obj_type":"%s","file_extension":"%s"}`, spec.DocType, spec.FileExtension())
}
Expand Down
50 changes: 38 additions & 12 deletions shortcuts/drive/drive_import_common.go
Original file line number Diff line number Diff line change
Expand Up @@ -59,14 +59,39 @@
FolderToken string
Name string
TargetToken string // existing bitable token to import data into (only for type=bitable)

// EffectiveExt is a caller-supplied override for the extension otherwise
// derived from FilePath (see ImportParams.FileExtension). It lets a caller
// that has detected the file's real container correct a mislabeled name
// (e.g. an OOXML workbook saved as .xls). Empty means "trust the filename".
EffectiveExt string
}

func (s driveImportSpec) FileExtension() string {
// rawExtension is the lowercased extension taken verbatim from the file name.
func (s driveImportSpec) rawExtension() string {
return strings.TrimPrefix(strings.ToLower(filepath.Ext(s.FilePath)), ".")
}

// FileExtension is the extension the import pipeline treats as authoritative:
// the content-sniffed override when set, otherwise the file name's extension.
func (s driveImportSpec) FileExtension() string {
if s.EffectiveExt != "" {
return s.EffectiveExt

Check warning on line 79 in shortcuts/drive/drive_import_common.go

View check run for this annotation

Codecov / codecov/patch

shortcuts/drive/drive_import_common.go#L79

Added line #L79 was not covered by tests
}
return s.rawExtension()
}

// SourceFileName is the name used when staging the upload media. When content
// sniffing corrected the extension, the staged name must carry the corrected
// suffix too: the import backend cross-checks the media file name's extension
// against the file_extension in the import task and rejects a mismatch with
// "import file extension not match" (code 1069910).
func (s driveImportSpec) SourceFileName() string {
return filepath.Base(s.FilePath)
base := filepath.Base(s.FilePath)
if s.EffectiveExt != "" && s.EffectiveExt != s.rawExtension() {
base = strings.TrimSuffix(base, filepath.Ext(base)) + "." + s.EffectiveExt

Check warning on line 92 in shortcuts/drive/drive_import_common.go

View check run for this annotation

Codecov / codecov/patch

shortcuts/drive/drive_import_common.go#L92

Added line #L92 was not covered by tests
}
return base
}

func (s driveImportSpec) TargetFileName() string {
Expand Down Expand Up @@ -97,18 +122,20 @@

// uploadMediaForImport uploads the source file to the temporary import media
// endpoint and returns the file token consumed by import_tasks.
func uploadMediaForImport(ctx context.Context, runtime *common.RuntimeContext, filePath, fileName, docType string) (string, error) {
func uploadMediaForImport(ctx context.Context, runtime *common.RuntimeContext, spec driveImportSpec) (string, error) {
filePath := spec.FilePath
fileName := spec.SourceFileName()
importInfo, err := runtime.FileIO().Stat(filePath)
if err != nil {
return "", driveInputStatError(err)
}

fileSize := importInfo.Size()
if err = validateDriveImportFileSize(filePath, docType, fileSize); err != nil {
if err = validateDriveImportFileSize(spec.FileExtension(), spec.DocType, fileSize); err != nil {
return "", err
}

extra, err := buildImportMediaExtra(filePath, docType)
extra, err := buildImportMediaExtra(spec.FileExtension(), spec.DocType)
if err != nil {
return "", err
}
Expand Down Expand Up @@ -139,23 +166,23 @@
})
}

func buildImportMediaExtra(filePath, docType string) (string, error) {
func buildImportMediaExtra(ext, docType string) (string, error) {
// The import media endpoint uses extra to decide both the target native type
// and how to interpret the uploaded source file.
extraBytes, err := json.Marshal(map[string]string{
"obj_type": docType,
"file_extension": strings.TrimPrefix(strings.ToLower(filepath.Ext(filePath)), "."),
"file_extension": ext,
})
if err != nil {
return "", errs.NewInternalError(errs.SubtypeUnknown, "build upload extra failed: %v", err).WithCause(err)
}
return string(extraBytes), nil
}

func driveImportFileSizeLimit(filePath, docType string) (int64, bool) {
func driveImportFileSizeLimit(ext, docType string) (int64, bool) {
// Keep the limit mapping local to import flows so we do not widen behavior
// changes beyond drive +import.
switch strings.TrimPrefix(strings.ToLower(filepath.Ext(filePath)), ".") {
switch ext {
case "docx", "doc":
return driveImport600MBFileSizeLimit, true
case "pptx":
Expand All @@ -174,13 +201,12 @@
}
}

func validateDriveImportFileSize(filePath, docType string, fileSize int64) error {
limit, ok := driveImportFileSizeLimit(filePath, docType)
func validateDriveImportFileSize(ext, docType string, fileSize int64) error {
limit, ok := driveImportFileSizeLimit(ext, docType)
if !ok || fileSize <= limit {
return nil
}

ext := strings.TrimPrefix(strings.ToLower(filepath.Ext(filePath)), ".")
if ext == "csv" {
// CSV is the only source format whose limit depends on the target type.
return errs.NewValidationError(errs.SubtypeInvalidArgument,
Expand Down
20 changes: 10 additions & 10 deletions shortcuts/drive/drive_import_common_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -94,61 +94,61 @@ func TestValidateDriveImportFileSize(t *testing.T) {

tests := []struct {
name string
filePath string
ext string
docType string
fileSize int64
wantText string
}{
{
name: "docx exceeds 600mb limit",
filePath: "./report.docx",
ext: "docx",
docType: "docx",
fileSize: driveImport600MBFileSizeLimit + 1,
wantText: "exceeds 600.0 MB import limit for .docx",
},
{
name: "csv sheet exceeds 20mb limit",
filePath: "./data.csv",
ext: "csv",
docType: "sheet",
fileSize: driveImport20MBFileSizeLimit + 1,
wantText: "exceeds 20.0 MB import limit for .csv when importing as sheet",
},
{
name: "csv bitable exceeds 100mb limit",
filePath: "./data.csv",
ext: "csv",
docType: "bitable",
fileSize: driveImport100MBFileSizeLimit + 1,
wantText: "exceeds 100.0 MB import limit for .csv when importing as bitable",
},
{
name: "xlsx within 800mb limit",
filePath: "./data.xlsx",
ext: "xlsx",
docType: "sheet",
fileSize: driveImport800MBFileSizeLimit,
},
{
name: "pptx exceeds 500mb limit",
filePath: "./deck.pptx",
ext: "pptx",
docType: "slides",
fileSize: driveImport500MBFileSizeLimit + 1,
wantText: "exceeds 500.0 MB import limit for .pptx",
},
{
name: "pptx within 500mb limit",
filePath: "./deck.pptx",
ext: "pptx",
docType: "slides",
fileSize: driveImport500MBFileSizeLimit,
},
{
name: "base exceeds 20mb limit",
filePath: "./snapshot.base",
ext: "base",
docType: "bitable",
fileSize: driveImport20MBFileSizeLimit + 1,
wantText: "exceeds 20.0 MB import limit for .base",
},
{
name: "base within 20mb limit",
filePath: "./snapshot.base",
ext: "base",
docType: "bitable",
fileSize: driveImport20MBFileSizeLimit,
},
Expand All @@ -158,7 +158,7 @@ func TestValidateDriveImportFileSize(t *testing.T) {
t.Run(tt.name, func(t *testing.T) {
t.Parallel()

err := validateDriveImportFileSize(tt.filePath, tt.docType, tt.fileSize)
err := validateDriveImportFileSize(tt.ext, tt.docType, tt.fileSize)
if tt.wantText == "" {
if err != nil {
t.Fatalf("expected no error, got %v", err)
Expand Down
Loading
Loading