When creating wiki nodes under the same parent concurrently, the API
returns error code 131009 (lock contention) ~5-15% of the time. This
adds automatic retry with exponential backoff (250ms, 500ms; max 2
retries) so callers no longer need to implement retry logic themselves.
- Retry loop in runWikiNodeCreate: only retries on code 131009, respects
context cancellation, prints progress to stderr
- wrapWikiNodeCreateRetryError preserves Err/Raw/Detail.Code in ExitError
- 6 unit tests covering retry success, exhaustion, non-contention error,
single-retry success, context cancellation, no-retry on success
- 8 dry-run E2E tests for wiki +node-create request shape and validation
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
- +member-add: wrap POST /spaces/{id}/members; --member-type / --member-role
enums, optional --need-notification query (omitted entirely when the flag
is unset, instead of forcing need_notification=false), my_library
resolution under --as user, flattened single-member output
- +member-remove: wrap DELETE /spaces/{id}/members/{member_id}; surfaces the
required member_type + member_role body the API expects, my_library
resolution, fallback to echoing the caller's inputs when the API omits
the member echo
- +member-list: wrap GET /spaces/{id}/members; reuses the +space-list /
+node-list pagination contract (single page by default, --page-all walks
every page capped by --page-limit, --page-token resumes a cursor)
- All three reject bot identity + my_library upfront with a clear hint and
declare the narrowest scope the API accepts (wiki:member:create /
wiki:member:update / wiki:member:retrieve) so tokens carrying only the
narrow scope are not false-rejected by the exact-string preflight
- skill docs: reference pages for the three new shortcuts + SKILL.md
shortcuts table; switch the membership flow guidance from raw
`wiki members create` to the new +member-add path
Change-Id: I158a86aa7f00bb7cecc7a4e99346f3fb151b3c09
When a resource is created with bot identity, the CLI attempts to
auto-grant full_access to the current user. If the user open_id is
missing or the grant API call fails, the result was only written to
the JSON permission_grant field and easily overlooked.
Changes:
- Add stderr warnings when auto-grant is skipped or fails
- Add 'hint' field to permission_grant JSON output with failure reason
and actionable next step (e.g. auth login, check scope, retry)
- Add end-to-end skipped/failed tests across all affected shortcuts
(doc, drive, sheets, slides, wiki, markdown, base)
Closes#963
* fix(wiki): surface real node url for +node-create / +node-copy
The create-node and copy-node OpenAPI responses carry a real `url`
field (present in practice though absent from the documented schema).
Both shortcuts ignored it: +node-create synthesized a link via
BuildResourceURL, and +node-copy emitted no URL at all.
Parse `url` into the shared wikiNodeRecord and add a wikiNodeURL helper
that prefers the response url, falling back to BuildResourceURL only
when it is blank. Wire +node-create and +node-copy to the helper so
both surface the canonical link when available.
Change-Id: I0ca5f91b02c24e81d083793e6a8e4f8c966aeec3
* refactor(wiki): move wikiNodeURL to shared wiki_helpers.go
The helper is consumed by both +node-create and +node-copy, so its
placement should reflect the broader usage rather than living in the
create command's file. Pure move; no behavior change.
Change-Id: I9990c12da042f631fe2519911c6a9d663fd5c22b
- +node-get: wrap wiki.spaces.get_node; accepts node_token, obj_token,
or a Lark URL (URL path auto-infers obj_type); formatted output with
creator / updated_at. No synthesized url — get_node returns none and a
BuildResourceURL fallback is a non-canonical link that misleads in a
read/confirm command (sibling read shortcuts omit it too)
- +node-delete: wrap space.node delete; high-risk-write (--yes gated),
async delete-node task polling, auto-resolves space_id via get_node
when --space-id omitted, actionable hints for codes 131011 / 131003.
The delete-node task result lives under the gateway's generic
`simple_task_result` key (NOT `delete_node_result`)
- +space-create: wrap spaces.create; user-only identity, --name
required (no empty-name spaces), flattened space output, no url
- factor the shared wiki async-task poll loop into wiki_async_task.go;
preserve upstream Lark Detail.Code on poll exhaustion (no longer
rebuilt via lossy ErrWithHint)
- drive +task_result: add wiki_delete_node scenario so +node-delete's
async-timeout next_command actually resolves
- skill docs: reference pages for the 3 new shortcuts + SKILL.md
shortcuts table (no raw nodes.delete API exists — it's shortcut-only,
so it is intentionally absent from API Resources / permission table);
drop the circular TestWikiShortcutsIncludeAllCommands change-detector
Change-Id: I316f78290cec5bc50f80d629173e3bf2a35dd005
Introduce three new wiki shortcuts that wrap the corresponding raw APIs
with structured flags, formatted output, my_library alias handling, and
unified envelope shape, replacing the bare `lark-cli wiki spaces list`
/ `wiki nodes list` / `wiki nodes copy` flows for the common cases.
Shortcuts
- wiki +space-list (read, scopes: wiki:space:retrieve):
lists wiki spaces. Default fetches a single page; --page-all walks
every page capped by --page-limit (default 10, 0 = unlimited).
Supports --page-size / --page-token / --format json|pretty|table|csv|ndjson.
Output: {spaces, has_more, page_token} + Meta.Count. Pretty mode
distinguishes "no spaces" from "empty page with has_more" and hints
the caller to resume.
- wiki +node-list (read, scopes: wiki:node:retrieve):
lists nodes in a space or under a parent. Same pagination + format
story as +space-list. Accepts the my_library alias for --space-id
with --as user (resolved via a shared resolveMyLibrarySpaceID helper
extracted from +node-create); rejects my_library upfront for --as bot.
- wiki +node-copy (high-risk-write, scopes: wiki:node:copy):
copies a node into a target space or parent. --target-space-id and
--target-parent-node-token are mutually exclusive. Risk is marked
high-risk-write to match the upstream API's danger: true flag, so the
framework requires --yes. Source is preserved; subtree is copied.
Both list shortcuts pick the narrowest scope the upstream API accepts.
The framework's preflight (internal/auth/scope.go MissingScopes) does
exact-string scope matching, so declaring the broader wiki:wiki:readonly
form would wrongly reject tokens that carry only the per-API scope —
which the API itself accepts — and emit a misleading missing-scope hint.
Shared changes
- shortcuts/wiki/wiki_node_create.go: factor out resolveMyLibrarySpaceID
so +node-list and +node-create share one my_library resolution path.
- shortcuts/wiki/shortcuts.go: register the three new shortcuts.
- skills/lark-wiki/SKILL.md and references/lark-wiki-{space,node-list,
node-copy}.md: documentation for the new shortcuts.
Tooling
- scripts/check-doc-tokens.sh + Makefile gitleaks target:
pre-commit check that scans skill reference docs for realistic-looking
Lark token values without the _EXAMPLE_TOKEN placeholder convention,
preventing gitleaks false positives.
- .gitleaks.toml: allowlist tuning.
- .gitignore: ignore .tmp/.
Tests
- shortcuts/wiki/wiki_list_copy_test.go: unit tests covering registry
membership, declared-narrow-scope pinning, flag validation (page-size
range, page-limit >= 0, target flag exclusivity, my_library + bot
rejection), auto-pagination merging, --page-limit truncation
surfacing next cursor, --page-token single-page mode, empty-slice
serialisation, has_more hint pretty rendering, my_library user-path
resolution, +node-copy copy-to-space / copy-to-parent + body shape,
pretty rendering, and the high-risk-write --yes gate.
- tests/cli_e2e/wiki/wiki_shortcut_workflow_test.go: live end-to-end
workflow exercising the shortcut layer against a real tenant.
Reuses an existing my_library node as a host so the test never adds
to the top-layer quota; the copy is placed under the same host node.
- tests/cli_e2e/wiki/coverage.md: shortcut coverage entries added.
Minor cleanups
- skills/lark-doc/references/lark-doc-search.md and
skills/lark-minutes/references/lark-minutes-search.md: replace
realistic-looking example ou_ tokens with _EXAMPLE_ placeholders so
scripts/check-doc-tokens.sh passes.
Change-Id: I9efb0557f477d369d7f26a09c1e154d4ab15b253
Co-authored-by: liujinkun <liujinkun@bytedance.com>
Add BuildResourceURL helper and wire it into doc/sheets/drive/base/wiki
create paths so callers always receive a clickable link, even when the
backend response (MCP degraded path or upstream OpenAPI) returns an
empty URL field. The fallback uses the brand-standard host
(www.feishu.cn / www.larksuite.com), which redirects to the tenant
domain.
Affected entries:
- docs +create v1 / v2
- sheets +create
- drive +create-folder / +import / +upload (newly exposes url)
- wiki +node-create (newly exposes url)
drive +create-shortcut is intentionally skipped because the URL form
depends on the underlying file kind, which the shortcut payload does
not carry.
Add lark-cli wiki +delete-space to delete a knowledge space via
DELETE /open-apis/wiki/v2/spaces/:space_id. When the API returns an
async task_id, the shortcut polls /open-apis/wiki/v2/tasks/:task_id
with task_type=delete_space for a bounded window and emits a
next_command pointing to drive +task_result on timeout. A new
wiki_delete_space scenario is added to drive +task_result for resuming
timed-out deletes.
Change-Id: I75da52b617c206fb778a493ffaa200adf7920a27