mirror of
https://github.com/TencentCloud/tencentmeeting-cli.git
synced 2026-09-14 20:46:40 +08:00
chore: Bump v1.0.18 and add event subcommand and fixes
This commit is contained in:
@@ -2,6 +2,51 @@
|
||||
|
||||
All notable changes to tmeet will be documented in this file, following the [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) convention.
|
||||
|
||||
## [v1.0.18] - 2026-09-11
|
||||
|
||||
### Added
|
||||
|
||||
- **New `event` subcommand group — real-time event subscription via a per-host bus daemon** (`cmd/event/`, `cmd/root.go`, `internal/event/**`): Introduces a full "per-host bus daemon + shared WSS long connection + multi-consumer fan-out" framework so agents can subscribe to Tencent Meeting real-time events (e.g. `meeting.started`, `meeting.end`) and consume them as NDJSON. All `event consume` consumers share a single WSS connection managed by the bus daemon, which centralises handshake / heartbeat / auto-reconnect.
|
||||
- `event list` (`cmd/event/list.go`) — Lists subscribable EventKeys from the local built-in registry; **does not require login** and issues no remote calls. Optional `--domain` filters by domain (e.g. `meeting`); unknown domain exits with code 1 and prints the set of known domains.
|
||||
- `event schema <EventKey>` (`cmd/event/schema.go`) — Prints `params_schema` / `resolved_output_schema` / `jq_root_path` for the given EventKey; **does not require login**. Unknown key exits with code 1 pointing at `event list`. This is a mandatory pre-step before writing `--jq` / `--param`: a wrong `jq_root_path` does not error, it silently drops events (e.g. `meeting.started` / `meeting.end` have `jq_root_path=.payload` and `payload` itself is an array — jq must first do `.[0]` before descending into fields).
|
||||
- `event consume --event-id <EventKey>` (`cmd/event/consume.go`, `cmd/event/consume_runner.go`) — The actual subscription entry point; **requires login** (owner_hash is bound to the bus). Supports both batch mode (`--max-events` / `--timeout`) and long-running mode (neither flag, exit via `Ctrl-C` or `event stop`). Ships with `--jq` client-side projection, `--param key=value` for subscription parameters, `--output-dir` for NDJSON tee-to-disk (relative paths only, `..` segments rejected — hard-validated), and `--quiet` to suppress non-fatal stderr. **Strict stdout / stderr separation**: business events go only to stdout NDJSON; ready markers / exit summaries / diagnostics go only to stderr. Once ready, stderr emits `[event] ready event_key=<key>` (not muted by `--quiet`); on exit, stderr emits `[event] exited — received <N> event(s) in <duration> (reason: limit|timeout|signal|shutdown)`. **Does not read stdin at all**: closing stdin / `< /dev/null` / `nohup` / `setsid` will not trigger exit in either bounded or unbounded mode, making the command naturally compatible with agent / background scenarios. The stream **does not replay historical events** — only events newly produced after subscription are delivered.
|
||||
- `event status` (`cmd/event/status.go`, `cmd/event/status_stop_test.go`) — Inspects the local bus daemon state; **does not require login** and reads only the local bus directory. Lists `buses[]` with `state`: `running` / `stale_owner` (credential OpenId disagrees with bus owner) / `orphan` (pid dead but artefacts left behind) / `refused` / `errored`. Optional `--fail-on-orphan` exits with code 2 when an orphan is detected, useful for CI / self-healing scripts.
|
||||
- `event stop` (`cmd/event/stop.go`) — Stops the local bus daemon; **does not require login**. `--force` is treated as a **write operation** (enters the dangerous-operation confirmation table) and is used to reap `orphan` / `stale_owner` remnants; `refused` / `errored` states return exit code 2.
|
||||
- `event _bus` (`cmd/event/bus.go`) — **Hidden subcommand** forked automatically by `event consume`; **agents must never invoke it directly**. Owns the single WSS connection to the server, multiplexes subscribers, and fans events out to consumers over IPC (Unix socket / Windows named pipe).
|
||||
- Event framework internals (`internal/event/bus`, `internal/event/source`, `internal/event/protocol`, `internal/event/protocol/wsspb`, `internal/event/busdiscover`, `internal/event/spawner`, `internal/event/transport`, `internal/event/cleanup`, `internal/event/jqfilter`): Hub / subscription registry / connection management / WSS + protobuf codec (`tmeet_wss.proto`) / bus discovery + PID file / cross-platform detach fork (unix / windows) / cross-platform IPC transport / session keep-alive with token refresh / event de-duplication / jq projection engine with unit tests.
|
||||
- New exit-code contract: `0` (normal) / `1` (fatal: Hello rejected, unknown EventKey, IO error, subscription failure, ...) / `2` (only for `event status --fail-on-orphan` and `event stop` on `refused` / `errored`).
|
||||
- New SKILL section and reference doc (`skills/tmeet-skill/references/tmeet-event.md`, 483 lines): full coverage of use-case routing, mandatory call sequence, subprocess contract (ready / exited markers, exit codes, stdin EOF behaviour, stderr diagnostics), red lines (schema-before-jq, no `kill -9`, multi-account switching, path validation, privacy in output, `_bus` never called directly), bus-remnant self-heal flow, EventKey field reference, and a common-failure lookup table.
|
||||
- **Event-related client error codes** (`internal/exception/client_code.go`, `internal/exception/errors.go`): `ClientCodeEventInternal = 4000` / `ClientCodeEventBus = 4001` / `ClientCodeEventBusNotRunning = 4002`, along with matching `EventInternalError` / `EventBusError` / `EventBusNotRunningError` constants.
|
||||
- **WSS token-expiry server codes** (`internal/exception/server_code.go`): `ServerCodeWssTokenExpired = 200010203` / `ServerCodeWssHeadTokenExpired = 10006`, consumed by the WSS long-connection side to detect expiry and trigger token refresh.
|
||||
- **`WSS` endpoint constant** (`internal/core/endpoints.go`): `Endpoints.WSS` and `GetWSSEndpoint()`, pointing to `meeting.tencent.com`, used by the event framework to open the WSS long connection.
|
||||
- **`config.ResourceReleaseHook` teardown-hook mechanism** (`internal/config/user.go`, `internal/config/resource_hooks_test.go`): New `RegisterResourceReleaseHook` / `ResetResourceReleaseHooksForTest` and a `ClearUserConfigFunc` type. Credential teardown (`ClearUserConfig`) now runs every registered hook **first** in registration order (fail-fast: any hook error or panic aborts teardown and leaves keychain + `active_open_id` intact so the user can retry), and only proceeds to keychain / meta cleanup if all hooks succeeded. `cmd/root.go` registers an `event-bus` hook via `registerResourceReleaseHook()` (dispatching to `internal/event/cleanup.OnUserCleared`) so `auth logout` and the refresh-token failure fallback gracefully stop the outgoing account's bus, preventing residue from leaking into the next login session. Deliberately placed in `internal/config` (the lowest package in the dependency graph) to avoid an `internal/event` → `internal/config` cycle.
|
||||
- **Process-lifetime exclusive lock `filelock.ProcessLock`** (`internal/core/filelock/processlock.go`, `internal/core/filelock/filelock.go`, `internal/core/filelock/filelock_unix.go`, `internal/core/filelock/filelock_windows.go`): Distinct from the existing short-lived `WithLock` (poll + defer auto-release), `ProcessLock` acquires non-blockingly and holds for the rest of the process; the OS releases it at process exit. Foundation for the bus daemon "am I alive?" probe (the bus takes a `ProcessLock` on `bus.alive.lock` at startup and never releases it; any subsequent `TryLock` succeeding means the previous holder died). The cross-platform lock primitives `lockFDNonBlocking` / `unlockFD` are extracted so both `WithLock` and `ProcessLock` share a single well-tested implementation. Exports `ErrHeld` for `errors.Is` to distinguish contention from real syscall failures.
|
||||
- **`log.InitNamed` and instance-level `Logger` helpers** (`internal/log/logging.go`): New `InitNamed(logDir, subDir, prefix, level)` lets long-running auxiliary processes (notably the event bus daemon) own their own log namespace while still benefiting from rotation / retention / async writes. The returned `*Logger` is **not** registered as the global `defaultLogger`; callers own it and must call `Close()`. Added instance-level `(*Logger).Debugf/Infof/Warnf/Errorf` and a `(*Logger).Close()` that is idempotent (double-close does not panic).
|
||||
- **`thttp.DefaultNoProxyHttpClient` direct-connection fallback client and per-request `WithRequestClient` option** (`internal/core/thttp/client.go`, `internal/core/thttp/request.go`): New `*http.Client` with `Proxy: nil` (all other transport tunables match `DefaultHttpClient`) for use as a retry path when a CGI request fails at the network layer; `WithRequestClient(clt)` is a new per-request option that overrides the client-level `*http.Client` for a single request without mutating the shared client.
|
||||
- **`common.BuildUniqueID(openID, machineID)` unifies `Tmeet-Unique-ID` / `cli_uniq_id` formatting** (`internal/common/system.go`): The `"<openId>*<machineId>"` concatenation is centralised in one helper. The REST proxy's `Tmeet-Unique-ID` request header and the WSS `AuthBindReq.cli_uniq_id` field now share the same implementation, ruling out format drift between the two paths.
|
||||
- **`build.sh` now runs unit tests before compilation** (`build.sh`): Runs `go test -count=1 ./...` up front and aborts on failure (via the script's `set -e`), baking "test before build" into the release flow.
|
||||
|
||||
### Changed
|
||||
|
||||
- **`ClearUserConfig` is now a two-phase "release resources, then clear credentials" flow** (`internal/config/user.go`, `internal/auth/auth.go`, `internal/proxy/rest-proxy/proxy.go`): The new `ClearUserConfig` runs (1) read `ActiveOpenId`; (2) dispatch every registered `ResourceReleaseHook` fail-fast — any failure or panic leaves keychain + `active_open_id` intact so the user can retry; (3) remove the keychain entry; (4) clear `active_open_id` from meta. The former implementation is preserved as `ClearUserConfigUnResource`. `auth.RefreshToken` now takes a `ClearUserConfigFunc` argument (wired to `config.ClearUserConfig` at the call site) so it no longer hard-depends on a specific teardown function, and so event / test paths can inject different teardown strategies.
|
||||
- **Record-state enum descriptions refined** (`internal/utils/enumerate/record_state.go`, `internal/utils/enumerate/record_state_test.go`, `internal/utils/converter_test.go`): `录制中` → `录制中,不可查看或申请`, `转码中` → `转码中,不可查看或申请`, `转码完成` → `转码完成,可根据录制文件权限进行下一步`; unit tests updated in lock-step.
|
||||
- **`log.Init` / `log.Close` internals refactored around `newLogger` and instance-level `Close`** (`internal/log/logging.go`): Package-level `Init` now delegates to the shared `newLogger` constructor, and package-level `Close` delegates to `defaultLogger.Close()`. `Logger` gains `subDir` / `prefix` fields with `filePrefix()` / `fileSubDir()` fallbacks so path composition, rotation and cleanup all route through instance methods instead of the hard-coded package-level `logPrefix` / `logSubDir`, unlocking the standalone log namespace used by the event bus daemon.
|
||||
- **`minute` package and CLI command renamed to `minutes`** (`cmd/minute/` → `cmd/minutes/`, `cmd/root.go`): CLI entry point changes from `tmeet minute search|get` to `tmeet minutes search|get`; `skills/tmeet-skill/references/tmeet-minute.md` renamed to `tmeet-minutes.md`; every `minute` reference in SKILL and README (command tree, routing decision tables, error hints) rewritten to `minutes`.
|
||||
- **`cmd/root.go` wires the `event` subcommand group and enables the resource-release hook** (`cmd/root.go`): Adds `rootCmd.AddCommand(event.NewBaseCmd(tmeet))`; calls `registerResourceReleaseHook()` at the top of `Execute()` (deliberately not in `init()` — registering in `init()` would activate the hook whenever the `cmd/root` package is imported, e.g. by tests, and risk accidentally tearing down the user's bus); also drops a redundant `fmt.Sprintf` wrapper around `output.PrintErrorf` in favour of the native printf-style signature.
|
||||
- **README trimmed of the long command-reference section** (`README.md`, `README_EN.md`, `docs/command.md`, `docs/command_en.md`): The ~1000-line "Command reference" section is moved out of README into the new `docs/command.md` / `docs/command_en.md`. README keeps only the command tree plus the overview of pagination / `--compact` / `--format`. The command tree gains `event list|schema|consume|status|stop`, and the pagination table renames `minute search|get` to `minutes search|get`.
|
||||
- **SKILL bumped to 1.0.18, fully embracing the event group and the minutes rename** (`skills/tmeet-skill/SKILL.md`, `skills/tmeet-skill/references/tmeet-event.md`, `skills/tmeet-skill/references/tmeet-minutes.md`):
|
||||
- **Top-level description extended**: adds "实时事件订阅(会议/录制/纪要等事件流)" to the capability description.
|
||||
- **Login-prerequisite exceptions extended**: on top of `auth login` / `auth status`, `event list` / `event schema` / `event status` / `event stop` are also **login-free** so users can inspect bus remnants from an unauthenticated state.
|
||||
- **Command tree gains an `event` branch**, linked to the new `references/tmeet-event.md`.
|
||||
- **`--format` section gains an exception clause**: the `event` subcommand family emits **bare JSON** — **no `{trace_id, message, data}` envelope** (e.g. `event consume` emits NDJSON lines shaped `{event, trace_id, payload}`; `event list` emits `[{...}, ...]` directly) — and **does not go through the compact middleware**, so `--compact` has no effect on event output. Use `event consume --jq` for projection instead.
|
||||
- **Yuanbao-minutes routing and the "minutes vs recording" decision table**: every `minute get|search` reference rewritten to `minutes get|search`.
|
||||
- **Common-errors table gains an `event consume` failure entry**: missing ready marker / exit code 1 / `event stop` returning `refused`, etc., all delegate to `tmeet-event.md`'s common-failure lookup table.
|
||||
|
||||
### Fixed
|
||||
|
||||
- **`Base64DecodeConverter` no longer leaves `RawStdEncoding` / `URLEncoding` fields as raw ciphertext** (`internal/utils/converter.go`): Previously the converter only fell back to `RawURLEncoding` once when `StdEncoding` decoding failed — coverage was incomplete: if the server returned `RawStdEncoding` (standard encoding without padding) or `URLEncoding` (URL-safe encoding with padding), decoding failed and the raw Base64 ciphertext was returned to the caller, surfacing as "an unreadable blob" in the model / user output. The converter now walks `StdEncoding` → `RawStdEncoding` → `URLEncoding` → `RawURLEncoding` in order, validating `utf8.Valid` on each success, and returns the decoded plaintext at the first hit; only if all four encodings fail does the original value pass through untouched.
|
||||
- **CGI requests would fail wholesale when a stale proxy env var was inherited** (`internal/proxy/cgi-proxy/proxy.go`, `internal/core/thttp/client.go`, `internal/core/thttp/request.go`): Agent-sandbox scenarios often set `HTTP_PROXY` / `HTTPS_PROXY` / `ALL_PROXY` pointing at a sidecar proxy, and a long-lived (or sandbox-spawned) `tmeet` process inherits those env vars. If the sidecar is torn down but the env vars remain, `DefaultHttpClient` dials a dead proxy on every request and fails at the network layer — the user-visible symptom is "every CGI-backed command times out / connection refused". Fix: introduce `DefaultNoProxyHttpClient` (`Proxy: nil` direct-connection client) plus a per-request `WithRequestClient` option. When `RequestProxy` detects a network-layer failure (not a non-200 HTTP response — an actual `c.clt.Do` error, meaning the request never got a response), it retries once over the direct-connection client using a **brand-new Request struct** so the retry does not inherit the authenticators / headers already appended by the first attempt; non-200 statuses are **not** retried (the server did respond, so retransmission would not help). The whole path also gains structured `log.Errorf` / `log.Warnf` diagnostics, with sensitive headers (`Set-Cookie` / `Authorization` / `Cookie`) redacted via `redactHeader` before being logged.
|
||||
|
||||
## [v1.0.17] - 2026-09-09
|
||||
|
||||
### Added
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@tencentcloud/tmeet",
|
||||
"version": "v1.0.17",
|
||||
"version": "v1.0.18",
|
||||
"description": "腾讯会议 CLI 工具",
|
||||
"bin": {
|
||||
"tmeet": "./scripts/tmeet.js"
|
||||
|
||||
Reference in New Issue
Block a user