npm Trusted Publishers only supports ONE (repo, workflow, environment)
tuple per package, so the two split workflows cannot both be granted
OIDC publish rights. Consolidate into release.yml with a mode input:
- mode=stable -> publish-stable job, environment=production (reviewer
gate), contents:write (push lightweight tag).
- mode=channel -> publish-channel job, no environment, contents:read.
Concurrency group keys on mode + channel so stable serializes globally
and channels serialize per dist-tag. Trusted Publisher entry should
now point at release.yml with environment left blank (matches both
the production-gated stable job and the env-less channel job).
Replace tools/release.mjs with two workflow_dispatch flows:
- stable: production environment gate (Required Reviewers) + lightweight git tag. Trusted Publishing (OIDC) removes the need for an npm token.
- channel beta: disposable 0.0.0-beta-<sha>-<date> versions on the corresponding dist-tag, no tag, no commit. Any collaborator can dispatch without npm credentials.
Pack-time scans via publint, attw, gitleaks; weekly Dependabot for npm + actions.
- `bl mcp list` — list MCP servers enabled under the current Bailian
account via console gateway PageList (always activated=1).
- `bl mcp tools <server-code>` — list tools exposed by a server.
- `bl mcp call <server-code>.<tool>` — invoke a tool. Accepts `--json`,
repeatable `--arg k=v` (JSON-parsed when possible) and `--query` sugar;
`--url` overrides the endpoint for non-Bailian MCPs.
- core: export `bailianMcpUrl(baseUrl, code)` building
`/api/v1/mcps/<code>/mcp`; `McpClient` now takes a full URL.
- `bl search web` switches to `mcpWebSearchEndpoint` directly.
- e2e: `mcp.e2e.test.ts` covering help, dry-run, arg-merge semantics,
invalid-input paths, and one live `tools/list` against WebSearch.
Extract console login logic to login-console.ts for better separation
of concerns. When no api_key is configured, append needapikey=true to
the console login URL so the frontend returns an apiKey in the callback.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The config file accepted any value that merely starts with "http" (so even
"httpfoo://evil" passed) for base_url and console_gateway_url — origins the
client sends the Bearer token to. Validate them with `new URL()` and an
http:/https: protocol check instead, rejecting malformed values. Valid http(s)
URLs (including custom proxies and local http) are unaffected.
https://claude.ai/code/session_017ZGQCjwNQF5Pz96gLUnnG1
script/js executes its `code` as host JavaScript (via new Function), and a step's
`code` is a *resolved* input — so it could be written as `{ $from: <chat-step> }`,
turning model/API output into the body of the executed function (untrusted data
-> arbitrary host code execution). Pipeline validation now requires script/js
`code` to be a literal string: any $from/expression-sourced code is rejected.
Authoring a literal script/js step remains supported (the pipeline file is the
trust boundary, like a shell/npm script). Combined with "dry-run never executes
$js", this closes the path where untrusted text reaches the JS sink.
Adds regression tests: $from-sourced code rejected, literal code accepted,
dry-run does not execute $js, getByJsonPointer blocks prototype/inherited keys,
and concurrency clamps to the maximum.
https://claude.ai/code/session_017ZGQCjwNQF5Pz96gLUnnG1