mirror of
https://github.com/larksuite/cli.git
synced 2026-07-03 14:02:43 +08:00
fix(wiki): rename +node-get --token to --node-token, keep alias (#1074)
Per issue #1049 (third point), wiki +node-get used --token while sibling commands (+node-delete / +node-copy / +move) use --node-token. The inconsistency forced humans and AI agents to remember which adjacent command takes which flag. Make --node-token the canonical flag and keep --token as a hidden, deprecated alias so existing scripts continue to work. pflag's MarkDeprecated prints "Flag --token has been deprecated, use --node-token instead" to stderr on use, guiding callers to migrate. Conflict between the two with different values is rejected upfront. Skills docs (lark-wiki, lark-base) updated to prefer --node-token. Change-Id: I3415a98f079613c0b1a0b989cf54a09cbb8986fb
This commit is contained in:
@@ -14,6 +14,7 @@ import (
|
||||
|
||||
"github.com/larksuite/cli/internal/output"
|
||||
"github.com/larksuite/cli/shortcuts/common"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
// wikiNodeGetURLObjTypes maps a Lark URL path prefix (slash-bounded) to the
|
||||
@@ -57,14 +58,26 @@ var WikiNodeGet = common.Shortcut{
|
||||
AuthTypes: []string{"user", "bot"},
|
||||
HasFormat: true,
|
||||
Flags: []common.Flag{
|
||||
{Name: "token", Desc: "wiki node_token, obj_token, or a Lark URL embedding one of them", Required: true},
|
||||
{Name: "obj-type", Desc: "obj_type when --token is an obj_token; auto-inferred from URL path when omitted", Enum: wikiNodeGetObjTypeEnum},
|
||||
// --node-token is the canonical flag, matching sibling wiki commands
|
||||
// (+node-delete / +node-copy / +move). --token is the original name
|
||||
// and is kept as a hidden deprecated alias for backward compatibility;
|
||||
// MarkDeprecated (registered in PostMount) prints a stderr warning
|
||||
// when --token is used.
|
||||
{Name: "node-token", Desc: "wiki node_token, obj_token, or a Lark URL embedding one of them"},
|
||||
{Name: "token", Desc: "DEPRECATED: use --node-token", Hidden: true},
|
||||
{Name: "obj-type", Desc: "obj_type when --node-token is an obj_token; auto-inferred from URL path when omitted", Enum: wikiNodeGetObjTypeEnum},
|
||||
{Name: "space-id", Desc: "optional: assert the resolved node lives in this space"},
|
||||
},
|
||||
Tips: []string{
|
||||
"--token accepts a raw token (wikcnXXX, docxXXX, ...) or a Lark URL like https://feishu.cn/wiki/<token> or https://feishu.cn/docx/<token>.",
|
||||
"--node-token accepts a raw token (wikcnXXX, docxXXX, ...) or a Lark URL like https://feishu.cn/wiki/<token> or https://feishu.cn/docx/<token>.",
|
||||
"For raw obj_tokens (not starting with wik), pass --obj-type so the API knows how to resolve them; URL inputs infer it from the path.",
|
||||
"Pair with +move / +node-copy / +delete-space to confirm space_id, obj_type, and parent before mutating.",
|
||||
"--token is the deprecated original name and still works for backward compatibility; new scripts should use --node-token.",
|
||||
},
|
||||
PostMount: func(cmd *cobra.Command) {
|
||||
// cobra's MarkDeprecated prints "Flag --token has been deprecated, use --node-token instead"
|
||||
// to stderr on use, and hides the flag from --help (matching the Hidden: true marker above).
|
||||
_ = cmd.Flags().MarkDeprecated("token", "use --node-token instead")
|
||||
},
|
||||
Validate: func(ctx context.Context, runtime *common.RuntimeContext) error {
|
||||
_, err := readWikiNodeGetSpec(runtime)
|
||||
@@ -142,20 +155,45 @@ func (spec wikiNodeGetSpec) RequestParams() map[string]interface{} {
|
||||
}
|
||||
|
||||
func readWikiNodeGetSpec(runtime *common.RuntimeContext) (wikiNodeGetSpec, error) {
|
||||
return parseWikiNodeGetSpec(
|
||||
rawToken, err := resolveWikiNodeGetRawToken(
|
||||
runtime.Str("node-token"),
|
||||
runtime.Str("token"),
|
||||
)
|
||||
if err != nil {
|
||||
return wikiNodeGetSpec{}, err
|
||||
}
|
||||
return parseWikiNodeGetSpec(
|
||||
rawToken,
|
||||
runtime.Str("obj-type"),
|
||||
runtime.Str("space-id"),
|
||||
)
|
||||
}
|
||||
|
||||
// resolveWikiNodeGetRawToken picks between the canonical --node-token and the
|
||||
// deprecated --token alias. Both empty is fine (parseWikiNodeGetSpec will
|
||||
// surface the required-flag error). Both set with different values is rejected
|
||||
// upfront so callers fix the obvious bug rather than silently picking one.
|
||||
func resolveWikiNodeGetRawToken(nodeToken, legacyToken string) (string, error) {
|
||||
canonical := strings.TrimSpace(nodeToken)
|
||||
legacy := strings.TrimSpace(legacyToken)
|
||||
switch {
|
||||
case canonical != "" && legacy != "" && canonical != legacy:
|
||||
return "", output.ErrValidation(
|
||||
"--node-token and --token are both set with different values; pass --node-token only (--token is deprecated)")
|
||||
case canonical != "":
|
||||
return nodeToken, nil
|
||||
default:
|
||||
return legacyToken, nil
|
||||
}
|
||||
}
|
||||
|
||||
// parseWikiNodeGetSpec normalizes the raw flag values: extracts a token from a
|
||||
// URL when needed, picks the obj_type (URL path > explicit flag > none for
|
||||
// node_tokens), and validates the token shape.
|
||||
func parseWikiNodeGetSpec(rawToken, rawObjType, rawSpaceID string) (wikiNodeGetSpec, error) {
|
||||
tokenInput := strings.TrimSpace(rawToken)
|
||||
if tokenInput == "" {
|
||||
return wikiNodeGetSpec{}, output.ErrValidation("--token is required")
|
||||
return wikiNodeGetSpec{}, output.ErrValidation("--node-token is required")
|
||||
}
|
||||
|
||||
spec := wikiNodeGetSpec{
|
||||
@@ -166,12 +204,12 @@ func parseWikiNodeGetSpec(rawToken, rawObjType, rawSpaceID string) (wikiNodeGetS
|
||||
if strings.Contains(tokenInput, "://") {
|
||||
u, err := url.Parse(tokenInput)
|
||||
if err != nil || u.Path == "" {
|
||||
return wikiNodeGetSpec{}, output.ErrValidation("--token URL is malformed: %q", tokenInput)
|
||||
return wikiNodeGetSpec{}, output.ErrValidation("--node-token URL is malformed: %q", tokenInput)
|
||||
}
|
||||
token, urlObjType, ok := tokenAndObjTypeFromWikiURL(u.Path)
|
||||
if !ok {
|
||||
return wikiNodeGetSpec{}, output.ErrValidation(
|
||||
"unsupported --token URL path %q: expected /wiki/, /docx/, /doc/, /sheets/, /base/, /mindnote/, /slides/, or /file/ followed by a token",
|
||||
"unsupported --node-token URL path %q: expected /wiki/, /docx/, /doc/, /sheets/, /base/, /mindnote/, /slides/, or /file/ followed by a token",
|
||||
u.Path,
|
||||
)
|
||||
}
|
||||
@@ -192,7 +230,7 @@ func parseWikiNodeGetSpec(rawToken, rawObjType, rawSpaceID string) (wikiNodeGetS
|
||||
}
|
||||
} else if strings.ContainsAny(tokenInput, "/?#") {
|
||||
return wikiNodeGetSpec{}, output.ErrValidation(
|
||||
"--token must be a raw token or a full URL; partial paths are not accepted: %q",
|
||||
"--node-token must be a raw token or a full URL; partial paths are not accepted: %q",
|
||||
tokenInput,
|
||||
)
|
||||
} else {
|
||||
@@ -223,7 +261,7 @@ func parseWikiNodeGetSpec(rawToken, rawObjType, rawSpaceID string) (wikiNodeGetS
|
||||
}
|
||||
}
|
||||
|
||||
if err := validateOptionalResourceName(spec.Token, "--token"); err != nil {
|
||||
if err := validateOptionalResourceName(spec.Token, "--node-token"); err != nil {
|
||||
return wikiNodeGetSpec{}, err
|
||||
}
|
||||
if err := validateOptionalResourceName(spec.SpaceID, "--space-id"); err != nil {
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
package wiki
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
"reflect"
|
||||
@@ -12,6 +13,7 @@ import (
|
||||
|
||||
"github.com/larksuite/cli/internal/cmdutil"
|
||||
"github.com/larksuite/cli/internal/httpmock"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
func TestParseWikiNodeGetSpecRawNodeToken(t *testing.T) {
|
||||
@@ -98,7 +100,7 @@ func TestParseWikiNodeGetSpecRejectsUnsupportedURLPath(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
_, err := parseWikiNodeGetSpec("https://feishu.cn/im/chat/oc_123", "", "")
|
||||
if err == nil || !strings.Contains(err.Error(), "unsupported --token URL path") {
|
||||
if err == nil || !strings.Contains(err.Error(), "unsupported --node-token URL path") {
|
||||
t.Fatalf("expected unsupported URL path error, got %v", err)
|
||||
}
|
||||
}
|
||||
@@ -115,11 +117,61 @@ func TestParseWikiNodeGetSpecRejectsPartialPath(t *testing.T) {
|
||||
func TestParseWikiNodeGetSpecRejectsEmptyToken(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
if _, err := parseWikiNodeGetSpec(" ", "", ""); err == nil || !strings.Contains(err.Error(), "--token is required") {
|
||||
if _, err := parseWikiNodeGetSpec(" ", "", ""); err == nil || !strings.Contains(err.Error(), "--node-token is required") {
|
||||
t.Fatalf("expected required-token error, got %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestResolveWikiNodeGetRawTokenPrefersNodeToken(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
got, err := resolveWikiNodeGetRawToken("wikcnNEW", "")
|
||||
if err != nil || got != "wikcnNEW" {
|
||||
t.Fatalf("resolve(node-token only) = (%q, %v), want (wikcnNEW, nil)", got, err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestResolveWikiNodeGetRawTokenAcceptsLegacyToken(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
got, err := resolveWikiNodeGetRawToken("", "wikcnLEGACY")
|
||||
if err != nil || got != "wikcnLEGACY" {
|
||||
t.Fatalf("resolve(legacy only) = (%q, %v), want (wikcnLEGACY, nil)", got, err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestResolveWikiNodeGetRawTokenAcceptsBothWhenEqual(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
// Same value on both flags is harmless (e.g. a script doubled the input
|
||||
// while migrating to --node-token) — prefer the canonical one and don't
|
||||
// surface a conflict error.
|
||||
got, err := resolveWikiNodeGetRawToken("wikcnSAME", "wikcnSAME")
|
||||
if err != nil || got != "wikcnSAME" {
|
||||
t.Fatalf("resolve(both same) = (%q, %v), want (wikcnSAME, nil)", got, err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestResolveWikiNodeGetRawTokenRejectsConflict(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
_, err := resolveWikiNodeGetRawToken("wikcnNEW", "wikcnOLD")
|
||||
if err == nil || !strings.Contains(err.Error(), "both set with different values") {
|
||||
t.Fatalf("expected conflict error, got %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestResolveWikiNodeGetRawTokenEmptyDefersToParser(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
// Both empty is not an error here — the caller (parseWikiNodeGetSpec) is
|
||||
// where the required-flag check lives and produces the user-facing message.
|
||||
got, err := resolveWikiNodeGetRawToken("", "")
|
||||
if err != nil || got != "" {
|
||||
t.Fatalf("resolve(empty) = (%q, %v), want ('', nil)", got, err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestBuildWikiNodeGetDryRunSendsObjType(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
@@ -204,7 +256,7 @@ func TestWikiNodeGetMountedExecuteParsesURLAndFormatsOutput(t *testing.T) {
|
||||
|
||||
err := mountAndRunWiki(t, WikiNodeGet, []string{
|
||||
"+node-get",
|
||||
"--token", "https://feishu.cn/docx/docxXYZ",
|
||||
"--node-token", "https://feishu.cn/docx/docxXYZ",
|
||||
"--as", "bot",
|
||||
}, factory, stdout)
|
||||
if err != nil {
|
||||
@@ -245,6 +297,150 @@ func TestWikiNodeGetMountedExecuteParsesURLAndFormatsOutput(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestWikiNodeGetMountedAcceptsNodeTokenFlag(t *testing.T) {
|
||||
t.Setenv("LARKSUITE_CLI_CONFIG_DIR", t.TempDir())
|
||||
|
||||
factory, stdout, _, reg := cmdutil.TestFactory(t, wikiTestConfig())
|
||||
|
||||
stub := &httpmock.Stub{
|
||||
Method: "GET",
|
||||
URL: "/open-apis/wiki/v2/spaces/get_node",
|
||||
Body: map[string]interface{}{
|
||||
"code": 0,
|
||||
"data": map[string]interface{}{
|
||||
"node": map[string]interface{}{
|
||||
"space_id": "space_123",
|
||||
"node_token": "wikcnABC",
|
||||
"obj_token": "docxXYZ",
|
||||
"obj_type": "docx",
|
||||
"node_type": "origin",
|
||||
"title": "Via Node-Token",
|
||||
},
|
||||
},
|
||||
"msg": "success",
|
||||
},
|
||||
}
|
||||
var capturedQuery string
|
||||
stub.OnMatch = func(req *http.Request) {
|
||||
capturedQuery = req.URL.RawQuery
|
||||
}
|
||||
reg.Register(stub)
|
||||
|
||||
// Mount inline (rather than using mountAndRunWiki) so we can redirect the
|
||||
// subcommand's pflag output and assert that no deprecation warning leaks
|
||||
// when the canonical --node-token is used. The deprecation message comes
|
||||
// from pflag, not cobra, so SetErr on the cobra root is NOT enough — pflag
|
||||
// writes to FlagSet.Output(), which we redirect via Flags().SetOutput.
|
||||
var flagOut bytes.Buffer
|
||||
parent := mountWikiNodeGetWithFlagOut(t, factory, &flagOut)
|
||||
parent.SetArgs([]string{
|
||||
"+node-get",
|
||||
"--node-token", "https://feishu.cn/docx/docxXYZ",
|
||||
"--as", "bot",
|
||||
})
|
||||
stdout.Reset()
|
||||
if err := parent.Execute(); err != nil {
|
||||
t.Fatalf("parent.Execute() error = %v", err)
|
||||
}
|
||||
|
||||
if !strings.Contains(capturedQuery, "token=docxXYZ") || !strings.Contains(capturedQuery, "obj_type=docx") {
|
||||
t.Fatalf("captured query = %q, want token=docxXYZ and obj_type=docx", capturedQuery)
|
||||
}
|
||||
|
||||
data := decodeWikiEnvelope(t, stdout)
|
||||
if data["title"] != "Via Node-Token" {
|
||||
t.Fatalf("title = %#v, want Via Node-Token", data["title"])
|
||||
}
|
||||
if got := flagOut.String(); strings.Contains(got, "deprecated") {
|
||||
t.Fatalf("pflag output unexpectedly contains deprecation warning when using --node-token: %q", got)
|
||||
}
|
||||
}
|
||||
|
||||
// mountWikiNodeGetWithFlagOut mounts +node-get on a fresh parent and redirects
|
||||
// the subcommand's pflag output to w so tests can capture cobra/pflag-level
|
||||
// deprecation messages (which bypass the runtime IO stderr exposed by
|
||||
// TestFactory).
|
||||
func mountWikiNodeGetWithFlagOut(t *testing.T, factory *cmdutil.Factory, w *bytes.Buffer) *cobra.Command {
|
||||
t.Helper()
|
||||
parent := &cobra.Command{Use: "wiki"}
|
||||
WikiNodeGet.Mount(parent, factory)
|
||||
parent.SilenceErrors = true
|
||||
parent.SilenceUsage = true
|
||||
parent.SetErr(w)
|
||||
for _, child := range parent.Commands() {
|
||||
if child.Use == WikiNodeGet.Command {
|
||||
child.Flags().SetOutput(w)
|
||||
return parent
|
||||
}
|
||||
}
|
||||
t.Fatalf("mountWikiNodeGetWithFlagOut: subcommand %q not registered on parent", WikiNodeGet.Command)
|
||||
return nil
|
||||
}
|
||||
|
||||
func TestWikiNodeGetMountedLegacyTokenFlagWarnsButWorks(t *testing.T) {
|
||||
t.Setenv("LARKSUITE_CLI_CONFIG_DIR", t.TempDir())
|
||||
|
||||
factory, stdout, _, reg := cmdutil.TestFactory(t, wikiTestConfig())
|
||||
|
||||
reg.Register(&httpmock.Stub{
|
||||
Method: "GET",
|
||||
URL: "/open-apis/wiki/v2/spaces/get_node",
|
||||
Body: map[string]interface{}{
|
||||
"code": 0,
|
||||
"data": map[string]interface{}{
|
||||
"node": map[string]interface{}{
|
||||
"space_id": "space_123",
|
||||
"node_token": "wikcnABC",
|
||||
"obj_token": "docxXYZ",
|
||||
"obj_type": "docx",
|
||||
"node_type": "origin",
|
||||
"title": "Legacy Token Path",
|
||||
},
|
||||
},
|
||||
"msg": "success",
|
||||
},
|
||||
})
|
||||
|
||||
var flagOut bytes.Buffer
|
||||
parent := mountWikiNodeGetWithFlagOut(t, factory, &flagOut)
|
||||
parent.SetArgs([]string{
|
||||
"+node-get",
|
||||
"--token", "wikcnABC",
|
||||
"--as", "bot",
|
||||
})
|
||||
stdout.Reset()
|
||||
if err := parent.Execute(); err != nil {
|
||||
t.Fatalf("parent.Execute() error = %v", err)
|
||||
}
|
||||
|
||||
data := decodeWikiEnvelope(t, stdout)
|
||||
if data["title"] != "Legacy Token Path" {
|
||||
t.Fatalf("title = %#v, want Legacy Token Path", data["title"])
|
||||
}
|
||||
// pflag MarkDeprecated prints "Flag --token has been deprecated, use --node-token instead".
|
||||
got := flagOut.String()
|
||||
if !strings.Contains(got, "deprecated") || !strings.Contains(got, "--node-token") {
|
||||
t.Fatalf("pflag output = %q, want a deprecation warning pointing to --node-token", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestWikiNodeGetMountedRejectsConflictingTokenFlags(t *testing.T) {
|
||||
t.Setenv("LARKSUITE_CLI_CONFIG_DIR", t.TempDir())
|
||||
|
||||
// reg is unused: conflict is caught in Validate before any HTTP call.
|
||||
factory, stdout, _, _ := cmdutil.TestFactory(t, wikiTestConfig())
|
||||
|
||||
err := mountAndRunWiki(t, WikiNodeGet, []string{
|
||||
"+node-get",
|
||||
"--node-token", "wikcnNEW",
|
||||
"--token", "wikcnOLD",
|
||||
"--as", "bot",
|
||||
}, factory, stdout)
|
||||
if err == nil || !strings.Contains(err.Error(), "both set with different values") {
|
||||
t.Fatalf("expected conflict error, got %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestWikiNodeGetFallsBackToCreatorWhenNodeCreatorMissing(t *testing.T) {
|
||||
t.Setenv("LARKSUITE_CLI_CONFIG_DIR", t.TempDir())
|
||||
|
||||
@@ -272,7 +468,7 @@ func TestWikiNodeGetFallsBackToCreatorWhenNodeCreatorMissing(t *testing.T) {
|
||||
|
||||
err := mountAndRunWiki(t, WikiNodeGet, []string{
|
||||
"+node-get",
|
||||
"--token", "wikcnABC",
|
||||
"--node-token", "wikcnABC",
|
||||
"--as", "bot",
|
||||
}, factory, stdout)
|
||||
if err != nil {
|
||||
@@ -311,7 +507,7 @@ func TestWikiNodeGetRejectsSpaceIDMismatch(t *testing.T) {
|
||||
|
||||
err := mountAndRunWiki(t, WikiNodeGet, []string{
|
||||
"+node-get",
|
||||
"--token", "wikcnABC",
|
||||
"--node-token", "wikcnABC",
|
||||
"--space-id", "space_expected",
|
||||
"--as", "bot",
|
||||
}, factory, stdout)
|
||||
|
||||
@@ -40,7 +40,7 @@ metadata:
|
||||
|
||||
1. 先阅读 [`../lark-shared/SKILL.md`](../lark-shared/SKILL.md)。
|
||||
2. Base 业务命令仅使用 `lark-cli base +...` 形式的 shortcut 命令。
|
||||
3. 如果输入是 Wiki 链接或 Wiki token,并且用户想读取/操作其中的 Base,先执行 `lark-cli wiki +node-get --token <wiki_url_or_token>`;当返回 `data.obj_type=bitable` 时,把 `data.obj_token` 当作 `--base-token`。不要把 URL 里的 `/wiki/{token}` 当成 Base token。
|
||||
3. 如果输入是 Wiki 链接或 Wiki token,并且用户想读取/操作其中的 Base,先执行 `lark-cli wiki +node-get --node-token <wiki_url_or_token>`;当返回 `data.obj_type=bitable` 时,把 `data.obj_token` 当作 `--base-token`。不要把 URL 里的 `/wiki/{token}` 当成 Base token。(旧的 `--token` flag 仍可用,但已 deprecated,会在 stderr 打印迁移提示。)
|
||||
4. 定位到命令后,先读该命令对应的 reference,再执行命令。
|
||||
5. 如果用户要把本地 Excel / CSV / `.base` 快照导入成 Base / 多维表格 / bitable,第一步不是 `base`,而是 `lark-cli drive +import --type bitable`;导入完成后再回到 `lark-cli base +...` 做表内操作。
|
||||
6. 不要在 Base 场景改走 `lark-cli api /open-apis/bitable/v1/...`。
|
||||
@@ -266,7 +266,7 @@ metadata:
|
||||
Wiki Base fast path:
|
||||
|
||||
```bash
|
||||
BASE_TOKEN="$(lark-cli wiki +node-get --as user --token "<wiki_url_or_token>" --jq '.data | select(.obj_type == "bitable") | .obj_token')"
|
||||
BASE_TOKEN="$(lark-cli wiki +node-get --as user --node-token "<wiki_url_or_token>" --jq '.data | select(.obj_type == "bitable") | .obj_token')"
|
||||
```
|
||||
|
||||
| `lark-cli wiki +node-get` 返回的 `data.obj_type` | 后续路线 | 说明 |
|
||||
@@ -352,7 +352,7 @@ lark-cli auth login --domain base
|
||||
| `1254066` | 人员字段错误 | `[{ "id": "ou_xxx" }]` |
|
||||
| `1254045` | 字段名不存在 | 检查字段名(含空格、大小写) |
|
||||
| `1254015` | 字段值类型不匹配 | 先 `+field-list`,再按类型构造 |
|
||||
| `param baseToken is invalid` / `base_token invalid` | 把 wiki token、workspace token 或其他 token 当成了 `base_token` | 如果输入来自 `/wiki/...`,先用 `lark-cli wiki +node-get --token <wiki_url_or_token>` 取真实 `data.obj_token`;当 `data.obj_type=bitable` 时,用 `data.obj_token` 作为 `--base-token` 重试,不要改走 `bitable/v1` |
|
||||
| `param baseToken is invalid` / `base_token invalid` | 把 wiki token、workspace token 或其他 token 当成了 `base_token` | 如果输入来自 `/wiki/...`,先用 `lark-cli wiki +node-get --node-token <wiki_url_or_token>` 取真实 `data.obj_token`;当 `data.obj_type=bitable` 时,用 `data.obj_token` 作为 `--base-token` 重试,不要改走 `bitable/v1` |
|
||||
| `not found` 且用户给的是 wiki 链接 | 常见于把 wiki token 当成 base token | 优先回退检查 wiki 解析,而不是改走 `bitable/v1` |
|
||||
| formula / lookup 创建失败 | 指南未读或结构不合法 | 先读 `formula-field-guide.md` / `lookup-field-guide.md`,再按 guide 重建请求 |
|
||||
| `ignored_fields` / `READONLY` | 只读字段被当成可写字段,常见于系统字段、formula、lookup | 移除只读字段,只写存储字段;计算结果交给 formula / lookup / 系统字段自动产出 |
|
||||
|
||||
@@ -6,7 +6,7 @@ Get a wiki node's details by `node_token`, `obj_token`, or a Lark URL. Use this
|
||||
|
||||
```bash
|
||||
lark-cli wiki +node-get \
|
||||
--token <node_token | obj_token | Lark URL> \
|
||||
--node-token <node_token | obj_token | Lark URL> \
|
||||
[--obj-type <doc|docx|sheet|bitable|mindnote|slides|file>] \
|
||||
[--space-id <space_id>] \
|
||||
[--format json|pretty|table|csv|ndjson] \
|
||||
@@ -17,8 +17,9 @@ lark-cli wiki +node-get \
|
||||
|
||||
| Flag | Type | Required | Default | Description |
|
||||
|------|------|----------|---------|-------------|
|
||||
| `--token` | string | **Yes** | — | `node_token`, cloud-doc `obj_token`, or a Lark URL embedding one (e.g. `https://feishu.cn/wiki/<token>` or `https://feishu.cn/docx/<token>`) |
|
||||
| `--obj-type` | enum | No | — | Needed when `--token` is a raw `obj_token`; auto-inferred from the URL path. Not allowed when the token looks like a `node_token` (`wik...`) |
|
||||
| `--node-token` | string | **Yes** | — | `node_token`, cloud-doc `obj_token`, or a Lark URL embedding one (e.g. `https://feishu.cn/wiki/<token>` or `https://feishu.cn/docx/<token>`). Matches the `--node-token` naming used by sibling `+node-delete` / `+node-copy` / `+move`. |
|
||||
| `--token` | string | — (deprecated) | — | Deprecated original name; still accepted for backward compatibility but emits a `Flag --token has been deprecated, use --node-token instead` warning on stderr. New scripts should use `--node-token`. |
|
||||
| `--obj-type` | enum | No | — | Needed when `--node-token` is a raw `obj_token`; auto-inferred from the URL path. Not allowed when the token looks like a `node_token` (`wik...`) |
|
||||
| `--space-id` | string | No | — | Optional cross-check: fail if the resolved node does not live in this space |
|
||||
| `--format` | enum | No | `json` | `json` / `pretty` / `table` / `csv` / `ndjson` |
|
||||
| `--as` | enum | No | `auto` | Identity `user`/`bot`; wiki is user-centric → pass `--as user` |
|
||||
|
||||
Reference in New Issue
Block a user