refactor(cli): parse dsh argv through one Commander adapter

Replace the dsh CLI's three hand-rolled parsing idioms (raw argv[0]/includes
dispatch in bin.ts, per-mode node:util parseArgs in headless.ts/web.ts, and the
bespoke parseResumeArg scanner in dsh-app-boot) with a single Commander adapter
in apps/cli/src/args.ts. parseDshArgs resolves argv into a discriminated
DshInvocation union; bin.ts switches on the mode and dynamic-imports the chosen
module, which now consumes already-parsed values.

- web is a real subcommand; --host uses choices and --port an argParser range
  check, moving validation into the parser.
- --resume rejects empty and repeated forms; --prompt rejects empty; a config
  positional after --prompt and a root flag placed before web fail loud.
- adds --help/--version; removes parseResumeArg from dsh-app-boot.
- new apps/cli/tests/args.spec.ts (apps/*/tests added to vitest include,
  apps/cli/tests to tsconfig.host.json); the tui-agent keyless PTY smoke covers
  bin.ts dispatch end to end unchanged.
This commit is contained in:
Turtle
2026-07-24 19:43:59 +08:00
parent 7c0c516f60
commit 800bafda3b
17 changed files with 460 additions and 130 deletions

View File

@@ -4,37 +4,19 @@
* concerns is this app module's job (packages stay single-sided).
*/
import { parseArgs } from 'node:util'
import { networkInterfaces } from 'node:os'
import { createRequire } from 'node:module'
import { mountWebPlugins, startHost } from '@deepseek-ai/dsh-host-runtime'
import { createHostWebPluginRegistry, startWebServer } from '@deepseek-ai/dsh-host-webserver'
import { ALL_INTERFACES_HOST, LOOPBACK_HOST } from './args.ts'
const LOOPBACK_HOST = '127.0.0.1'
const ALL_INTERFACES_HOST = '0.0.0.0'
export async function runWeb(argv: string[]): Promise<void> {
const { values } = parseArgs({
args: argv,
options: {
host: { type: 'string', default: LOOPBACK_HOST },
port: { type: 'string', default: '3080' },
},
allowPositionals: false,
})
if (values.host !== LOOPBACK_HOST && values.host !== ALL_INTERFACES_HOST) {
process.stderr.write(
`dsh web: invalid --host ${values.host}; expected ${LOOPBACK_HOST} or ${ALL_INTERFACES_HOST}\n`,
)
process.exit(1)
}
const hostAddress = values.host
const port = Number(values.port)
if (!Number.isInteger(port) || port < 0 || port > 65535) {
process.stderr.write(`dsh web: invalid --port ${values.port}\n`)
process.exit(1)
}
/**
* Serve the browser UI. Host and port are already validated by the argument
* adapter (host constrained to loopback/all-interfaces, port a 065535 integer).
* @param hostAddress - the bind host: {@link LOOPBACK_HOST} or {@link ALL_INTERFACES_HOST}.
* @param port - the listen port; `0` lets the OS choose a free port.
*/
export async function runWeb(hostAddress: string, port: number): Promise<void> {
// A missing DEEPSEEK_API_KEY throws here (plugin load is fail-loud, uncaught by design).
const host = await startHost({
boot: {