mirror of
https://github.com/larksuite/cli.git
synced 2026-07-07 17:45:15 +08:00
Introduces extension/platform — the in-process plugin SDK external
Go forks of lark-cli use to extend or restrict the command surface.
Plugins compile in via blank import; there is no dynamic loading
and no RPC isolation.
Public SDK (extension/platform):
- Plugin interface (Name / Version / Capabilities / Install).
- Registrar verbs: Observe, Wrap, On, Restrict.
- Hook types: Observer (side-effect, panic-safe, fires Before/After
RunE), Wrapper (middleware, may short-circuit via AbortError),
LifecycleHandler (Startup / Shutdown), Selector with nil-safe
And/Or/Not composition.
- Risk / Identity are defined string types with closed taxonomies;
ParseRisk / ParseIdentity convert raw strings with the
absent-vs-invalid distinction the engine relies on.
- Builder ergonomic constructor (NewPlugin().Observer().Wrap()
...MustBuild()) that enforces name/hookName grammar, hookName
uniqueness, and the Restrict ↔ FailClosed pairing regardless of
call order.
- Invocation is a read-only interface; the framework's concrete
invocation type lives in internal/hook so plugins cannot
fabricate denial / strict-mode / identity state. Args() returns
a defensive copy on every call so hook mutation cannot leak
into the original RunE.
- CommandDeniedError + AbortError carry structured fields for the
closed `command_denied` / `hook` envelope contract.
- ResetForTesting gated behind //go:build testing.
- README + godoc examples (Observer / Wrapper / Restrict) + two
runnable example forks (audit-observer, readonly-policy).
Host (internal/platform, internal/hook, internal/cmdpolicy):
- InstallAll: staged plugin registration with atomic commit, panic
isolation, FailOpen / FailClosed semantics, RequiredCLIVersion
semver check, single-Restrict invariant, duplicate-plugin-name
detection.
- hook.Install wraps every runnable cmd.RunE with:
Before observers (panic-safe) → denial guard → composed Wrap
chain → original RunE → After observers (always fire, even on
err). Denied commands physically bypass the Wrap chain so a
plugin Wrapper cannot suppress or rewrite a denial; observers
still see the attempt for audit.
- Recover shim around plugin Wrappers converts panics (including
the factory call) into a structured `hook` envelope with
reason_code=panic; namespacing shim attributes AbortError to
the namespaced hook name.
- cmdpolicy (renamed from internal/pruning) is the user-layer
command policy engine: walks the cobra tree, evaluates each
runnable command against a Rule's four-axis filter (Allow /
Deny / MaxRisk / Identities), produces parent-group aggregate
denials, and installs denyStubs. Rule.AllowUnannotated opts out
of the unannotated-deny gate for gradual adoption; risk_invalid
typos always deny with an edit-distance "did you mean"
suggestion.
- Strict-mode stub in cmd/prune.go composes the shared
detail.* / wrapped CommandDeniedError shape via cmdpolicy
helpers (BuildDenialError / CommandDeniedFromDenial /
DenialDetailMap), so command_denied envelopes from strict-mode
and user-layer policy carry the same closed-enum fields
(detail.layer / reason_code / policy_source). The historical
short Message + independent Hint are preserved unchanged.
- cmdpolicy/yaml: structural parsing of ~/.lark-cli/policy.yml
with KnownFields strict mode, including allow_unannotated.
- `config policy show` / `config policy validate` and the plugin
inventory diagnostic surface the resolved Rule (allow,
deny, max_risk, identities, allow_unannotated) and the hook
contributions per plugin.
Envelope contract (docs/extension/reason-codes.md):
- error.type is a closed set: command_denied, hook, plugin_install,
plugin_conflict, plugin_lifecycle.
- reason_code is a closed enum per error.type, dispatched on by
external agents and CI integrations.
- detail.layer = "policy" | "strict_mode" attributes the rejection.
Build / CI:
- Makefile unit-test / vet / coverage and ci.yml fast-gate +
unit-test + coverage now pass -tags testing so register_testing.go
is visible; ./extension/... is in the package list so the SDK's
own tests actually run.
- fmt-check and examples-build Makefile targets.
- bmatcuk/doublestar/v4 added as a direct dependency for `**` glob
matching in Rule.Allow / Rule.Deny.
Author-facing material:
- docs/extension/ (quickstart, plugin-author-guide, reason-codes)
is provided in the working tree but kept out of git tracking
per repo convention (.gitignore covers docs/).
Change-Id: I3b8ecc2923bd54c2dff19e5dce8a0855a6f9e703
74 lines
2.5 KiB
Makefile
74 lines
2.5 KiB
Makefile
# Copyright (c) 2026 Lark Technologies Pte. Ltd.
|
|
# SPDX-License-Identifier: MIT
|
|
|
|
BINARY := lark-cli
|
|
MODULE := github.com/larksuite/cli
|
|
VERSION := $(shell git describe --tags --always --dirty 2>/dev/null || echo dev)
|
|
DATE := $(shell date +%Y-%m-%d)
|
|
LDFLAGS := -s -w -X $(MODULE)/internal/build.Version=$(VERSION) -X $(MODULE)/internal/build.Date=$(DATE)
|
|
PREFIX ?= /usr/local
|
|
|
|
.PHONY: all build vet fmt-check test unit-test integration-test examples-build install uninstall clean fetch_meta gitleaks
|
|
|
|
all: test
|
|
|
|
fetch_meta:
|
|
python3 scripts/fetch_meta.py
|
|
|
|
build: fetch_meta
|
|
go build -trimpath -ldflags "$(LDFLAGS)" -o $(BINARY) .
|
|
|
|
vet: fetch_meta
|
|
go vet -tags testing ./...
|
|
|
|
# fmt-check fails when any file would be reformatted by gofmt. Keep this
|
|
# in sync with the fast-gate "Check formatting" step in CI.
|
|
fmt-check:
|
|
@unformatted=$$(gofmt -l . | grep -v '^\.claude/' || true); \
|
|
if [ -n "$$unformatted" ]; then \
|
|
echo "Unformatted Go files:"; \
|
|
echo "$$unformatted"; \
|
|
echo "Run 'gofmt -w .' and commit."; \
|
|
exit 1; \
|
|
fi
|
|
|
|
# unit-test passes -tags testing because public-SDK packages gate test-only
|
|
# helpers (e.g. platform.ResetForTesting) behind //go:build testing. The
|
|
# ./extension/... package list keeps the public plugin SDK in the default
|
|
# test matrix.
|
|
unit-test: fetch_meta
|
|
go test -tags testing -race -gcflags="all=-N -l" -count=1 \
|
|
./cmd/... ./internal/... ./shortcuts/... ./extension/...
|
|
|
|
# examples-build keeps the shipped plugin-SDK examples compilable. If this
|
|
# breaks, the plugin author guide's "go build ./..." path is broken.
|
|
examples-build:
|
|
go build ./extension/platform/examples/audit-observer
|
|
go build ./extension/platform/examples/readonly-policy
|
|
|
|
integration-test: build
|
|
go test -v -count=1 ./tests/...
|
|
|
|
test: vet fmt-check unit-test examples-build integration-test
|
|
|
|
install: build
|
|
install -d $(PREFIX)/bin
|
|
install -m755 $(BINARY) $(PREFIX)/bin/$(BINARY)
|
|
@echo "OK: $(PREFIX)/bin/$(BINARY) ($(VERSION))"
|
|
|
|
uninstall:
|
|
rm -f $(PREFIX)/bin/$(BINARY)
|
|
|
|
clean:
|
|
rm -f $(BINARY)
|
|
|
|
# Run secret-leak checks locally before pushing.
|
|
# Step 1: check-doc-tokens catches realistic-looking example tokens in reference
|
|
# docs and asks you to use _EXAMPLE_TOKEN placeholders instead.
|
|
# Step 2: gitleaks scans the full repo for real leaked secrets.
|
|
# Install gitleaks: https://github.com/gitleaks/gitleaks#installing
|
|
gitleaks:
|
|
@bash scripts/check-doc-tokens.sh
|
|
@command -v gitleaks >/dev/null 2>&1 || { echo "gitleaks not found. Install: brew install gitleaks"; exit 1; }
|
|
gitleaks detect --redact -v --exit-code=2
|