Skip to content

feat(auth): add OAuth2 browser login with PKCE, token persistence and… - #32

Open
lw07 wants to merge 523 commits into
mainfrom
feat/oauth2
Open

feat(auth): add OAuth2 browser login with PKCE, token persistence and…#32
lw07 wants to merge 523 commits into
mainfrom
feat/oauth2

Conversation

@lw07

@lw07 lw07 commented Jun 25, 2026

Copy link
Copy Markdown
Collaborator

… userinfo backfill

Implement the OAuth2 authorization-code + PKCE flow for cz-cli login against ClickZetta.

SDK (clickzetta-sdk):

  • pkce.ts: RFC 7636 S256 code_verifier/code_challenge generation
  • oauth.ts: /clickzetta-hornhub/oauth2 token exchange, refresh-token rotation, userinfo
  • oauth-login-param.ts, oauth-constants.ts: oauthLoginParam build/encode + constants
  • callback-server.ts: loopback listener (dynamic redirect_uri, state validation), default-disabled switch
  • login.ts: send oauthLoginParam, exchange authorizationCode
  • token.ts: refresh-token rotation, TokenStore-backed cross-process persistence
  • types: AuthToken.refreshToken + ConnectionConfig.tokenStore

cz-cli:

  • cz-cli login [--browser]: browser loopback OAuth, opens system browser
  • profile-store: profile-backed TokenStore (instance-keyed), patchProfileConnection, lossless userinfo archive under [profiles..userinfo]
  • accounts-url: env-based accounts host derivation
  • exec: accept a valid persisted OAuth token as credentials (hasUsableCredentials)
  • config: inject instance-keyed tokenStore

build: codesign --remove-signature before --force re-sign to fix bun-compiled binaries.

Specs: openspec/specs/oauth-login + .kiro/specs/oauth-login.

Issue for this PR

Closes #

Type of change

  • Bug fix
  • New feature
  • Refactor / code improvement
  • Documentation

What does this PR do?

Please provide a description of the issue, the changes you made to fix it, and why they work. It is expected that you understand why your changes work and if you do not understand why at least say as much so a maintainer knows how much to value the PR.

If you paste a large clearly AI generated description here your PR may be IGNORED or CLOSED!

How did you verify your code works?

Screenshots / recordings

If this is a UI change, please include a screenshot or recording.

Checklist

  • I have tested my changes locally
  • I have not included unrelated changes in this PR

If you do not follow this template your PR will be automatically rejected.

lin.zhang and others added 30 commits June 1, 2026 17:36
Align with Python connector's Field(name, field_type, ..., nullable).
- Add nullable to LhFieldType and ColumnSchema interfaces
- Parse nullable from API field.type.nullable (default true)
- Cursor.description now includes nullable
- AGENTS.md: instruct AI agents to append Co-Authored-By trailer
- github.ts: programmatic commits include cz-cli co-author

Co-Authored-By: cz-cli <noreply@clickzetta.com>
- Switch all gateway commands to use portal token (X-Clickzetta-Token) directly
  against /llm-gateway-admin/v2/* endpoints — gateway accepts it
- Add gwRequest wrapper with --debug roundtrip logging (path, body, latency, response)
- Add getGatewayContext with userName field for portal fallback endpoints
- Add 'upsert' command (idempotent create-or-update) alongside 'create' (which
  now rejects if alias already exists)
- Fix response data extraction (resp.data is the array directly, not nested)
- Fix field name mapping for Jackson lowercase (vapiKeyAlias/vapiKeyMasked)
- Retain portal endpoint code (portalGet/PORTAL_API) as fallback

Co-Authored-By: cz-cli <noreply@clickzetta.com>
Co-Authored-By: cz-cli <noreply@clickzetta.com>
Both cos-promote.mjs and cos-release.mjs now read the current channel
version before writing. If the version being published is lower than
the current pointer, promote is refused (cos-promote throws, cos-release
skips with a warning). This prevents re-building an older tag from
accidentally rolling back stable/nightly.

Co-Authored-By: cz-cli <noreply@clickzetta.com>
Sleep 20s/30s between retries instead of 1s to give ARM runner
network time to recover from transient failures.

Co-Authored-By: cz-cli <noreply@clickzetta.com>
Co-Authored-By: cz-cli <noreply@clickzetta.com>
When a platform build fails or its artifact is missing, skip that
platform with a warning rather than aborting the entire npm publish.

Co-Authored-By: cz-cli <noreply@clickzetta.com>
Consolidate the hardcoded 'json' default into a shared defaultFormat()
function that reads the CZ_FORMAT environment variable, allowing users
to set a persistent default output format without passing --format on
every invocation.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
feat(cz-cli): support CZ_FORMAT env var for default output format
Replace --generate-notes with manual commit log summary between tags.
Shows all non-merge commits since the previous release tag.

Co-Authored-By: cz-cli <noreply@clickzetta.com>
…iles)

COS SDK defaults to serial chunk uploads (AsyncLimit=1, ChunkParallelLimit=1).
Set FileParallelLimit=10, ChunkParallelLimit=8, and per-file AsyncLimit=8
with 5MB slices to fully utilize bandwidth.

Co-Authored-By: cz-cli <noreply@clickzetta.com>
Add finalize-release job that runs after build/npm/cos, explicitly
marking the GitHub release as --draft=false --latest.

Co-Authored-By: cz-cli <noreply@clickzetta.com>
- Add withRetry helper for COS uploads (3 retries, exponential backoff)
- Enable global acceleration by default (COS_ACCELERATE env var)
- Always upgrade via install script instead of npm (avoids mirror sync delay)

Co-Authored-By: cz-cli <noreply@clickzetta.com>
… lakehouse-doc in release skills sync

Co-Authored-By: cz-cli <noreply@clickzetta.com>
error() and success() only set process.exitCode and write output; they
do not stop execution. Add early returns so handlers don't continue with
invalid state or emit duplicate output after reporting a result.
Extract dep-tasks parsing into parseDependencyTasks, which throws a
HandledCliError on invalid input instead of reporting and then saving a
partially-stripped config. Add early returns after validation errors so
save-config and flow subcommands abort before calling the SDK.
Deleting the profile referenced by default_profile left a dangling
reference. Point it at a remaining profile, or drop the key when none
remain.
Store credential-derived LLM settings under the profile name instead of
a hardcoded "clickzetta" key, and default default_llm to the profile
name. Keeps LLM config per-profile and consistent across packages.
A non-zero start minute was ignored, so 'every N minutes from minute M'
encoded as */N (from 0) instead of M/N. Emit startM/gap when startM != 0.
formatTable now renders the header and separator for empty result sets
instead of falling back to pretty JSON. formatCsvCell no longer JSON-wraps
number/bool/null-looking strings (CSV is typeless), quoting only the
literal "NULL" string to disambiguate it from the null sentinel.
CLICKZETTA_AGENT_RUNTIME in process.env was inherited by child processes,
so a nested cz-cli launched from the agent's bash tool re-entered the
agent runtime. Pass it as a main() argument to scope it to the in-process
call.
Launching the full-screen TUI with captured stdout (a pipe / agent bash
tool) floods escape sequences and hangs. Error out and point at the
non-interactive 'agent run' entry instead.
Adds a /sql slash command that executes SQL via session.shell, equivalent
to `cz-cli sql`. Simple single-line queries are inlined (readable); SQL
containing ' " ` $ \ or newlines is written to a temp file and run with
--file to avoid shell-quoting corruption.
config aliased/routed to agent llm, colliding with the universal
git config-style idiom. Remove the alias and the top-level/agent config
routing; LLM management stays on llm / agent llm.
The committed skills are hand-maintained; the template/generator had
drifted and was no longer the source of the shipped SKILL.md files.
Drop SKILL.template.md, skill-generator.ts, and ai-guide's
--generate-skill/--check-skill options; keep the command-reference output.
…nked llm entry in ai_message

Co-Authored-By: cz-cli <noreply@clickzetta.com>
lyman and others added 29 commits June 16, 2026 21:21
fix(sql,job): 修复字段截断破坏 JSON 结构的问题
- save-cron/save-config 默认保留已有依赖和产出,不自动调用解析接口
- 新增 --auto-lineage 显式启用依赖和任务产出解析
- 新增 --outputs/--output-tables 支持手工替换或清空任务产出

验证:
- cd packages/cz-cli && bun test test/task-save-config-validation.test.ts test/task-lineage.test.ts
- cd packages/cz-cli && bun test ./test/e2e-help.ts
- cd packages/cz-cli && bun typecheck
- openspec validate task-dependency-output-parse --strict
- openspec validate cli-command-routing --strict
- UAT: save-cron 默认保存 parseDataFileDependencyOut 调用 0 次
- UAT: save-cron --auto-lineage 触发 parseDataFileDependencyOut
- UAT: save-config --outputs replace 写入 parseType=1

Co-Authored-By: cz-cli <noreply@clickzetta.com>
…rols

fix(task): 保存调度改为显式解析血缘
…splits JSON

Agent runtimes may strip the surrounding quotes of --output-tables and split the
JSON on internal whitespace, producing stray positional fragments that yargs
rejected with "Unknown command", or an unquoted blob that failed JSON.parse.

- coalesce trailing non-flag fragments back into --output-tables before yargs
- reconstruct output table records from quote-stripped/backslash-escaped blobs,
  returning INVALID_ARGUMENTS only when truly unrecoverable

Implements the existing task-dependency-output-parse spec scenario.

Co-Authored-By: cz-cli <noreply@clickzetta.com>
Expressions like '0 00 * * * ? *' and '0 10 * * * ? *' (every hour at
minute N) were falling through to the 'once daily' branch, producing
NaN in outputCron because parseInt('*') returns NaN.

Add explicit handling: when hour='*' and minute is a fixed value, map
to frequency=2, timeGap=1 (hourly).

Co-Authored-By: cz-cli <noreply@clickzetta.com>
fix(cron-adapter): handle hour=* with fixed minute
toast.show() never defaulted duration, so the zod .default(5000)
(parse-only) didn't apply and the toast cleared instantly. Default
it to 5000, and report the copied character count.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The /llm-gateway-admin/* routes are only served by the Shanghai
alicloud portal; other alicloud regions return 404 and break key
rotation. Rewrite the admin host to cn-shanghai for those regions
(virtual keys are tenant-global, so the token still authenticates).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- 默认不传 --formatted:直接输出 /open/safe_question_poll 的完整 JSON,
  包含推理过程和最终答案
- 显式传 --formatted:输出格式化后的最终答案,不含推理过程
- --format 全局选项保留原有输出格式控制职责,两者不冲突
- 新增 analytics-agent-session-run.test.ts 覆盖两条路径

验证:
- bun test packages/cz-cli/test/analytics-agent-session-run.test.ts
- UAT: session run 默认输出完整 responses 数组(含推理过程)
- UAT: session run --formatted 只输出最终答案文本

Co-Authored-By: cz-cli <noreply@clickzetta.com>
- --summary 语义更直观,表示"只输出最终答案"
- 同步更新测试文件

Co-Authored-By: cz-cli <noreply@clickzetta.com>
…utput

feat(analytics-agent): session run 新增 --summary 输出推理过程
MCP server renamed this tool in commit 4895be6. cz-cli was still
calling the old name, causing tool-not-found errors.

Co-Authored-By: cz-cli <noreply@clickzetta.com>
…splits JSON

- Add JOB_TIMEOUT error classification with actionable hints
- Add job cancel and job list subcommands
- Refactor parseParamValueList to handle escaped quotes from shell/agent
- Extract mergeTaskParamValueList for clearer param override logic
- Update skill docs and tests

Co-Authored-By: cz-cli <noreply@clickzetta.com>
fix: rename job performance tool to match MCP server
fix(task): recover --output-tables and add job cancel/list
…/collectType

- save-config/save-schedule: read existing params and pass to saveTaskConfig
  to prevent backend from resetting params with auto-parsed defaults (bizdate)
- save-cron: same fix
- create-setup --cron: pass paramValueList to saveTaskConfig
- Fix paramType: always use 'manual' for user-provided params (was 'system')
- Fix collectType: use 1 to match Studio UI (was 0)

Co-Authored-By: cz-cli <noreply@clickzetta.com>
fix(task): preserve params in save-config/save-cron
Add explicit trigger conditions and skip rules to avoid intercepting
host project's own SQL/query toolchain.

Co-Authored-By: cz-cli <noreply@clickzetta.com>
docs: refine cz-cli skill trigger description
parseJdbcUrl dropped the port from the host, so a JDBC string like
jdbc:clickzetta://inst.10.155.2.214:8033/ws produced service=10.155.2.214
and toServiceUrl built http://10.155.2.214/... (port 80) -> ConnectionRefused.

- carry parsed.port into service (host:port), matching toServiceUrl's contract
- accept all three vcluster aliases (vcluster/virtualCluster/virtualcluster)
- map use_http=true to protocol=http

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
profile create had its own parseJdbcUrl copy that dropped the port and
only recognized the virtualCluster alias, so `profile create --jdbc
jdbc:clickzetta://inst.host:8033/ws?vcluster=DEFAULT` saved service
without :8033 -> requests hit port 80 -> ConnectionRefused, even after
the bug-37 fix (which only touched the connection/ parser).

Delete the duplicate and reuse the already-fixed parseJdbcUrl from
connection/jdbc.ts, so port preservation, all three vcluster aliases,
and use_http=true work in profile create too.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…operties

fix(table): describe shows full table properties including __metadata
Allow a profile to authenticate via an X-ClickZetta-Token cookie header,
alongside the existing PAT and username/password methods.

- profile create: accept `--header Cookie=...X-ClickZetta-Token=...` as a
  third auth method; skip connection verification on this path since the
  token is supplied directly.
- profile detail: mask the Cookie header value (in addition to pat and
  password) via maskProfileSecrets.
- getExecContext: try the Cookie header first (getCookieToken), parsing the
  JWT payload for userId/accountId/exp and instanceId, and falling back to
  the portal serviceInstanceList API to resolve the instance id by name when
  it is absent; fall back to PAT or username/password when no cookie token
  is present.
- Add profile-cookie-auth tests covering create-time masking, validation,
  and the getExecContext cookie path.
… userinfo backfill

Implement the OAuth2 authorization-code + PKCE flow for cz-cli login against ClickZetta.

SDK (clickzetta-sdk):
- pkce.ts: RFC 7636 S256 code_verifier/code_challenge generation
- oauth.ts: /clickzetta-hornhub/oauth2 token exchange, refresh-token rotation, userinfo
- oauth-login-param.ts, oauth-constants.ts: oauthLoginParam build/encode + constants
- callback-server.ts: loopback listener (dynamic redirect_uri, state validation), default-disabled switch
- login.ts: send oauthLoginParam, exchange authorizationCode
- token.ts: refresh-token rotation, TokenStore-backed cross-process persistence
- types: AuthToken.refreshToken + ConnectionConfig.tokenStore

cz-cli:
- cz-cli login [--browser]: browser loopback OAuth, opens system browser
- profile-store: profile-backed TokenStore (instance-keyed), patchProfileConnection,
  lossless userinfo archive under [profiles.<name>.userinfo]
- accounts-url: env-based accounts host derivation
- exec: accept a valid persisted OAuth token as credentials (hasUsableCredentials)
- config: inject instance-keyed tokenStore

build: codesign --remove-signature before --force re-sign to fix bun-compiled binaries.

Specs: openspec/specs/oauth-login + .kiro/specs/oauth-login.

Co-Authored-By: cz-cli <noreply@clickzetta.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

7 participants