Commit Graph

7 Commits

Author SHA1 Message Date
evandance
99e314fe0b feat(errs): typed envelope contract for auth-domain errors (#1135)
Every failure on the authentication, authorization, and configuration
path now surfaces as a typed structured error instead of an ad-hoc
envelope. Users and scripts that consume CLI output get:

  - a fixed nine-category taxonomy on the wire, each mapped to a
    stable shell exit code (authentication/authorization/config = 3,
    network = 4, internal = 5, policy = 6, confirmation = 10)
  - identity-aware detail fields (missing_scopes, requested_scopes,
    granted_scopes, console_url, log_id, retryable, hint) carried
    uniformly on the envelope
  - a single canonical policy envelope at exit 6; the legacy
    auth_error carve-out is retired
  - per-subtype canonical message + hint that preserves Lark's
    diagnostic phrasing and routes recovery to the right actor:
    app developer (app_scope_not_applied), user (missing_scope,
    token_scope_insufficient, user_unauthorized), or tenant admin
    (app_unavailable, app_disabled)
  - wrong app credentials classify as config/invalid_client whether
    surfaced by the Open API endpoint (99991543) or the tenant
    access-token mint endpoint (10003 / 10014), instead of
    collapsing to a transport error or api/unknown
  - local shortcut scope preflight emits the same
    authorization/missing_scope envelope (identity + deterministic
    missing-scope set) used by the post-call permission path, so AI
    consumers read the same structured shape from precheck and from
    server-returned permission denial
  - streaming download/upload failures keep the same network subtype
    split (timeout / TLS / DNS / transport) as the non-stream path
    instead of collapsing every cause to a generic transport failure
  - console_url is carried only on the bot-perspective
    app_scope_not_applied envelope (where the recovery action is
    "developer applies the scope at the developer console"); the
    user-perspective missing_scope envelope drops the field, since
    the only actionable user recovery is `lark-cli auth login --scope`
    and pointing an end user at a console they cannot modify is
    misleading
  - bind workflows (Hermes / OpenClaw / lark-channel) flatten dynamic
    Type tags to wire 'config' with the original module name kept
    as a metric label

All 10 typed errors are cause-bearing, nil-safe on .Error() and
.Unwrap(), and defensively clone slice setter inputs. Four lint
rules (CheckNilSafeError / CheckBuilderImmutable / CheckUnwrapSymmetry
/ CheckBuildAPIErrorArms) lock these invariants on migrated paths.
2026-05-30 19:08:41 +08:00
raistlin042
e93e2a98e1 feat(apps): replace +html-publish cwd hard-reject with credential-file scan (#1072)
* feat(apps): replace +html-publish cwd hard-reject with credential-file scan

The previous --path == "." block was a coarse heuristic: it caught the
common foot-gun of publishing a repo root, but also rejected legitimate
clean cwds, and let a ./dist with a forgotten .env ship the secret
through anyway (the sensitive-paths scanner was advisory and never ran
on the Execute path).

Move the gate from path shape to path content:

- Validate now walks --path candidates and rejects publishes that
  include well-known credential files (.env / .env.* / .npmrc / .netrc
  / .git-credentials / .aws/credentials / .gcloud/credentials* /
  .docker/config.json / .kube/config). Living in Validate (not DryRun)
  means dry-run returns non-zero on hit too, so the dry-run preview
  matches Execute.
- Narrow the credential pattern set. .git/, SSH private keys, *.pem
  and *.key are out of scope -- they're not env-token files and the
  false-positive rate (public certs, docs about key formats) is high.
- Add --allow-sensitive as the escape hatch for legitimate cases
  (e.g. a docs site shipping .env.example on purpose). DryRun surfaces
  the waived list in sensitive_waived so the caller can relay it.
- Drop the cwd defense-in-depth in runHTMLPublish. A clean cwd is now
  a valid publish target.

The lark-apps skill and the html-publish reference are updated to
describe the new gate, the override flag, and the patterns now
explicitly out of scope.

* feat(apps): drop .gcloud/* from credential-file scan

The .gcloud/credentials pattern matched a non-existent path: gcloud's
actual config dir is ~/.config/gcloud/ (XDG-based), and the real
credential files there are credentials.db / access_tokens.db /
application_default_credentials.json -- none of which would land under
a .gcloud/ segment in a publish payload.

Drop the rule rather than fix it: the realistic gcloud foot-gun would
require recognizing the .config/gcloud/* tree by file basename, which
is a broader change than the targeted env/cred scan in this PR. The
remaining 7 patterns (.env / .env.* / .npmrc / .netrc /
.git-credentials / .aws/credentials / .docker/config.json /
.kube/config) cover the common Node/Python/CLI-tooling foot-guns.

* fix(apps): close credential-scan bypass when --path is the parent dir itself

isSensitiveRelPath anchors cloud-SDK matchers on adjacent parent/file
segments (.aws/credentials, .docker/config.json, .kube/config), but
walker strips that parent via filepath.Rel when --path is the conventional
parent dir (e.g. ./.aws), yielding a bare RelPath="credentials" that
slipped through silently. Same bypass for the single-file form
--path ./.aws/credentials (walker sets RelPath = Base(rootPath)).

Wrap the scan in isSensitiveCandidate: keep the fast RelPath scan, and
on miss fall back to filepath.Abs(AbsPath) so the parent segment is
visible again. isSensitiveRelPath itself is unchanged; existing tests
still pin its pure-function contract.

* fix(apps): drop filepath.Abs from sensitive scan to satisfy forbidigo lint

The previous fix called filepath.Abs(c.AbsPath) — banned by the repo's
forbidigo rule because shortcuts must not reach into the filesystem for
path resolution.

Reframe the same fix without fs access: re-prepend the root's basename
(or, for the single-file form, the parent dir's basename of rootPath)
to RelPath and re-scan only the parent-anchored credential pairs
(.aws/credentials, .docker/config.json, .kube/config). Leaf matchers
(.env / .npmrc / ...) stay scoped to RelPath — incidentally closing a
latent false-positive where --path /home/alice/.env/dist would have
flagged every file under it just because .env appeared in the
absolute path.
2026-05-25 23:24:40 +08:00
raistlin042
0dda56914d fix(apps): read app object from data.app for +create and +update (#1087)
* fix(apps): read app object from data.app for +create and +update

The Miaoda OpenAPI returns the application object nested under
data.app for both POST /apps and PATCH /apps/{appId}. The CLI text
helper was reading common.GetString(data, "app_id"), which yields an
empty string against the wire format -- so `lark-cli apps +create
--format pretty` printed `created: ` with no ID.

Navigate the new nested path via GetString(data, "app", "app_id") for
both create and update. Update unit-test mocks to wrap the response
under `app`. Refresh the lark-apps skill references (example response
shape + jq paths) so agents reading them follow the right path.

Wire format is passed through to the user's JSON envelope untouched
-- no unwrapping in CLI. Consumers reading the response should use
.data.app.app_id.

The GET /apps list endpoint is unchanged: per the design doc its
items[] are flat objects, no wrapper.

* docs(apps): add required --app-type HTML to scenario 2 snippet

The "用户没有 app_id" snippet in lark-apps-html-publish.md was missing
the required --app-type flag, so copy-pasting it triggered Validate
("--app-type is required") and left $APP empty -- the following
+html-publish then failed with --app-id "". Bring the snippet in line
with every other apps +create example in the skill.

* docs(apps): simplify auth-recovery rule to error.type == missing_scope

Every apps shortcut declares Scopes, so the precheck path in
shortcuts/common/runner.go:825 is always the one that fires on scope
violations and the envelope's error.type is the stable discriminator.
Drop the keyword-sniffing of error.hint, the chain explanation, and the
bot caveat — they all reduce to one boolean: error.type == "missing_scope"
→ run `lark-cli auth login --domain apps`.

Also collapse the corresponding bullet in 快速决策 to point at this rule.
2026-05-25 23:16:30 +08:00
fangshuyu-768
282c27784d docs(skills): add 云盘/云存储 alias alongside 云空间 for agent clarity (#1073) 2026-05-25 17:15:50 +08:00
raistlin042
d5d2fee848 chore(apps): refine lark-apps skill description and surface (#1040)
- 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)
2026-05-22 16:39:36 +08:00
liangshuo-1
daba3c9afd feat(apps): gate apps domain off on Lark brand (#1025)
* 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
2026-05-22 03:03:41 +08:00
raistlin042
6cea6c9af0 feat(apps): add miaoda apps domain (6 shortcuts + dry-run e2e) (#1002)
Adds the apps domain to lark-cli for managing Miaoda (妙搭) applications: 6 shortcuts covering the full lifecycle (+create / +update / +list / +access-scope-set / +access-scope-get / +html-publish). Aligned with the OAPI v2 design — app_type enum (currently HTML), string scope enum (All / Tenant / Range), cursor pagination, in-memory tar.gz multipart publish flow. Namespace registered at /open-apis/spark/v1/ with spark:app.* scopes.

---------

Co-authored-by: wangjiangwen-gif <286006750+wangjiangwen-gif@users.noreply.github.com>
2026-05-21 20:30:42 +08:00