Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
17 commits
Select commit Hold shift + click to select a range
24e9caf
refactor: converge command pipelines onto a typed metadata model + ca…
liangshuo-1 May 31, 2026
9f1a786
fix(cmdutil): ParseJSONMap returns a writable map for `null`
liangshuo-1 Jun 1, 2026
950be76
feat(service): derive typed-flag collision set; surface params-only p…
liangshuo-1 Jun 3, 2026
3a451d4
fix(service): treat explicit false/0 as real param values; passthroug…
liangshuo-1 Jun 4, 2026
70d6394
feat(service): surface min/max bounds on method --help
liangshuo-1 Jun 4, 2026
cb275ac
test(meta): cover the truly-nil affordance payload
liangshuo-1 Jun 4, 2026
be84277
refactor(meta): name the access-token vocabulary (meta.Token)
liangshuo-1 Jun 4, 2026
c3384dd
refactor(service): assemble method Long in one place
liangshuo-1 Jun 4, 2026
742d1d3
feat(service): carry the field description on typed-flag help lines
liangshuo-1 Jun 4, 2026
5c8dff7
refactor(service): converge param help onto a single fieldFacts list
liangshuo-1 Jun 4, 2026
8c45c42
fix(registry): cache the remote data payload verbatim
liangshuo-1 Jun 4, 2026
65264af
fix(service): render grouped usage by hand, not via SetUsageTemplate
liangshuo-1 Jun 4, 2026
59b15c4
fix(service): trim dangling connectors from truncated help clauses
liangshuo-1 Jun 4, 2026
f446eb9
style(service): gofmt sanitize_test.go alignment
liangshuo-1 Jun 9, 2026
8da79e7
fix(service): backquote metavar leak; missing-required hint names bot…
liangshuo-1 Jun 10, 2026
61aba63
style(registry): tighten remoteResponse comment
liangshuo-1 Jun 10, 2026
f85e25d
feat(schema): tolerate --as for agent compatibility
liangshuo-1 Jun 11, 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
18 changes: 18 additions & 0 deletions cmd/api/api_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,24 @@ func TestApiCmd_DryRun(t *testing.T) {
}
}

// Regression: --params null parses to a nil map; writing page_size onto it must
// not panic. Symmetric to the typed-flag overlay path in cmd/service — both
// write into the map ParseJSONMap returns.
func TestApiCmd_NullParamsWithPageSize(t *testing.T) {
f, stdout, _, _ := cmdutil.TestFactory(t, &core.CliConfig{
AppID: "test-app", AppSecret: "test-secret", Brand: core.BrandFeishu,
})

cmd := NewCmdApi(f, nil)
cmd.SetArgs([]string{"GET", "/open-apis/test", "--params", "null", "--page-size", "50", "--as", "bot", "--dry-run"})
if err := cmd.Execute(); err != nil {
t.Fatalf("--params null with --page-size should not error, got: %v", err)
}
if out := stdout.String(); !strings.Contains(out, "page_size") {
t.Errorf("expected page_size applied over null --params, got:\n%s", out)
}
}

func TestApiCmd_BotMode(t *testing.T) {
f, stdout, _, reg := cmdutil.TestFactory(t, &core.CliConfig{
AppID: "test-app", AppSecret: "test-secret", Brand: core.BrandFeishu,
Expand Down
13 changes: 4 additions & 9 deletions cmd/auth/login_interactive.go
Original file line number Diff line number Diff line change
Expand Up @@ -92,16 +92,11 @@ func buildDomainMeta(name, lang string) domainMeta {
Description: desc,
}
}
// Fallback: read from from_meta spec (legacy)
meta := registry.LoadFromMeta(name)
// Fallback: read from the typed service spec (legacy)
dm := domainMeta{Name: name}
if meta != nil {
if t, ok := meta["title"].(string); ok {
dm.Title = t
}
if d, ok := meta["description"].(string); ok {
dm.Description = d
}
if svc, ok := registry.ServiceTyped(name); ok {
dm.Title = svc.Title
dm.Description = svc.Description
}
return dm
}
Expand Down
52 changes: 52 additions & 0 deletions cmd/command_catalog_path_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
// Copyright (c) 2026 Lark Technologies Pte. Ltd.
// SPDX-License-Identifier: MIT

package cmd

import (
"reflect"
"testing"

"github.com/spf13/cobra"
)

// TestCommandCatalogPath pins that the auth-hint path reconstruction inverts the
// service command tree for any depth — flat dotted resources AND genuinely
// nested resources — so it round-trips through apicatalog.Resolve instead of
// assuming a fixed root->service->resource->method shape.
func TestCommandCatalogPath(t *testing.T) {
chain := func(names ...string) *cobra.Command {
var parent, leaf *cobra.Command
for _, n := range names {
c := &cobra.Command{Use: n}
if parent != nil {
parent.AddCommand(c)
}
parent = c
leaf = c
}
return leaf
}

tests := []struct {
name string
leaf *cobra.Command
want []string
}{
{"flat dotted resource", chain("lark-cli", "im", "chat.members", "create"), []string{"im", "chat.members", "create"}},
{"nested resources", chain("lark-cli", "im", "spaces", "items", "get"), []string{"im", "spaces", "items", "get"}},
{"service level", chain("lark-cli", "im"), []string{"im"}},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := commandCatalogPath(tt.leaf); !reflect.DeepEqual(got, tt.want) {
t.Errorf("commandCatalogPath = %v, want %v", got, tt.want)
}
})
}

// The root command (no parent) has no catalog path.
if got := commandCatalogPath(&cobra.Command{Use: "lark-cli"}); len(got) != 0 {
t.Errorf("root path = %v, want empty", got)
}
}
50 changes: 25 additions & 25 deletions cmd/error_auth_hint.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import (
"github.com/spf13/cobra"

"github.com/larksuite/cli/errs"
"github.com/larksuite/cli/internal/apicatalog"
internalauth "github.com/larksuite/cli/internal/auth"
"github.com/larksuite/cli/internal/cmdutil"
"github.com/larksuite/cli/internal/core"
Expand Down Expand Up @@ -118,38 +119,37 @@ func resolveDeclaredShortcutScopes(cmd *cobra.Command, identity string) []string
}

// resolveDeclaredServiceMethodScopes returns the scopes declared by a
// service/resource/method command from the embedded from_meta registry.
// service/resource/method command. It reconstructs the catalog path from the
// command ancestry and resolves it through the same navigation Module the
// command tree is built from (apicatalog), so it stays correct for nested
// resources instead of hard-coding a root->service->resource->method depth.
// Non-method commands (services, resources, shortcuts) resolve to a non-method
// target and yield no scopes.
func resolveDeclaredServiceMethodScopes(cmd *cobra.Command, identity string) []string {
// Service-method scope lookup only applies to commands mounted as
// root -> service -> resource -> method. Non-resource/method commands
// intentionally return no scopes here so auth-hint enrichment does not
// change runtime semantics for other command shapes.
if cmd == nil || cmd.Parent() == nil || cmd.Parent().Parent() == nil || cmd.Parent().Parent().Parent() == nil {
if cmd == nil || strings.HasPrefix(cmd.Name(), "+") {
return nil
}
if strings.HasPrefix(cmd.Name(), "+") {
path := commandCatalogPath(cmd)
if len(path) == 0 {
return nil
}

service := cmd.Parent().Parent().Name()
resource := cmd.Parent().Name()
method := cmd.Name()

spec := registry.LoadFromMeta(service)
if spec == nil {
return nil
}
resources, _ := spec["resources"].(map[string]interface{})
resMap, _ := resources[resource].(map[string]interface{})
if resMap == nil {
target, err := registry.RuntimeCatalog().Resolve(path)
if err != nil || target.Kind != apicatalog.TargetMethod {
return nil
}
methods, _ := resMap["methods"].(map[string]interface{})
methodMap, _ := methods[method].(map[string]interface{})
if methodMap == nil {
return nil
}
return registry.DeclaredScopesForMethod(methodMap, identity)
return registry.DeclaredScopesForMethod(target.Method.Method, identity)
}

// commandCatalogPath reconstructs the catalog path [service, resource..., method]
// from a command's ancestry, excluding the root command. It is the inverse of
// the service command tree's construction, so any depth (flat or nested)
// round-trips through apicatalog.Resolve.
func commandCatalogPath(cmd *cobra.Command) []string {
var path []string
for c := cmd; c != nil && c.Parent() != nil; c = c.Parent() {
path = append([]string{c.Name()}, path...)
}
return path
}

// shortcutSupportsIdentity reports whether a shortcut supports the requested
Expand Down
2 changes: 1 addition & 1 deletion cmd/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ const rootLong = `lark-cli — Lark/Feishu CLI tool.
USAGE:
lark-cli <command> [subcommand] [method] [options]
lark-cli api <method> <path> [--params <json>] [--data <json>]
lark-cli schema <service.resource.method> [--format pretty]
lark-cli schema <service.resource.method>

EXAMPLES:
# View upcoming events
Expand Down
Loading
Loading