* feat(compat): merge same-name subcommands under shared parent
When multiple server entries attach to the same parent and their cli.command
collides with a subcommand already in the parent tree, the incoming subcommand's
children are merged recursively into the existing one instead of producing a
duplicate sibling. Leaf-name collisions resolve first-wins.
Fixes the duplicate `group` / `message` rows in `dws chat --help` that surfaced
when bot capabilities were distributed across chat.group.members and
chat.message subtrees.
* feat(ir): carry FlagOverlay and ToolAnnotations into canonical catalog
FlagOverlay mirrors the per-parameter CLI overlay (alias, transform,
transformArgs, env default, default value, hidden) sourced from
market.CLIToolOverride.Flags.
ToolAnnotations mirrors MCP 2025+ tool annotations with nullable hints
(destructive / read-only / idempotent / open-world) so absence means
"unknown" rather than "false".
BuildCatalog now propagates both through the canonical layer, with tests
covering group metadata and flag-overlay passthrough.
* feat(output): add -f pretty for schema-aware colored output
dws schema now supports -f pretty alongside json / raw. Pretty mode
partitions output by product / tool / parameter / enum and applies ANSI
color so humans can browse the catalog without piping through jq.
json / raw behaviour is unchanged.
docs/reference.md gains a Schema Introspection section documenting
dws schema and the new pretty format.
* feat(skills): refresh 13-product references; add devdoc / drive / oa
SKILL.md cli_version bumped to >=1.0.15; product index reordered to match
the v1.0.15 command surface.
New references:
- devdoc.md: Open Platform documentation search
- drive.md: DingTalk drive (promoted out of Coming soon)
- oa.md: OA approval end-to-end flow
Major rewrites for aitable (dashboard / chart share, import/export,
attachment flow), chat (bot capabilities merged into chat.message and
chat.group.members), calendar (event suggest / attachments), doc (comment
subtree, file create, upload/download flow), and minutes (list
mine/shared/all unified, record subcommands split).
Minor alignment touch-ups for contact / report / todo flag names.
* docs(release): sync v1.0.15 notes — 159 commands across 13 products
README Key Services fully refreshed: chat 23, calendar 14, aitable 41,
doc 21, minutes 19; drive promoted out of Coming soon; workbench and
standalone bot rows removed; Quick Start expanded to 7 examples spanning
doc / minutes / drive; Coming soon trimmed to mail / conference / aiapp /
live / wiki.
Adds docs/command-index.md, an auto-generated English listing of all 159
runtime commands with description and when-to-use guidance aimed at AI
agents. Replaces the ad-hoc command-index.pre.* / command-index.full.*
snapshots used during development.
CHANGELOG 1.0.15 records the compat subcommand-merging feature, the new
command index, and a flag-naming cleanup across chat / calendar / drive /
minutes / contact / devdoc.
PR #126 removed the privileged managed-plugin mechanism but kept
LoadManaged, IsManaged, and ~/.dws/plugins/managed/ fallbacks so that
plugins installed by pre-#126 builds would keep loading. The window
for that migration was ~4 days (2026-04-15 → 2026-04-19), and the
original install path mostly failed anyway (issue #124 / GitHub Pages
HTML response), so there is no real user base to preserve.
Scope of deletion:
- plugin.Loader.LoadManaged, scanDir's isManaged param, loadPlugin's
isManaged param. LoadAll now combines user + dev only.
- Plugin.IsManaged field. Converter/root.go no longer branch on it;
stdio and HTTP descriptors both report source="plugin".
- ~/.dws/plugins/managed/ branches in ListInstalled, SetEnabled,
RemovePlugin.
- config.PluginManagedDir constant.
- TestLoaderLoadManaged, TestRemoveLegacyManagedPlugin, and the
"legacy managed" table case in TestRemovePluginPurgesSettings.
Kept on purpose:
- Manifest.Type public schema field ("managed"|"user") — it appears
in existing plugin.json files and is only validated, never
behaviorally consumed.
Users with an orphaned ~/.dws/plugins/managed/ directory can
`rm -rf ~/.dws/plugins/managed/` — the CLI no longer reads it.
Wire plugin module + help command + OAuth client-id/secret flags through
the existing i18n catalog so --help is consistent Chinese under zh locale
and English under en locale, instead of mixing the two.
- internal/app/plugin_cmd.go: wrap 16 Short strings with i18n.T
- internal/app/flags.go: wrap --client-id/--client-secret descriptions
- internal/app/root_help.go: override cobra default help command with a
localized one so "dws help --help" and the utility-commands listing
share the same catalog
- internal/i18n/locales/{en,zh}.json: add 20 new catalog entries
Verified under DWS_LANG=zh (all Chinese), DWS_LANG=en (original English
preserved), and LANG-based auto-routing. go test ./internal/app/... pass.
Co-authored-by: 修雨 <huyizhou.hyz@alibaba-inc.com>
Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
* feat: PAT scope error visualization and auto-retry with authorization polling
1. PAT error result visualization (non-JSON)
- Human-readable error output matching lark-cli style
- Shows error type, message, hint, and authorization command
- JSON output also available via --format json
2. Auto-retry with polling after authorization wall
- Detects missing_scope / insufficient_scope / permission errors
- Polls every 5s for token update after user completes auth
- 10 minute timeout before giving up
- Automatically retries the original command after successful auth
Changes:
- internal/app/pat_auth_retry.go: Core PAT error handling and retry logic
- internal/app/pat_auth_retry_test.go: 11 unit tests
- internal/app/runner.go: Integration with executeInvocation error path
- internal/auth/device_flow.go: Add SetScope() method
* feat: migrate PAT (Permission Authorization) infrastructure to open-source core
Move PAT permission management code from dws-wukong overlay to the
open-source dingtalk-workspace-cli core. This enables PAT handling
in the public distribution while keeping dws-wukong fully compatible
through the edition.Hooks mechanism.
Changes:
- pkg/edition: add 6 new Hooks fields (AuthClientID, AuthClientFromMCP,
SaveToken, LoadToken, DeleteToken, ClassifyToolResult) for overlay
extensions to inject custom auth/token/error behaviour
- internal/errors/pat: new PATError type with ExitCode()=4 and
RawStderr() for embedded-mode passthrough, plus ClassifyToolResultContent
and ClassifyMCPResponseText classification functions
- internal/pat: new command group (pat chmod) with tool result handling
- internal/app/root: register PAT commands before RegisterExtraCommands hook
- internal/app/runner: integrate ClassifyToolResult hook in the
callResult.IsError branch so overlays can intercept PAT/gateway errors
before generic handling
The dws-wukong overlay remains unchanged — its existing pat/ package and
RegisterExtraCommands hook continue to work. Through the go.mod replace
directive and deduplicateCommands mechanism, both codebases coexist
without conflict.
* feat(pat): add PAT auth check with device flow polling and auto-retry
- Add ClassifyPatAuthCheck / AsPatAuthCheckError in pat.go for
AGENT_CODE_NOT_EXISTS error detection
- Add handlePatAuthCheck in pat_auth_retry.go: inject clientId as
x-robot-uid header, poll device flow endpoint, auto-retry on APPROVED
- Integrate PAT check into runner.go executeInvocation pipeline
(edition hook + open-source fallback)
- Add patAuthRequiredCodes map for extensible auth-required code matching
- Remove test_parse.go (temporary test script)
* fix(pat): update poll path to /cli/oauth/device/poll and add x-user-access-token header
- Change DevicePollPath from /api/dingtalk-workspace-cli/oauth/device/poll
to /cli/oauth/device/poll (aligning with backend endpoint convention)
- Add x-user-access-token header to poll requests (loaded from stored token)
* feat(auth): PAT device flow improvements and flowId empty guard
- pat_auth_retry: fix JSON field parsing (result -> data) using DevicePollResponse
- pat_auth_retry: fix elapsed time calculation based on start timestamp
- device_flow: add flowId empty guard - skip polling and show auth URL for manual handling
- device_flow: unify terminalBaseURL to GetMCPBaseURL for polling endpoint
- device_flow: remove unused PollFlowApproval dead code
- runner: integrate DWS_CLIENT_ID env and app.json persistence
- oauth_provider: add x-robot-uid header removal on logout
- client: add request/response logging for transport debugging
* feat(env): switch all service discovery URLs from pre-release to production
- endpoints: pre-login/pre-api/pre-mcp/pre-open-dev -> login/api/mcp/open-dev
- loader: DefaultMarketBaseURL -> mcp.dingtalk.com
- registry: defaultBaseURL -> mcp.dingtalk.com
- constants: DefaultTerminalBaseURL -> open-dev.dingtalk.com
* fix: remove residual merge conflict markers and duplicate Hooks fields in edition.go; unify developer-settings URL via config helper in device_flow.go
* chore: remove lark-cli reference from PrintPatAuthError comment
* test: add comprehensive unit tests for PAT auth modules
- Create internal/errors/pat_test.go (38 tests): covers ClassifyToolResultContent,
ClassifyMCPResponseText, ClassifyPatAuthCheck, AsPatAuthCheckError, cleanPATJSON,
stripClassFields, getDWSGatewayErrorCode, isNotLoggedInError, isBusinessError,
IsPATError, IsPATNoPermissionCode, suggestForBusinessErrorText
- Supplement pat_auth_retry_test.go (+7 tests): IsPatRetrying context checks,
pollPatDeviceFlow edge cases (server error fallback, SSO redirect skip),
extractPatScopeError nil/identity extraction
* feat(pat): exchange authCode for fresh token after PAT APPROVED
Previously pollPatDeviceFlow discarded the authCode from the poll
response, so handlePatAuthCheck retried with the stale token after
APPROVED. This mirrors device_flow.go loginOnce which calls
exchangeCode → SaveTokenData.
Changes:
- pollPatDeviceFlow: return (status, authCode, error) instead of
(status, error); extract Data.AuthCode on APPROVED
- handlePatAuthCheck APPROVED path: call ExchangeCodeForToken +
SaveTokenData before ResetRuntimeTokenCache, with graceful
fallback on exchange failure
- Update all 6 poll test cases for new 3-return signature; add
authCode assertions for APPROVED/REJECTED/EXPIRED/CANCELLED/
ServerError scenarios
* fix(pat): CR round-2 must-fix items
- scopeValueRegex: support multi-segment scopes (mail:a.b:send)
Updated regex to ([a-zA-Z][a-zA-Z0-9_.]*(?::[a-zA-Z][a-zA-Z0-9_.]*)+)
- handlePatAuthCheck: add 3 integration tests (APPROVED/REJECTED/EmptyFlowID)
Covers the 132-line main orchestrator with mock runner + httptest server
- Extract ParseDeviceFlowStatus + status constants to auth package
Eliminates string literal duplication across device_flow.go and pat_auth_retry.go
* fix(pat): use direct OAuth path when clientSecret is provided
When PAT error response includes both clientId and clientSecret,
use SetClientID() (direct DingTalk API mode) instead of
SetClientIDFromMCP() (MCP proxy mode). The MCP proxy does not
hold the secret for the PAT-assigned app, causing HTTP 400
'invalidParameter.idOrSecret.notFound' on token exchange.
Now: clientSecret present → direct mode; absent → MCP proxy mode.
---------
Co-authored-by: shangguanxuan.sgx <shangguanxuan.sgx@alibaba-inc.com>
Address reviewer feedback on #125:
- Hoist a single *cache.Store above the discovery fan-out so all HTTP and
stdio goroutines share one instance rather than each spawning its own in
registerHTTPServer/registerStdioServer. Atomic tmp+rename per cache key
keeps concurrent writers on distinct keys collision-free; global runtime
registries (AppendDynamicServer, RegisterStdioClient) already guard
themselves with a mutex. Added a comment pointing at those invariants.
- Relax cold-path timeouts to survive healthy cross-region endpoints and
Python/Node interpreter warm-up: HTTP 500ms → 1s (plain), 700ms → 1.5s
(auth), stdio 1s → 2s. A new DWS_PLUGIN_COLD_TIMEOUT env var overrides
all three with a single duration, registered in configmeta so it shows
up in `dws config --help`.
- Add internal/app/plugin_discovery_concurrency_test.go covering the
thread-safety claims end-to-end: 16 parallel SaveTools with distinct
keys, 32 parallel AppendDynamicServer, 32 parallel RegisterStdioClient,
plus TestResolvePluginColdTimeouts exercising defaults, a valid
override, an unparseable value, and a non-positive value. All pass with
go test -race.
Verified: go build ./... clean; targeted -race tests PASS in 1.5s;
./internal/{app,cli,cache,plugin}/... all green.
Drop the hardcoded default-managed-plugin bootstrap that auto-fetched
DingTalk-Real-AI/* plugins on CLI startup. All third-party plugins are
now installed equally via `dws plugin install` — no privileged workspace.
Motivation (issue #124): first-run startup printed
WARN failed to fetch remote info for default plugin
DingTalk-Real-AI/conference: invalid character '<' looking for
beginning of value
because the GitHub Pages registry returned HTML for the missing plugin.
Beyond the error, the whole "built-in plugin" concept contradicts the
CLI's lightweight design: plugin installation planning belongs to
agent-authored skills, not to the CLI binary.
Changes:
- Delete internal/plugin/updater.go (EnsureManaged, CheckAndUpdate,
checkRemoteVersion, downloadAndInstall, promptUpdate, zip extraction
— the entire managed-plugin update pipeline).
- Delete internal/plugin/updater_test.go.
- Remove DefaultManagedPlugins, OfficialPluginWorkspace, and
PluginUpdateCheckInterval from pkg/config/constants.go.
- Remove the EnsureManaged/CheckAndUpdate bootstrap block from
internal/app/root.go; only legacy LoadManaged() kept for back-compat
so plugins already installed under ~/.dws/plugins/managed/ still load.
- internal/plugin/loader.go: InstallFromGit always installs under
PluginUserDir; RemovePlugin allows removal of legacy managed-dir
plugins instead of refusing.
- internal/app/plugin_cmd.go: drop `--type managed` flag and related
gating from `plugin install`; disable/remove commands no longer
differentiate managed vs user.
- Update plugin_test.go accordingly (replace TestRemoveManagedPluginBlocked
with TestRemoveLegacyManagedPlugin, drop TestPromptUpdate).
Backward compatibility: `PluginManagedDir = "managed"` constant retained
so plugins already on disk from older CLI versions still load and are
removable. No migration required.
Verification:
- go build ./cmd → success
- go test ./internal/plugin/... → PASS
- go test ./internal/app/... → PASS (436s, matches main baseline)
- Smoke: `/tmp/dws-new plugin list` no longer prints the "Pulling
built-in plugin" or "failed to fetch remote info" WARN.
- Pre-existing failures on main (unrelated): test/cli_compat,
test/integration/extensions (requires auth login),
test/scripts (requires DWS_PACKAGE_VERSION or git tag),
test/unit TestOpenSourceTreeOmitsEmbeddedHostMarkers.
Closes#124
Follow-up to cache-first registration (c95ec04). The cold-cache wall
clock is now bounded by the slowest individual plugin, not the sum:
- Fan out HTTP and stdio discovery together in goroutines rather than
running the stdio loop serially after HTTP.
- HTTP cold budget: 4s → 700ms (auth) / 500ms (plain). Honest dial
timeouts on unreachable endpoints fail fast; healthy third-party
endpoints respond well under the window. The outcome is still saved
as a negative cache, so a miss this run costs the next run ~0ms.
- Stdio cold budget: 4s → 1s. A local subprocess handshake is milliseconds.
With three user plugins (one pointing at TEST-NET-1, permanently
unreachable), cold `dws --help` drops from ~3.3s to ~0.8s and warm stays
at ~80ms. The savings compound linearly with plugin count.
Follow-up to the 4s startup cap (df01f36). Plugin command registration
now reads the tools snapshot directly from the cache store on the hot
path, and only falls through to an Initialize+ListTools RPC when no
snapshot exists.
- registerHTTPServer / registerStdioServer try cache.LoadTools first;
on hit they build Cobra commands synchronously with zero network I/O.
- Cold cache falls back to the existing synchronous discovery (already
bounded at 2-4s) and persists the outcome — including empty tool
lists — as a negative cache so the next invocation is fast regardless
of endpoint health.
- Cache entries are namespaced under "plugin:<name>:<server>" so they
are distinct from Market cache entries in `dws cache status`, and
refresh on-demand via `dws cache clean` / `dws cache refresh`; the
existing 7d ToolsTTL otherwise expires entries naturally.
Warm-cache `dws --help` with three user plugins (one unreachable) now
returns in ~80ms versus 3.7s with synchronous discovery, a >40x win
when endpoints are offline.
Issue #119: an unreachable third-party MCP plugin (e.g. blocked/firewalled
endpoint) blocks `dws --help` for ~10s on every CLI invocation, because
plugin discovery happens eagerly during command-tree construction.
Root cause was a stack of timeouts that multiplied under transport failure:
* `transport.Client.Initialize` loops three supported protocol versions on
every error — including dial timeouts and HTTP 5xx — even though those
failure modes are protocol-version-independent. Three loops × the dial
budget = 3× the worst-case startup cost.
* Default `DialContext.Timeout` was 10s, so a single dial against an
unroutable address (e.g. TEST-NET-1) burned the full plugin context.
* `registerHTTPServer` granted plugins with `AuthHeaders` a 10s outer
context (intended for slow third-party services), and `registerStdioServer`
granted every stdio plugin 10s. Either single misbehaving plugin therefore
stalled the entire CLI.
* Registry-side `defaultDiscoveryTimeout` (10s) and `perServerDiscoveryTimeout`
(5s) had the same shape on the LoadCatalog path.
Changes:
* `transport.Client.Initialize`: short-circuit when the underlying
`*CallError.Stage` is anything other than `CallStageJSONRPC`. Protocol
version negotiation is the only justification for retrying with another
version; transport/HTTP failures fail identically and should surrender.
* `transport.defaultTransport`: `DialContext.Timeout` 10s → 3s.
* `app.registerHTTPServer`: AuthHeaders timeout 10s → 4s.
* `app.registerStdioServer`: outer ctx timeout 10s → 4s.
* `cli.defaultDiscoveryTimeout`: 10s → 4s.
* `discovery.Service`: rename `perServerDiscoveryTimeout` 5s → 2s
(`defaultPerServerDiscoveryTimeout`); add `Service.PerServerTimeout`
override field for tests/callers needing a tighter or looser bound.
Tests:
* `TestInitializeShortCircuitsOnHTTPError` — asserts only ONE protocol
version is attempted on HTTP 5xx.
* `TestInitializeShortCircuitsOnDialFailure` — asserts Initialize returns
in <2s on a refused-connection address.
* Existing `TestInitializeNegotiatesProtocolVersion` continues to pass —
JSON-RPC-stage errors still trigger version fallback.
End-to-end measurement with a blackhole plugin
(endpoint=192.0.2.1, AuthHeaders set):
| Variant | median `dws --help` |
| ------------- | ------------------- |
| main | 10.2s |
| PR #121 alone | 9.1s |
| this branch | 3.72s (-63%) |
The `loader.go` and `discovery/service.go` timeout reductions overlap with
PR #121 by @utafrali; see PR description for attribution.
Refs: #119
Supersedes: #121
- Support file path reference in plugin.json cli field (e.g. "cli": "overlay.json")
in addition to inline JSON objects, resolving path relative to plugin root
- Add description field to CLIToolOverride for static command descriptions
as fallback when MCP tools/list is unavailable (e.g. upstream server offline)
- Fix plugin install on Windows: use cmd /C instead of sh -c for build commands
- Skip copying identical files during plugin install to avoid overwriting
locked executables (running stdio plugin processes on Windows)
- Add symlink skip and path traversal guard in copyDir for security
- Clean up stale files in destination during plugin upgrade via removeStaleFiles
🤖 Generated with [Qoder][https://qoder.com]
- Add `EnsureManaged` to the plugin updater to automatically install missing default plugins on startup.
- Translate the managed plugin removal error message to English for better consistency.
- Update tests to match the new error message.
Prevent plugins from hijacking built-in commands (auth, plugin, cache,
etc.) and detect duplicate names between plugins.
- Add reservedCommands set for protected built-in command names
- Add addPluginCommandsSafe with three conflict rules:
- Plugin vs reserved → reject with warning
- Plugin vs plugin → first wins, second rejected with warning
- Plugin vs Market → plugin wins (intentional override)
- Fix plugin create scaffold format string arg count
Add `build` field to plugin.json so stdio servers can be automatically
compiled to native binaries during install, eliminating runtime deps.
- Add BuildConfig struct to Manifest (command + output fields)
- Add BuildPlugin/runBuild in loader with output verification
- Auto-trigger build in InstallFromDir and InstallFromGit (rollback on failure)
- Add `dws plugin build <dir>` command for manual builds
- Update `plugin create` scaffold to include build template
Implement 'dws plugin config set/get/list/unset' commands that persist
plugin configuration (e.g. API keys) to ~/.dws/settings.json. Values
are automatically injected as environment variables at plugin load time,
so ${KEY} references in plugin.json headers/endpoints resolve without
manual 'export' each session.
Key changes:
- loader.go: add Get/Set/Unset/List/InjectPluginConfigEnv methods
- plugin_cmd.go: add 'plugin config' subcommand group (set/get/list/unset)
- root.go: call InjectPluginConfigEnv() before plugin loading
- loader_config_test.go: 9 unit tests covering all new functionality
User env vars take precedence over settings.json values.
Sensitive values are masked in 'plugin config list' output.
Add Auth Token Registry pattern (inspired by stdio_registry.go) to allow
plugins to declare per-server HTTP headers in plugin.json. This enables
third-party streamable-http MCP servers (e.g. Alibaba Cloud Bailian) to
use their own API keys independently from the default DingTalk OAuth token.
Changes:
- New: internal/app/auth_registry.go — per-productID auth credential store
- manifest.go: MCPServer gains Headers field for custom HTTP headers
- registry.go: ServerDescriptor gains AuthHeaders field
- converter.go: resolve and pass headers through ToServerDescriptors
- root.go: inject plugin auth at discovery time + auto-generate ToolOverrides
- runner.go: route plugin-auth servers to their own Bearer token at runtime
Multi-token isolation: each server is keyed by its CLI.ID (productID),
so different servers can use independent tokens without interfering with
each other or with the default DingTalk OAuth token.
- Fix schema-derived flags not being collected into tool call params
- Add registerHTTPServer for plugin streamable-http tool discovery
- Include dev plugins in ListInstalled output
- Isolate settings path for test environments
- Add stdio and HTTP MCP end-to-end integration tests
Stdio MCP servers discovered by the plugin loader now have their tools
automatically registered as CLI subcommands. The runner dispatches
stdio:// virtual endpoints to the local StdioClient subprocess instead
of the HTTP transport, enabling plugin tools to be invoked directly
from the command line (e.g. `dws hello greet --name Peter`).
Also fixes a bug where plugin endpoints registered via AppendDynamicServer
were overwritten by the subsequent SetDynamicServers call in
loadDynamicCommands, and a context lifecycle bug where stdio subprocesses
were killed immediately after startup due to a short-lived timeout context.
- Wire updater into loadPlugins to check managed plugin updates on
CLI startup with 10s timeout and best-effort semantics
- Add StdioClients() to Plugin for creating stdio transport clients
with DWS_PLUGIN_ROOT/DWS_PLUGIN_DATA variable expansion
- Start and initialize stdio MCP server subprocesses during plugin load
- Reorder loadPlugins: update check → load → http inject → stdio start → hooks
- Add updater.go for managed plugin auto-update (version check, download,
Y/n prompt, zip extraction with zip-slip protection)
- Add stdio.go transport for local MCP server subprocess communication
via stdin/stdout JSON-RPC 2.0
- Implement InstallFromGit in loader.go with URL parsing for HTTPS/SSH
- Wire up --git flag in plugin install command
- Switch all plugin CLI output to English (headers, status, messages)
- Add tests for parseGitURL and promptUpdate
Introduce the plugin system for DWS CLI, enabling official and third-party
plugin management. This includes plugin manifest parsing/validation, loader
with managed/user directory-based identity, MCP server conversion and
injection into the dynamic routing registry, pipeline hook adapter for
shell-based hooks, and CLI commands (list/install/info/enable/disable/
remove/validate).
- Add ExitCoder interface for edition-specific error types to provide custom exit codes
- Add RawStderrError interface for errors that bypass CLI formatting and output raw content to stderr
- Update ExitCode() to resolve exit codes via ExitCoder interface before falling back to default
- Add ClassifyToolResult hook in edition.Hooks for custom MCP tool result error classification
- Invoke ClassifyToolResult in runner before default business-error detection
- Handle RawStderrError in printExecutionError to pass raw JSON through to desktop runtime
- Add unit tests for ExitCoder and RawStderrError interfaces
- Add skill find (keyword search) and skill get (download to temp dir)
using mcp.dingtalk.com endpoints; keep skill add (aihub download)
- Add hidden skill search hint for old usage
- List utility commands in root help; keep skill visible (not hidden)
- Extend tests for new subcommands and root help
Made-with: Cursor
EnvironmentLoader.Load() previously returned (empty Catalog, nil) on all
failure paths, making it impossible for callers to distinguish "no services
available" from "discovery failed due to auth/network issues".
Changes:
- Add CatalogDegraded error type with three reasons: unauthenticated,
market_unreachable, runtime_all_failed
- Add auth pre-check: return DegradedUnauthenticated immediately when no
token is available, avoiding doomed MCP connections
- Schema command now handles CatalogDegraded gracefully: outputs hint to
stderr and includes degraded/reason/hint fields in JSON output
- Runner preserves graceful degradation by ignoring CatalogDegraded errors
- Skip empty-products cache to prevent stale cache from masking auth errors
- Edition-aware hint text (open-source vs embedded/wukong)
Made-with: Cursor
Extract resolveAccessTokenFromDir from getCachedRuntimeToken so both
MCP and non-MCP clients (e.g. A2A gateway) share the same OAuth +
legacy fallback logic including host compatibility hooks.
- Add ResolveAuxiliaryAccessToken in internal/app for overlay use;
reuses process-level token cache when configDir matches the edition
default, avoiding repeated Keychain access.
- Simplify pkg/runtimetoken to a thin delegate to the shared impl.
- Refactor getCachedRuntimeToken to call resolveAccessTokenFromDir,
removing duplicated provider/manager setup code.
Made-with: Cursor