When AutoGrantCurrentUserDrivePermission encounters lark code 99991672/99991679,
extract permission_violations from the underlying ExitError and surface
lark_code, required_scope, and console_url on the result map. Override the
generic fallback hint with one pointing at the developer console — the
concrete next step a user can take.
Refactor extractRequiredScopes / SelectRecommendedScope wrapping / console URL
construction out of cmd/root.go into internal/registry/scope_hint.go so both
the top-level enrichPermissionError path and the best-effort sub-call path in
shortcuts/common share one implementation.
Change-Id: Ida63ed160d1167b7961b6faac5c2cf9b7f971c65
- description: switch from trigger-word enumeration to a general
principle (any HTML artifact intended to be independently accessible
falls under this skill; defer the deploy-vs-demo decision to the
skill body)
- surface apps +access-scope-get in prerequisites list and Shortcuts
table so agents can find the read side of access-scope
- add "writing HTML hard constraints" section: index.html is the
required entry filename, --path cannot equal cwd (both are CLI-side
hard rejects that previously only lived in the html-publish ref)
* feat(sidecar): support multi-client identity isolation in server-demo
When multiple CLI sandbox environments share a single sidecar instance,
user tokens (UAT) were not isolated -- the last user to log in would
overwrite previous users' tokens, causing identity cross-contamination.
This change introduces per-client HMAC key isolation:
- Each client gets a unique client-*.key file for data-plane HMAC signing,
allowing the sidecar to identify request origin.
- A new auth_bridge.go handles management endpoints (login/poll/status)
with explicit client-to-feishuOpenId binding.
- User token resolution is strictly bound to the matched client -- no
fallback to other users' tokens when a client has no mapping.
- The shared proxy.key is reused across restarts instead of regenerated,
fixing a race condition when multiple sidecar instances start together.
Wire protocol (sidecar package) is unchanged; existing single-client
deployments are fully backward compatible.
Signed-off-by: Gao Yang <grany@yeah.net> (topwin.tech)
* fix(sidecar): address review feedback on filesystem and safety
- Replace os.ReadFile/WriteFile/ReadDir with vfs.* equivalents for test
mockability, consistent with project coding guidelines.
- Limit auth bridge request body to 64KB to prevent memory exhaustion.
- Log errors in saveUserMap instead of silently discarding them.
- Reject client keys that collide with the shared proxy key.
- Reject duplicate client keys instead of silently overwriting.
Signed-off-by: Gao Yang <grany@yeah.net> (topwin.tech)
* refactor(sidecar): remove workspace-specific naming and backward compat
- parseClientID: only accept "client_id" field, remove legacy fallback
- loadClientKeys: scan all *.key (excluding proxy.key), no prefix required
- Remove legacy file migration logic in newAuthBridge
- Update flag description to reflect generic key scanning
Signed-off-by: Gao Yang <grany@yeah.net> (topwin.tech)
* refactor(sidecar): extract multi-tenant demo and add unit tests
Address review feedback from sang-neo03:
1. Extract multi-client code into sidecar/server-multi-tenant-demo/,
keeping server-demo as the minimal single-tenant reference.
2. Add unit tests for the isolation guarantee:
- loadClientKeys: shared-key collision and duplicate keyHex are skipped
- verifyWithClientKeys: correct client matched, unknown key rejected
- loadUserMap/saveUserMap: round-trip persistence across restart
3. Cross-link READMEs between server-demo and server-multi-tenant-demo.
Signed-off-by: Gao Yang <grany@yeah.net> (topwin.tech)
* docs(sidecar): rewrite multi-tenant demo README with problem statement and client guide
- Explain the multi-app credential isolation problem (app_secret must
not be exposed to client environments)
- Document typical deployment topology with multiple sidecar instances
- Add complete client setup guide: env vars, multi-app switching, login
flow, and end-to-end workflow example
- Document design decisions and management endpoint details
Signed-off-by: Gao Yang <grany@yeah.net> (topwin.tech)
* fix(sidecar): address CodeRabbit review feedback on tests and docs
- Make TestProxyHandler_AcceptsAllowedAuthHeaders fully offline by using
httptest.NewTLSServer instead of depending on open.feishu.cn
- Isolate TestRun_RejectsSelfProxy config state with t.Setenv and temp dirs
- Check os.MkdirAll error in test fixture setup
- Add language identifiers to fenced code blocks (MD040)
- Validate user-supplied CLI paths with validate.SafeInputPath
Signed-off-by: Gao Yang <grany@yeah.net> (topwin.tech)
---------
Signed-off-by: Gao Yang <grany@yeah.net> (topwin.tech)
- Bump version to 1.0.38
- Update CHANGELOG.md with the apps brand gating change since v1.0.37
- Backfill the [v1.0.38] link reference at the bottom of CHANGELOG.md
Change-Id: I6fd0d1243e2219a1eaa1fae5fae4ff6d8de361da
* feat(apps): gate apps domain off on Lark brand
The Miaoda apps OpenAPI is Feishu-only. On Lark brand:
- shortcut subtree is registered + hidden, RunE returns a structured
brand-restriction error so users see a clear message instead of
cobra's generic "unknown command"
- auth login `--domain apps` is treated as unknown; `--domain all`
skips apps; help text omits it
- scope collection skips apps shortcuts so spark:* scopes are never
requested
The leaf-stub pattern mirrors internal/cmdpolicy/apply.go::installDenyStub
(DisableFlagParsing + ArbitraryArgs + leaf-level PersistentPreRunE
override) so cobra can't short-circuit the stub with a missing-flag or
parent-PreRunE detour.
Change-Id: I5817e87ae6fedabdb5faf05d0d32ea988f7effc9
- +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
strings.Fields("") returns an empty slice, causing --scope "" to bypass
validation and return ok: true. Replace the false-positive success path
with an ErrValidation error so callers correctly detect the invalid input.
Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
docs +search is in maintenance and will be removed; cloud-space resource
discovery is consolidated onto drive +search. Two related doc/help fixes:
1. Redirect guidance: docs +search -> drive +search
- skill-template/domains/{doc,sheets}.md
- lark-base/SKILL.md: --filter '{"doc_types":["BITABLE"]}' -> --doc-types bitable
- lark-sheets/SKILL.md: body + frontmatter description, add drive-search ref link
Same server API, equivalent capability; only flattens the entry from
nested --filter JSON to flags. reference links repointed to lark-drive.
2. Fix creator_ids/--mine semantic: creator -> owner
The server matches creator_ids (incl. --mine / --creator-ids) by owner
(document owner), not original creator, despite the OpenAPI field name.
- shortcuts/drive/drive_search.go: --help Desc and Tip
- lark-drive/references/lark-drive-search.md: identity section, params, rules, examples
- lark-drive/SKILL.md: top-level guidance
- lark-doc/references/lark-doc-search.md: creator_ids usage note (now self-consistent)
Wire field name creator_ids kept (aligned with the server).
Docs/help strings only, no logic change; gofmt / go vet / package build pass.
Change-Id: If3ebf5a247b7e38b58050c677dc888a310f1c6b6
* feat(doc): warn before overwrite when document contains whiteboard or file blocks
Before executing an overwrite in v1 mode, pre-fetch the current document
and scan the Markdown for <whiteboard> and <file> resource blocks. If any
are found, print a warning to stderr listing the counts and suggesting the
user take a backup with `docs +fetch` first.
Overwrite replaces the entire document and cannot reconstruct these blocks
from Markdown; previously the data was lost with no indication to the caller.
The check is best-effort: a failed pre-fetch silently skips the guard rather
than blocking the overwrite.
* test(doc): add validateSelectionByTitleV1 tests and drop redundant empty-md guard in warnOverwriteResourceBlocks
* fix(doc): use regex for resource block detection, add latency/coverage comments, document skip_task_detail purpose
Switch `drive +export --file-extension markdown` from the legacy V1
GET /open-apis/docs/v1/content API to the V2
POST /open-apis/docs_ai/v1/documents/{token}/fetch API for
higher-quality Lark-flavored Markdown output.
- Update DryRun and Execute paths to use V2 endpoint with JSON body
- Add docx:document:readonly scope for the new API
- Validate V2 response structure (fail fast on missing document/content)
- Encode token in URL path via validate.EncodePathSegment
- Update unit tests and add V2 response validation error path tests
- Add E2E dry-run test for markdown export path
- Update skill documentation
* fix(identitydiag): harden verify path and tighten status semantics
Follow-ups to #957:
- bound bot/user verify calls with a 10s timeout (mirrors the doctor
endpoint probe) so a hanging server cannot wedge `auth status --verify`
or `doctor`
- return StatusNotConfigured (not StatusMissing) when the user-identity
path is blocked by missing app config, matching the bot side
- surface the `{code, msg}` envelope on bot-info HTTP 4xx responses so
callers see why bot auth was rejected, not just the bare HTTP code
- introduce identity{User,Bot,None} constants in cmd/auth/status.go and
use the exported StatusMessage() in the human-readable note instead of
raw status codes like "not_configured"
- collapse the duplicated verify-failed identity construction in the
user path into a local helper
- cover the new failure paths with unit tests (HTTP 4xx with envelope,
business error code, user server-rejected, expired user token,
strict-mode user-only, missing app config for user)
Change-Id: I581348a65f15b1452a6f48a3e3245d09257314ac
* fix(identitydiag): decode bot/v3/info from "bot" field, not "data"
`/open-apis/bot/v3/info` returns `{code, msg, bot: {...}}` — the bot
payload is under `bot`, not `data` as the newer Lark API convention
would suggest. The decoder was reading from a non-existent `data`
field, so `envelope.Data.OpenID` was always empty and every successful
verify was reported as `Bot identity: verify failed: open_id is empty`.
The pre-existing test mocks used `{"data": {...}}` matching the buggy
decoder, so unit tests passed while production reads of every Lark
account failed verification.
Fix:
- change the JSON tag on the envelope from `json:"data"` to `json:"bot"`
- update mocks in identitydiag and cmd/auth/status tests to emit `bot`
Verified locally: `lark-cli doctor` now reports `bot_identity: pass`
for both a normal account and a bot-only profile, restoring the
behavior that #957 set out to deliver.
Change-Id: Ib26dfdd5a0cc37d2d62537ae2bf5e854e67cb83c
* fix(shortcuts/common): decode bot/v3/info from "bot" field, not "data"
Same schema bug as the one fixed in identitydiag — `RuntimeContext.
fetchBotInfo` reads from a non-existent "data" key, so every successful
call would report "open_id is empty" once a caller starts depending on
it.
There are no production callers of `RuntimeContext.BotInfo()` yet
(only tests + the `TestNewRuntimeContextWithBotInfo` helper), so this
bug is dormant — but the pre-existing tests pass with the same wrong
schema in their mocks, so the first real consumer would silently break.
Fix: tag `json:"data"` → `json:"bot"` plus aligning the four mock
fixtures in runner_botinfo_test.go. The Go field name `Data` is kept
to minimize the diff; only the JSON contract is corrected.
Change-Id: I11e1e871603e5349f8df29b1d58e35d07b628dfd
* feat(drive): add +inspect shortcut for document URL inspection with wiki unwrapping
Implements #662: `lark-cli drive +inspect --url <url>` inspects any
Lark/Feishu document URL to get its type, title, and canonical token,
with automatic wiki URL unwrapping via get_node API.
- Add ParseResourceURL (inverse of BuildResourceURL) in common
- Extract FetchDriveMetaTitle as public shared helper
- Add drive +inspect shortcut with wiki unwrapping support
- Add skill reference docs and update SKILL.md
- Dry-run E2E tests for docx URL, wiki URL, and bare token
* refactor: move host validation from ParseResourceURL to +inspect
ParseResourceURL is a general-purpose URL parser that should not
hardcode domain lists — future Lark domains would silently break.
Move isLarkHost/larkHostSuffixes to drive_inspect.go where host
validation is a business decision of the +inspect command.
Add E2E test for non-Lark host with Lark-like path.
* refactor: remove host validation from +inspect
Lark supports custom enterprise domains, so a hardcoded suffix list
can never be exhaustive and would falsely reject valid URLs.
Path-based matching in ParseResourceURL is sufficient; invalid URLs
will fail naturally at the API call stage.
* 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
* feat(mail): bot+mailbox=me validation and dynamic --as help tests
Add validateBotMailboxNotMe helper to shortcuts/mail/helpers.go and
wire it as a Validate callback into +message, +messages, +thread and
+triage, so bot identity combined with the default --mailbox me is
rejected early with a clear fixup hint instead of a late opaque API
error.
The --as help text was already dynamic via AddShortcutIdentityFlag;
add TC-10/TC-11 tests in internal/cmdutil/identity_flag_test.go to
pin that behaviour, and TC-1 through TC-9 in
shortcuts/mail/mail_shortcut_validation_test.go to cover the new
Validate callbacks.
+watch is excluded: its AuthTypes is ["user"], so bot is never valid.
sprint: S2
* test(cmdutil): add Hidden and DefValue assertions to identity flag tests
* fix(mail): add bot+mailbox=me validation to +template-create and +template-update
* fix(mail): add bot+mailbox=me validation to +template-update
* fix(mail): gofmt mail_template_create.go
* fix(mail): gofmt mail_template_update.go
* fix(mail): skip bot+mailbox=me check for print-patch-template local path
Add a Priority field to DraftProjection populated from the EML header pair
X-Cli-Priority (CLI/OAPI primary) → X-Priority (RFC fallback for IMAP-回灌
historical drafts), with case-insensitive lookup via the existing
headerValue helper and a local mapping table aligned with the backend
gopkg/mail_priority.PriorityValueToType vocabulary. When neither header is
present (the symmetric read of --set-priority normal=remove_header) the
projection emits "unknown" so agents have a stable read-side surface.
Append one notes entry to buildDraftEditPatchTemplate documenting the
--set-priority flag and the X-Cli-Priority translation contract.
The write-side (--set-priority flag, parsePriority helper, translation
branch in mail_draft_edit.go, EML header target) is unchanged — already
shipped on master.
sprint: S4
Test files legitimately need to construct dangerous Unicode inputs
(RLO, ZWSP, BOM, etc.) to verify validation logic rejects them.
bidichk treats decoded \u escape literals as Trojan Source risks,
which is a false positive for intentional test data.
Change-Id: I555028a992ab008da16129eb41075c333d0099b8
- +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