startup.ts 3.5 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788
  1. /**
  2. * The web app's command-line provider: it parses the `dsh --profile web` flag
  3. * family (`--host`, `--port`, `--trusted-host`, `--no-open`) and its `--help`
  4. * text, then provides the immutable values as {@link WEB_STARTUP_SERVICE}.
  5. * Ordinary rows inject that service before reading it from lazy config.
  6. * @module @deepseek-ai/dsh-web-app/startup
  7. */
  8. import { Command } from 'commander'
  9. import type { Context } from '@deepseek-ai/cordis'
  10. import { parseCmdline } from '@deepseek-ai/dsh-cmdline'
  11. /** Stable Cordis plugin name. */
  12. export const name = 'web-startup'
  13. /** Services required before the flags can be resolved. */
  14. export const inject = ['cmdlineArgs']
  15. /** Service provided by this ordinary plugin and injected by flag-configured rows. */
  16. export const WEB_STARTUP_SERVICE = 'webStartup'
  17. /** What the web rows read from {@link WEB_STARTUP_SERVICE}. */
  18. export interface WebStartupValues {
  19. /** Whether this invocation opens the default browser after startup. */
  20. openBrowser: boolean
  21. /** `--host`, absent when the invocation did not name one. */
  22. host?: string
  23. /** `--port`, absent when the invocation did not name one. */
  24. port?: number
  25. /** Explicit `--trusted-host` authorities, in argument order. */
  26. trustedHosts: string[]
  27. }
  28. /** The web flag family, as commander parsed it. */
  29. interface WebOptions {
  30. host?: string
  31. open: boolean
  32. port?: string
  33. trustedHost?: string[]
  34. }
  35. /**
  36. * This app's command: its flags, its description, and its help text.
  37. * @returns a fresh program, so one process can parse more than once (tests).
  38. */
  39. function webCommand(): Command {
  40. return new Command()
  41. .name('dsh --profile web')
  42. .description('Serve the DeepSeek Harness browser UI.')
  43. .helpOption('-h, --help', 'show this help')
  44. .option('--host <host>', 'bind host')
  45. .option('--no-open', 'do not open the Web UI in the default browser')
  46. .option('--port <port>', 'listen port; pass 0 to let the OS pick a free one')
  47. .option('--trusted-host <authority...>', 'extra authority the /api browser-trust fence accepts (host or host:port; repeatable)')
  48. .addHelpText('after', `
  49. Examples:
  50. dsh --profile web serve on the composed host and port
  51. dsh --profile web --no-open serve without opening a browser
  52. dsh --profile web --port 8080 serve on another port
  53. `)
  54. }
  55. /**
  56. * Parse and provide the Web invocation as an ordinary Cordis service. The
  57. * command's action publishes the flags this invocation named; `--host 0.0.0.0`
  58. * or a non-numeric `--port` is a usage error, so on rejection (and on `--help`)
  59. * nothing is provided.
  60. * @param ctx - plugin context carrying the command line.
  61. */
  62. export function apply(ctx: Context): void {
  63. const program = webCommand()
  64. program.action(() => {
  65. const options = program.opts<WebOptions>()
  66. if (options.host === '0.0.0.0') {
  67. program.error('error: --host 0.0.0.0 is intentionally not supported yet for safety: it would expose remote code execution to the network; use 127.0.0.1 instead')
  68. }
  69. if (options.port !== undefined && !/^\d+$/.test(options.port)) {
  70. program.error(`error: --port must be a number, got ${JSON.stringify(options.port)}`)
  71. }
  72. ctx.provide(WEB_STARTUP_SERVICE, {
  73. openBrowser: options.open,
  74. ...options.host !== undefined && { host: options.host },
  75. ...options.port !== undefined && { port: Number(options.port) },
  76. trustedHosts: options.trustedHost ?? [],
  77. } satisfies WebStartupValues)
  78. })
  79. parseCmdline(ctx, program)
  80. }