') + ')', 'gi'); if (regex.test(text)) { found = true; var frag = document.createDocumentFragment(); var parts = text.split(regex); parts.forEach(function(part, i) { if (i % 2 === 0) { frag.appendChild(document.createTextNode(part)); } else { var span = document.createElement('span'); span.className = 'userscript-highlight'; span.textContent = part; frag.appendChild(span); } }); node.parentNode.replaceChild(frag, node); } }); } else if (node.nodeType === 1 && node.childNodes) { // element var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT']; if (!skipTags.includes(node.tagName)) { Array.from(node.childNodes).forEach(highlight); } } } highlight(document.body); // Re-highlight on dynamic content var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1 || node.nodeType === 3) highlight(node); }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ', 'i'); if (__m === '*' || __re.test(location.href)) { // Strip utm_, fbclid, gclid, etc. from all links on page (function() { var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content', 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid', 'ref', 'ref_src', 'source', 'medium', 'campaign']; function cleanUrl(url) { try { var u = new URL(url, window.location.origin); var changed = false; trackingParams.forEach(function(p) { if (u.searchParams.has(p)) { u.searchParams.delete(p); changed = true; } }); return changed ? u.toString() : url; } catch (e) { return url; } } function cleanLinks() { document.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } cleanLinks(); var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1) { if (node.tagName === 'A') cleanLinks(); node.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + ', 'i'); if (__m === '*' || __re.test(location.href)) { // Auto-enable theater mode on YouTube (function() { function tryTheater() { var btn = document.querySelector('button[aria-label="Theater mode"], ytd-player #player button[title="Theater mode"]'); if (btn && !btn.classList.contains('activated')) { btn.click(); } } // Try immediately tryTheater(); // Try after navigation (SPA) var lastUrl = location.href; setInterval(function() { if (location.href !== lastUrl) { lastUrl = location.href; setTimeout(tryTheater, 500); } }, 1000); // Also try on player load var observer = new MutationObserver(tryTheater); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ', 'i'); if (__m === '*' || __re.test(location.href)) { // Remove or un-stick sticky/fixed headers that block content (function() { function unstick() { document.querySelectorAll('header, nav, [role="banner"], .header, .navbar, .sticky, .fixed-top, [style*="position: fixed"], [style*="position:sticky"]').forEach(function(el) { if (el.style.position === 'fixed' || el.style.position === 'sticky' || getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') { el.style.position = 'static'; el.style.top = 'auto'; el.style.zIndex = 'auto'; } }); } unstick(); var observer = new MutationObserver(unstick); observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] }); })(); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); })(); feat: refactor command framework by XXPermanentXX · Pull Request #90 · modelstudioai/cli · GitHub
Skip to content

feat: refactor command framework - #90

Merged
XXPermanentXX merged 29 commits into
mainfrom
feat/self-built-framework
Jul 9, 2026
Merged

feat: refactor command framework#90
XXPermanentXX merged 29 commits into
mainfrom
feat/self-built-framework

Conversation

@XXPermanentXX

@XXPermanentXXXXPermanentXX commented Jul 8, 2026

Copy link
Copy Markdown
Collaborator

Overview

This PR refactors the CLI onto a self-built command framework and splits the monorepo into clearer layers: core logic, runtime framework, shared command implementations, and product entrypoints. Commands now declare their own auth domain, flags, usage, validation, and run(ctx) implementation, while each product entrypoint decides the exposed command path.

What Changed

  • Introduced the core / runtime / commands / cli / kscli package split.
  • Replaced the old command registry with product-owned command maps.
  • Centralized argument parsing, help rendering, middleware, auth injection, telemetry, update checks, and error handling in bailian-cli-runtime.
  • Split credentials into API key, console, and OpenAPI auth domains.
  • Migrated command implementations to defineCommand({ auth, flags, usageArgs, exampleArgs, validate, run }).
  • Regenerated the Bailian CLI skill reference from the new packages/cli/src/commands.ts source.

Migrated Mainline Features

  • Dataset upload/list/get/delete/validate.
  • Fine-tuning create/list/get/cancel/delete/logs/checkpoints/export/watch/capability.
  • Deployment create/list/get/models/scale/update/delete.
  • Knowledge search/chat and the dedicated knowledge-studio-cli entrypoint.
  • Token Plan seat and key management through the OpenAPI AK/SK auth domain.
  • Advisor intent detection, updated retrieval behavior, media command flag handling, and auto-update support.

Behavior Changes

  • Default output is now human-readable text unless --output json, DASHSCOPE_OUTPUT=json, or config explicitly selects JSON.
  • Credential flags are scoped by command auth domain instead of being global everywhere.
  • Destructive command confirmation behavior is now handled through command-owned flags.
  • Deprecated global flags such as --no-color, --non-interactive, and --output rich are removed from the new framework surface.

Validation

  • pnpm run sync:skill-assets
  • vp check
  • vp test
  • GitHub Actions lint + typecheck + test

Commands now declare their credential requirement explicitly via a
required `auth: "apiKey" | "console" | "none"` field instead of the
boolean `skipDefaultApiKeySetup`. The runtime prepares credentials
based on this declaration and skips API-key setup under --dry-run.
- core: add AuthRequirement type and required `auth` field to
Command/CommandSpec; drop skipDefaultApiKeySetup
- runtime: gate API-key setup on `auth === "apiKey" && !dryRun`
- commands: annotate all 45 commands (apiKey 25 / console 11 / none 9)
…tion
把 main 从一堆 if + process.exit 重构为「argv 解析成数据 → 交给统一管线执行」。
内核
- resolve(argv) → Resolution(version/help/run/usageError):路由即数据,dispatch 只 switch
- compose 洋葱中间件 (versionCheck/telemetry/auth/runCommand),命令仍收 (config, flags)
- registry.locate() 统一 leaf/group/unknown,取代 isGroupPath + 抛异常的 resolve
- 删除 command-help.ts 全局可变单例:help 渲染收口到错误边界
错误模型
- 新增 UsageError(写错了 → exit 2) 与 IncompleteCommandError(没写完 → 打 help、exit 0)
- version / help / onboarding / 组帮助统一由 resolve 产出、dispatch 分派
参数与校验
- parseFlags 重写:无 positional、新增 switch 类型、值/类型/重复校验
- 无条件必填 → 解析器声明式强制 (OptionDef.required)
- 跨 flag / 条件约束 → 新增 command.validate(flags) 钩子
(text-chat / search-web / speech / vision / video-ref)
- 移除全部交互式输入 (promptText/Select/Confirm),缺输入直接打 help
输出
- detectOutputFormat 默认 text,不再按 TTY 切 json
测试
- 删除 3 个 stale cli 测试,runtime 单测重写 (29 passed),e2e 适配新行为
…ws help
- drop IncompleteCommandError: missing-required, failed validate, and bad/unknown
flags are all UsageError now
- error boundary keys on bareness — bare command that fails → help (exit 0);
non-bare invalid → error + message (exit 2)
- login: drop config.apiKey fallback, --api-key required-unless-console via validate
- speech: drop empty --text-file guard (empty content is the API's concern)
Replace the positional `OptionDef[]` array (key/type regex-parsed from
"--x <v>" strings) with a keyed `FlagsDef` record whose `type` drives both
runtime parsing and compile-time flag-type inference (`Flags<typeof DEF>`).
GLOBAL_FLAGS becomes the single source; the hand-kept GlobalFlags interface
(types/flags.ts) is deleted.
- core: SwitchFlag|ValueFlag union, ParsedFlags/Flags inference, defineCommand
infers F from spec.flags
- runtime: parseFlags dispatches on def.type (switch/string/number/boolean/
array/choices) with declarative required-flag enforcement
- commands: migrate every flag declaration to the keyed form
- naming: option→flag throughout (OptionDef→FlagDef, OptionsDef→FlagsDef,
GLOBAL_OPTIONS→GLOBAL_FLAGS, command field options→flags), plus user-facing
"Options:"→"Flags:" in help and the regenerated skill reference docs
Behavior-preserving aside from the intentional Options→Flags wording:
vp check clean across all packages, 29 parser tests pass, reference regen
byte-identical before the terminology swap.
Move all credential handling out of commands into one place. authStage
resolves the credential for the command's declared `auth` and bakes it into
`ctx.client`, gating (throw) when missing; commands reach the network only
through `ctx.client` and never touch tokens or baseUrl.
- add Client (request/requestJson/uploadFile/mcp/console/url) wrapping the
token + baseUrl; commands call it instead of self-resolving
- run(config, flags) → run(ctx) + CommandContext; migrate all 45 commands
- split domains: model = pure api-key (drop access-token fallback), console =
config.json only (drop DASHSCOPE_ACCESS_TOKEN env)
- consolidate env reads in loadConfig; CredentialSource = flag | env | config;
priority flag > env > config
- endpoints return paths (xxxPath) instead of full URLs; baseUrl owned by Client
- auth status now uses describeAuth
- video/download: auth "none" → "apiKey" (it needs the model API)
- dedupe fetchModelList behind an injected console-call function
- remove ensureApiKey/ensure-key.ts, prompt.ts + isInteractive, and the old
resolveCredential/resolveConsoleGatewayCredential resolvers
- tests: adapt auth.e2e to the new auth status shape; drop the
DASHSCOPE_ACCESS_TOKEN branch from console-readiness gates
AK/SK signing was used only by `knowledge retrieve`'s deprecated fallback,
which the api-key auth gate now makes unreachable. Drop it; the command is
pure api-key.
- knowledge/retrieve: remove the AK/SK path + --access-key-id/secret/workspace-id
flags; api-key only
- delete client/ak-sign.ts and its signRequest/AkSignConfig exports
- drop access_key_id/access_key_secret from config schema, loader, and
`config show` / `config set`
- remove the now-unused PascalCase KnowledgeRetrieve request/response types
Commands no longer call process.exit() directly. Every failure now throws
UsageError (bad input → exit 2) or BailianError (runtime failure, with
AUTH/TIMEOUT codes), so the runtime's handleError stays the single exit
point and telemetry always flushes.
- convert 27 process.exit() sites across 15 commands to throws
- move cross-flag/value checks into validate(); use flag `choices` for
--events / --sort; drop dead --model required check
- keep pipeline's process.exitCode for lint-style soft failures
- enforce with unicorn/no-process-exit, allowed only in runtime/tools/tests
- fix incidental lint: void floating run(), narrow console errorCode,
align generate-reference type imports to source
Missing required flags now throw UsageError (exit code 2, error on
stderr, JSON under --output json) instead of printing help and exiting
0. Update assertions and titles accordingly:
- missing-flag cases: exitCode 0/1 -> 2, retitle to "errors as usage error (2)"
- auth / video-task-get under --output json: assert the error JSON on stderr
- proxy probe: import setupProxyFromEnv from runtime/src/proxy.ts
- drop tests for the removed config export-schema command
- fix t2v dry-run: cliTimeoutPrefix misplaced between --model and its value
- drop the CI / non-TTY early-return so agents see "Update available" too
- widen check interval 4h→24h; stay silent inside the window (no per-command repeat)
- remove orphan isCI() helper (zero callers)
- versioning.md: drop the now-false "banner suppressed under agent" rationale
…solved at the dispatch boundary
- commands consume a narrowed context (identity/settings/own flags/client);
config/auth commands additionally use configStore()/authStore() accessors
- resolution happens once at dispatch: buildSources/buildSettings plus
per-domain credential resolvers; dry-run tolerates missing credentials
- transport takes structured deps; credentials are injected only by Client;
console gateway takes a resolved target with optional token (anonymous
catalog calls); pipeline steps and advisor run against client/settings
- telemetry receives authMethod as a value; global/command flags are split
at dispatch with a same-type shadowing guard at registry build
- behavior change: base URL resolution now prefers DASHSCOPE_BASE_URL env
over config file base_url (unified flag > env > file > default chain)
- priority chains, store semantics and command capability boundaries are
locked by unit tests
- flags split into GLOBAL_FLAGS (all commands) plus MODEL_AUTH_FLAGS /
CONSOLE_AUTH_FLAGS, parsed only for commands of the matching auth
domain; cross-domain flags now fail with "Unknown flag" instead of
being silently ignored
- all shadow redeclarations removed; the registry guard now rejects any
own flag named after a reserved (global or visible-domain) flag
- --workspace-id joins the console domain (chain: flag > env > file);
usage stats drops its private declaration and in-command priority
- auth login declares its credential args as own command parameters
(--api-key / --base-url / --console-site, original behavior intact);
auth status no longer accepts credential-domain overrides (use env or
config set instead)
- command help and the generated reference both show Flags (own + auth
domain) plus a full Global Flags section, replacing the footer hint
- breaking: pipeline run --timeout renamed to --step-timeout (collided
with the global request timeout)
- remove nonInteractive plus yes/async/concurrent from GLOBAL_FLAGS and Settings;
command dispatch no longer resolves command-only switches into global settings
- add shared ASYNC_FLAG / CONCURRENT_FLAG definitions for commands that actually
support task-only return or parallel requests
- keep quota downgrade protection by moving --yes onto quota request and reading
flags.yes for confirmed downgrade submission
- update existing async/concurrent consumers to read own flags; no new capability
matrix entries are added
- refresh generated command reference and remove stale --non-interactive usage
from e2e/stress invocations
- scope image/video task execution to --async and --concurrent
- add concurrent task fan-out for video edit and video ref
- extend image edit to the async image task path
- refresh e2e coverage and generated command references
- remove --no-color from GLOBAL_FLAGS and drop Settings.noColor
- move ANSI styling decisions into runtime color helpers with NO_COLOR support
- update command text renderers to use shared color helpers instead of local ANSI codes
- refresh e2e invocations, generated reference, and agent skill guidance
- add openapi auth requirement with command-scoped access key flags and paired credential resolution
- persist OpenAPI credentials through auth login/status/logout using access_key_* config fields
- route token-plan commands through the centralized ACS signing client
- keep legacy openapi_access_key_* config readable while rejecting it as a new config set key
- refresh docs, generated references, telemetry authMethod, and e2e coverage
- render auth flag sections only for auth domains used by registered commands
- make quick-start prompts opt-in through createCli options
- keep the existing quick-start prompts wired only for bl
- replace stale rag/package references with kscli/knowledge-studio-cli
- update release docs for core/runtime/commands/cli plus kscli publishing
- refresh skill setup notes and generated reference output defaults
@XXPermanentXXXXPermanentXX changed the title feat: 自建命令框架重构 + 合入 main 新功能feat: 自建命令框架重构Jul 9, 2026
@XXPermanentXXXXPermanentXX changed the title feat: 自建命令框架重构feat: refactor command frameworkJul 9, 2026
- use tsx for bl/kscli dev and test runners
- point local library exports to src while keeping publishConfig on dist
- switch vendored telemetry modules to .cjs for source-run compatibility
- update stress/e2e helpers and agent docs for the new source execution path
@XXPermanentXX
XXPermanentXX merged commit 03f0e7c into mainJul 9, 2026
2 checks passed
@XXPermanentXX
XXPermanentXX deleted the feat/self-built-framework branch July 10, 2026 01:56
Sign up for freeto 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.

1 participant

@XXPermanentXX