Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
092388e
fix: reduce base shortcut token overhead
zhouyue-bytedance Jun 9, 2026
62fd727
fix: address eval feedback on base token optimization
zhouyue-bytedance Jun 10, 2026
3a8e86d
docs(lark-base): consolidate caveat sections under 注意事项 chapter
zhouyue-bytedance Jun 10, 2026
64e9b00
fix: 回滚 record alias(仅保留 record search 的 query alias) & 去除 drive expo…
yballul-bytedance Jun 10, 2026
54b59a6
feat: 分拆 guide 和 schema
yballul-bytedance Jun 10, 2026
cebb313
feat: 增加 workflow 草图引导,一次性读取所有需要的 step 对应的数据结构
yballul-bytedance Jun 11, 2026
dc894e4
fix: address r3 eval feedback on token waste
zhouyue-bytedance Jun 11, 2026
f8569da
Merge branch 'github-base-token-improve' of https://github.com/zhouyu…
zhouyue-bytedance Jun 11, 2026
f7096d2
feat: add guess-tolerant flag aliases and misuse hints for base short…
zhouyue-bytedance Jun 11, 2026
19daffa
fix: plural alias flags always split ASCII comma as list separator
zhouyue-bytedance Jun 11, 2026
e4df47f
docs(lark-base): align dashboard guide field-list examples with batch…
zhouyue-bytedance Jun 11, 2026
c584aae
Merge remote-tracking branch 'github-upstream/main' into github-base-…
zhouyue-bytedance Jun 12, 2026
8786074
feat(base): forgive record pagination flags and consolidate dashboard…
zhouyue-bytedance Jun 15, 2026
2754e84
feat(base): accept /base and /wiki URLs as --base-token, with token-o…
zhouyue-bytedance Jun 15, 2026
20b3702
fix(base): address PR #1426 review comments
yballul-bytedance Jun 19, 2026
5885795
refactor(base): drop client-side table ref resolution in field-list
yballul-bytedance Jun 19, 2026
fb7f4e5
Merge remote-tracking branch 'upstream/main' into github-base-token-i…
yballul-bytedance Jun 19, 2026
a7e25b5
fix(base): remove unused table helper
yballul-bytedance Jun 20, 2026
5b415d1
fix(base): address review gate issues
yballul-bytedance Jun 20, 2026
1cca939
docs(lark-base): clarify workflow guide step ref reading
yballul-bytedance Jun 20, 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
41 changes: 33 additions & 8 deletions internal/suggest/suggest.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,10 @@
// carrying their own copy.
package suggest

import "sort"
import (
"sort"
"strings"
)

// Levenshtein computes the classic edit distance between two strings. It is
// rune-aware, so it is correct for multi-byte input.
Expand Down Expand Up @@ -51,22 +54,29 @@ func Levenshtein(a, b string) int {
// signal of intent that raw edit distance misses.
func Closest(typed string, candidates []string, maxN int) []string {
type scored struct {
name string
prefix int
dist int
name string
contain bool
prefix int
dist int
}
limit := editLimit(typed)
ranked := make([]scored, 0, len(candidates))
for _, c := range candidates {
p := sharedPrefixLen(typed, c)
d := Levenshtein(typed, c)
// Keep only plausible matches: a meaningful shared prefix, or an edit
// distance within budget. Drop everything else so the hint stays short.
if p >= 3 || d <= limit {
ranked = append(ranked, scored{name: c, prefix: p, dist: d})
ct := containsSegment(typed, c)
// Keep only plausible matches: a meaningful shared prefix, an edit
// distance within budget, or one name containing the other (a missing
// namespace prefix like "+block-list" vs "+base-block-list"). Drop
// everything else so the hint stays short.
if p >= 3 || d <= limit || ct {
ranked = append(ranked, scored{name: c, contain: ct, prefix: p, dist: d})
}
}
sort.Slice(ranked, func(i, j int) bool {
if ranked[i].contain != ranked[j].contain {
return ranked[i].contain
}
if ranked[i].prefix != ranked[j].prefix {
return ranked[i].prefix > ranked[j].prefix
}
Expand Down Expand Up @@ -94,6 +104,21 @@ func editLimit(s string) int {
return 2
}

// containsSegment reports whether one name contains the other as a substring
// after stripping the "+"/"--" sigils. It catches hallucinated names that drop
// a namespace prefix (e.g. "+block-list" for "+base-block-list"), which share
// almost no prefix and sit far beyond the edit-distance budget. The shorter
// side must be at least 5 runes so generic fragments like "list" do not match
// half the catalog.
func containsSegment(a, b string) bool {
a = strings.TrimLeft(a, "+-")
b = strings.TrimLeft(b, "+-")
if len([]rune(a)) > len([]rune(b)) {
a, b = b, a
}
return len([]rune(a)) >= 5 && strings.Contains(b, a)
}

func sharedPrefixLen(a, b string) int {
ra, rb := []rune(a), []rune(b)
n := 0
Expand Down
19 changes: 10 additions & 9 deletions shortcuts/base/base_advperm_disable.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,12 +16,13 @@ import (
)

var BaseAdvpermDisable = common.Shortcut{
Service: "base",
Command: "+advperm-disable",
Description: "Disable advanced permissions for a Base",
Risk: "high-risk-write",
Scopes: []string{"base:app:update"},
AuthTypes: []string{"user", "bot"},
Service: "base",
Command: "+advperm-disable",
Description: "Disable advanced permissions for a Base",
Risk: "high-risk-write",
ConditionalScopes: []string{"wiki:node:retrieve"},
Scopes: []string{"base:app:update"},
AuthTypes: []string{"user", "bot"},
Flags: []common.Flag{
{Name: "base-token", Desc: "base token", Required: true},
},
Expand All @@ -30,18 +31,18 @@ var BaseAdvpermDisable = common.Shortcut{
"Disabling advanced permissions invalidates existing custom roles; confirm the target Base before passing --yes.",
},
Validate: func(ctx context.Context, runtime *common.RuntimeContext) error {
if strings.TrimSpace(runtime.Str("base-token")) == "" {
if strings.TrimSpace(baseTokenOrRaw(runtime)) == "" {
return baseFlagErrorf("--base-token must not be blank")
}
return nil
},
DryRun: func(ctx context.Context, runtime *common.RuntimeContext) *common.DryRunAPI {
return common.NewDryRunAPI().
PUT("/open-apis/base/v3/bases/:base_token/advperm/enable?enable=false").
Set("base_token", runtime.Str("base-token"))
Set("base_token", baseTokenOrRaw(runtime))
},
Execute: func(ctx context.Context, runtime *common.RuntimeContext) error {
baseToken := runtime.Str("base-token")
baseToken := baseTokenOrRaw(runtime)

queryParams := make(larkcore.QueryParams)
queryParams.Set("enable", "false")
Expand Down
19 changes: 10 additions & 9 deletions shortcuts/base/base_advperm_enable.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,31 +16,32 @@ import (
)

var BaseAdvpermEnable = common.Shortcut{
Service: "base",
Command: "+advperm-enable",
Description: "Enable advanced permissions for a Base",
Risk: "write",
Scopes: []string{"base:app:update"},
AuthTypes: []string{"user", "bot"},
Service: "base",
Command: "+advperm-enable",
Description: "Enable advanced permissions for a Base",
Risk: "write",
ConditionalScopes: []string{"wiki:node:retrieve"},
Scopes: []string{"base:app:update"},
AuthTypes: []string{"user", "bot"},
Flags: []common.Flag{
{Name: "base-token", Desc: "base token", Required: true},
},
Tips: []string{
"Caller must be a Base admin; enable advanced permissions before creating or updating roles.",
},
Validate: func(ctx context.Context, runtime *common.RuntimeContext) error {
if strings.TrimSpace(runtime.Str("base-token")) == "" {
if strings.TrimSpace(baseTokenOrRaw(runtime)) == "" {
return baseFlagErrorf("--base-token must not be blank")
}
return nil
},
DryRun: func(ctx context.Context, runtime *common.RuntimeContext) *common.DryRunAPI {
return common.NewDryRunAPI().
PUT("/open-apis/base/v3/bases/:base_token/advperm/enable?enable=true").
Set("base_token", runtime.Str("base-token"))
Set("base_token", baseTokenOrRaw(runtime))
},
Execute: func(ctx context.Context, runtime *common.RuntimeContext) error {
baseToken := runtime.Str("base-token")
baseToken := baseTokenOrRaw(runtime)

queryParams := make(larkcore.QueryParams)
queryParams.Set("enable", "true")
Expand Down
13 changes: 7 additions & 6 deletions shortcuts/base/base_block_create.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,13 @@ import (
)

var BaseBaseBlockCreate = common.Shortcut{
Service: "base",
Command: "+base-block-create",
Description: "Create a block",
Risk: "write",
Scopes: []string{"base:block:create"},
AuthTypes: authTypes(),
Service: "base",
Command: "+base-block-create",
Description: "Create a block",
Risk: "write",
ConditionalScopes: []string{"wiki:node:retrieve"},
Scopes: []string{"base:block:create"},
AuthTypes: authTypes(),
Flags: []common.Flag{
baseTokenFlag(true),
{Name: "type", Desc: "resource type", Required: true, Enum: baseBlockTypeEnums},
Expand Down
13 changes: 7 additions & 6 deletions shortcuts/base/base_block_delete.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,13 @@ import (
)

var BaseBaseBlockDelete = common.Shortcut{
Service: "base",
Command: "+base-block-delete",
Description: "Delete a block",
Risk: "high-risk-write",
Scopes: []string{"base:block:delete"},
AuthTypes: authTypes(),
Service: "base",
Command: "+base-block-delete",
Description: "Delete a block",
Risk: "high-risk-write",
ConditionalScopes: []string{"wiki:node:retrieve"},
Scopes: []string{"base:block:delete"},
AuthTypes: authTypes(),
Flags: []common.Flag{
baseTokenFlag(true),
baseBlockIDFlag(true),
Expand Down
13 changes: 7 additions & 6 deletions shortcuts/base/base_block_list.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,13 @@ import (
)

var BaseBaseBlockList = common.Shortcut{
Service: "base",
Command: "+base-block-list",
Description: "List blocks in a base",
Risk: "read",
Scopes: []string{"base:block:read"},
AuthTypes: authTypes(),
Service: "base",
Command: "+base-block-list",
Description: "List blocks in a base",
Risk: "read",
ConditionalScopes: []string{"wiki:node:retrieve"},
Scopes: []string{"base:block:read"},
AuthTypes: authTypes(),
Flags: []common.Flag{
baseTokenFlag(true),
{Name: "type", Desc: "filter by resource type", Enum: baseBlockTypeEnums},
Expand Down
13 changes: 7 additions & 6 deletions shortcuts/base/base_block_move.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,13 @@ import (
)

var BaseBaseBlockMove = common.Shortcut{
Service: "base",
Command: "+base-block-move",
Description: "Move a block",
Risk: "write",
Scopes: []string{"base:block:update"},
AuthTypes: authTypes(),
Service: "base",
Command: "+base-block-move",
Description: "Move a block",
Risk: "write",
ConditionalScopes: []string{"wiki:node:retrieve"},
Scopes: []string{"base:block:update"},
AuthTypes: authTypes(),
Flags: []common.Flag{
baseTokenFlag(true),
baseBlockIDFlag(true),
Expand Down
20 changes: 10 additions & 10 deletions shortcuts/base/base_block_ops.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,36 +20,36 @@ func dryRunBaseBlockList(_ context.Context, runtime *common.RuntimeContext) *com
return common.NewDryRunAPI().
POST("/open-apis/base/v3/bases/:base_token/blocks/list").
Body(buildBaseBlockListBody(runtime)).
Set("base_token", runtime.Str("base-token"))
Set("base_token", baseTokenOrRaw(runtime))
}

func dryRunBaseBlockCreate(_ context.Context, runtime *common.RuntimeContext) *common.DryRunAPI {
return common.NewDryRunAPI().
POST("/open-apis/base/v3/bases/:base_token/blocks").
Body(buildBaseBlockCreateBody(runtime)).
Set("base_token", runtime.Str("base-token"))
Set("base_token", baseTokenOrRaw(runtime))
}

func dryRunBaseBlockMove(_ context.Context, runtime *common.RuntimeContext) *common.DryRunAPI {
return common.NewDryRunAPI().
POST("/open-apis/base/v3/bases/:base_token/blocks/:block_id/move").
Body(buildBaseBlockMoveBody(runtime)).
Set("base_token", runtime.Str("base-token")).
Set("base_token", baseTokenOrRaw(runtime)).
Set("block_id", runtime.Str("block-id"))
}

func dryRunBaseBlockRename(_ context.Context, runtime *common.RuntimeContext) *common.DryRunAPI {
return common.NewDryRunAPI().
POST("/open-apis/base/v3/bases/:base_token/blocks/:block_id/rename").
Body(map[string]interface{}{"name": strings.TrimSpace(runtime.Str("name"))}).
Set("base_token", runtime.Str("base-token")).
Set("base_token", baseTokenOrRaw(runtime)).
Set("block_id", runtime.Str("block-id"))
}

func dryRunBaseBlockDelete(_ context.Context, runtime *common.RuntimeContext) *common.DryRunAPI {
return common.NewDryRunAPI().
DELETE("/open-apis/base/v3/bases/:base_token/blocks/:block_id").
Set("base_token", runtime.Str("base-token")).
Set("base_token", baseTokenOrRaw(runtime)).
Set("block_id", runtime.Str("block-id"))
}

Expand Down Expand Up @@ -78,7 +78,7 @@ func validateBaseBlockRename(runtime *common.RuntimeContext) error {
}

func executeBaseBlockList(runtime *common.RuntimeContext) error {
data, err := baseV3Call(runtime, "POST", baseV3Path("bases", runtime.Str("base-token"), "blocks", "list"), nil, buildBaseBlockListBody(runtime))
data, err := baseV3Call(runtime, "POST", baseV3Path("bases", baseTokenOrRaw(runtime), "blocks", "list"), nil, buildBaseBlockListBody(runtime))
if err != nil {
return err
}
Expand All @@ -88,7 +88,7 @@ func executeBaseBlockList(runtime *common.RuntimeContext) error {
}

func executeBaseBlockCreate(runtime *common.RuntimeContext) error {
data, err := baseV3Call(runtime, "POST", baseV3Path("bases", runtime.Str("base-token"), "blocks"), nil, buildBaseBlockCreateBody(runtime))
data, err := baseV3Call(runtime, "POST", baseV3Path("bases", baseTokenOrRaw(runtime), "blocks"), nil, buildBaseBlockCreateBody(runtime))
if err != nil {
return err
}
Expand All @@ -97,7 +97,7 @@ func executeBaseBlockCreate(runtime *common.RuntimeContext) error {
}

func executeBaseBlockMove(runtime *common.RuntimeContext) error {
data, err := baseV3Call(runtime, "POST", baseV3Path("bases", runtime.Str("base-token"), "blocks", runtime.Str("block-id"), "move"), nil, buildBaseBlockMoveBody(runtime))
data, err := baseV3Call(runtime, "POST", baseV3Path("bases", baseTokenOrRaw(runtime), "blocks", runtime.Str("block-id"), "move"), nil, buildBaseBlockMoveBody(runtime))
if err != nil {
return err
}
Expand All @@ -106,7 +106,7 @@ func executeBaseBlockMove(runtime *common.RuntimeContext) error {
}

func executeBaseBlockRename(runtime *common.RuntimeContext) error {
data, err := baseV3Call(runtime, "POST", baseV3Path("bases", runtime.Str("base-token"), "blocks", runtime.Str("block-id"), "rename"), nil, map[string]interface{}{
data, err := baseV3Call(runtime, "POST", baseV3Path("bases", baseTokenOrRaw(runtime), "blocks", runtime.Str("block-id"), "rename"), nil, map[string]interface{}{
"name": strings.TrimSpace(runtime.Str("name")),
})
if err != nil {
Expand All @@ -117,7 +117,7 @@ func executeBaseBlockRename(runtime *common.RuntimeContext) error {
}

func executeBaseBlockDelete(runtime *common.RuntimeContext) error {
data, err := baseV3Call(runtime, "DELETE", baseV3Path("bases", runtime.Str("base-token"), "blocks", runtime.Str("block-id")), nil, nil)
data, err := baseV3Call(runtime, "DELETE", baseV3Path("bases", baseTokenOrRaw(runtime), "blocks", runtime.Str("block-id")), nil, nil)
if err != nil {
return err
}
Expand Down
13 changes: 7 additions & 6 deletions shortcuts/base/base_block_rename.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,13 @@ import (
)

var BaseBaseBlockRename = common.Shortcut{
Service: "base",
Command: "+base-block-rename",
Description: "Rename a block",
Risk: "write",
Scopes: []string{"base:block:update"},
AuthTypes: authTypes(),
Service: "base",
Command: "+base-block-rename",
Description: "Rename a block",
Risk: "write",
ConditionalScopes: []string{"wiki:node:retrieve"},
Scopes: []string{"base:block:update"},
AuthTypes: authTypes(),
Flags: []common.Flag{
baseTokenFlag(true),
baseBlockIDFlag(true),
Expand Down
2 changes: 1 addition & 1 deletion shortcuts/base/base_command_common.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ func authTypes() []string {
}

func baseTokenFlag(required bool) common.Flag {
return common.Flag{Name: "base-token", Desc: "base token", Required: required}
return common.Flag{Name: "base-token", Desc: "base token or /base|/wiki URL", Required: required}
}

func tableRefFlag(required bool) common.Flag {
Expand Down
15 changes: 8 additions & 7 deletions shortcuts/base/base_copy.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,14 @@ import (
)

var BaseBaseCopy = common.Shortcut{
Service: "base",
Command: "+base-copy",
Description: "Copy a base resource",
Risk: "write",
UserScopes: []string{"base:app:copy"},
BotScopes: []string{"base:app:copy", "docs:permission.member:create"},
AuthTypes: authTypes(),
Service: "base",
Command: "+base-copy",
Description: "Copy a base resource",
Risk: "write",
ConditionalUserScopes: []string{"wiki:node:retrieve"},
Scopes: []string{"base:app:copy"},
BotScopes: []string{"base:app:copy", "docs:permission.member:create"},
AuthTypes: authTypes(),
Flags: []common.Flag{
baseTokenFlag(true),
{Name: "name", Desc: "new base name"},
Expand Down
Loading
Loading