@artenx/dshb 0.1.15 → 0.1.17

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -10,9 +10,11 @@ dsh plugin --profile web add @artenx/dshb
10
10
 
11
11
  Restart the corresponding DSH Web profile after installation.
12
12
 
13
+ Desktop app: install from **Settings → Plugins → More** in the DSH desktop app; the plugin takes effect on next startup.
14
+
13
15
  ## Requirements
14
16
 
15
17
  - Node.js 22.13 or newer
16
- - DeepSeek Harness 0.1.7-rc.1
18
+ - DeepSeek Harness 0.1.7-rc.1 or 0.2.1-rc.2 (Web or Desktop)
17
19
 
18
20
  Source code and complete documentation: https://github.com/Artenx/dsh-honeybee
@@ -8,7 +8,7 @@ declare class AuthGate {
8
8
  decide(req: IncomingMessage): GateDecision;
9
9
  private isPageRequest;
10
10
  }
11
- declare function rewriteToLoopback(req: IncomingMessage, port: number): void;
11
+ declare function rewriteToLoopback(req: IncomingMessage, port?: number): void;
12
12
  //#endregion
13
13
  export { AuthGate, GateDecision, rewriteToLoopback };
14
14
  //# sourceMappingURL=gate.d.ts.map
package/lib/auth/gate.js CHANGED
@@ -27,7 +27,9 @@ var AuthGate = class {
27
27
  }
28
28
  };
29
29
  function rewriteToLoopback(req, port) {
30
- const authority = `127.0.0.1:${port}`;
30
+ const localPort = port ?? req.socket.localPort;
31
+ if (typeof localPort !== "number") return;
32
+ const authority = `127.0.0.1:${localPort}`;
31
33
  req.headers.host = authority;
32
34
  for (const name of ["origin", "referer"]) {
33
35
  const value = req.headers[name];
@@ -1 +1 @@
1
- {"version":3,"file":"gate.js","names":[],"sources":["../../src/auth/gate.ts"],"sourcesContent":["import type { IncomingMessage } from 'node:http'\nimport { isLoopbackRequest } from './loopback.js'\nimport { readCookie, SESSION_COOKIE, verifySession } from './session.js'\nimport type { CredentialStore } from './credentials.js'\n\nexport type GateDecision = 'allow' | 'exempt' | 'redirect-login' | 'reject'\n\nexport class AuthGate {\n constructor(private readonly store: CredentialStore) {}\n\n decide(req: IncomingMessage): GateDecision {\n const path = new URL(req.url ?? '/', 'http://x').pathname\n if (path === '/login' || path.startsWith('/api/auth/')) return 'exempt'\n if (isLoopbackRequest(req.socket.remoteAddress, req.headers.host)) return 'allow'\n const secret = this.store.sessionSecret\n const username = this.store.username\n if (secret && username) {\n const value = readCookie(req.headers.cookie, SESSION_COOKIE)\n if (verifySession(value, secret, username)) return 'allow'\n }\n if (this.isPageRequest(req)) return 'redirect-login'\n return 'reject'\n }\n\n private isPageRequest(req: IncomingMessage): boolean {\n if (req.method !== 'GET' && req.method !== 'HEAD') return false\n const path = new URL(req.url ?? '/', 'http://x').pathname\n if (path.startsWith('/api/')) return false\n const accept = req.headers.accept ?? ''\n return accept.includes('text/html') || accept.includes('*/*')\n }\n}\n\nexport function rewriteToLoopback(req: IncomingMessage, port: number): void {\n const authority = `127.0.0.1:${port}`\n req.headers.host = authority\n for (const name of ['origin', 'referer'] as const) {\n const value = req.headers[name]\n if (typeof value !== 'string') continue\n try {\n const url = new URL(value)\n url.host = authority\n req.headers[name] = url.toString()\n } catch {}\n }\n}\n"],"mappings":";;;AAOA,IAAa,WAAb,MAAsB;CACS;CAA7B,YAAY,OAAyC;EAAxB,KAAA,QAAA;CAAyB;CAEtD,OAAO,KAAoC;EACzC,MAAM,OAAO,IAAI,IAAI,IAAI,OAAO,KAAK,UAAU,CAAC,CAAC;EACjD,IAAI,SAAS,YAAY,KAAK,WAAW,YAAY,GAAG,OAAO;EAC/D,IAAI,kBAAkB,IAAI,OAAO,eAAe,IAAI,QAAQ,IAAI,GAAG,OAAO;EAC1E,MAAM,SAAS,KAAK,MAAM;EAC1B,MAAM,WAAW,KAAK,MAAM;EAC5B,IAAI,UAAU,UAAU;GACtB,MAAM,QAAQ,WAAW,IAAI,QAAQ,QAAQ,cAAc;GAC3D,IAAI,cAAc,OAAO,QAAQ,QAAQ,GAAG,OAAO;EACrD;EACA,IAAI,KAAK,cAAc,GAAG,GAAG,OAAO;EACpC,OAAO;CACT;CAEA,cAAsB,KAA+B;EACnD,IAAI,IAAI,WAAW,SAAS,IAAI,WAAW,QAAQ,OAAO;EAE1D,IADa,IAAI,IAAI,IAAI,OAAO,KAAK,UAAU,CAAC,CAAC,SACxC,WAAW,OAAO,GAAG,OAAO;EACrC,MAAM,SAAS,IAAI,QAAQ,UAAU;EACrC,OAAO,OAAO,SAAS,WAAW,KAAK,OAAO,SAAS,KAAK;CAC9D;AACF;AAEA,SAAgB,kBAAkB,KAAsB,MAAoB;CAC1E,MAAM,YAAY,aAAa;CAC/B,IAAI,QAAQ,OAAO;CACnB,KAAK,MAAM,QAAQ,CAAC,UAAU,SAAS,GAAY;EACjD,MAAM,QAAQ,IAAI,QAAQ;EAC1B,IAAI,OAAO,UAAU,UAAU;EAC/B,IAAI;GACF,MAAM,MAAM,IAAI,IAAI,KAAK;GACzB,IAAI,OAAO;GACX,IAAI,QAAQ,QAAQ,IAAI,SAAS;EACnC,QAAQ,CAAC;CACX;AACF"}
1
+ {"version":3,"file":"gate.js","names":[],"sources":["../../src/auth/gate.ts"],"sourcesContent":["import type { IncomingMessage } from 'node:http'\nimport { isLoopbackRequest } from './loopback.js'\nimport { readCookie, SESSION_COOKIE, verifySession } from './session.js'\nimport type { CredentialStore } from './credentials.js'\n\nexport type GateDecision = 'allow' | 'exempt' | 'redirect-login' | 'reject'\n\nexport class AuthGate {\n constructor(private readonly store: CredentialStore) {}\n\n decide(req: IncomingMessage): GateDecision {\n const path = new URL(req.url ?? '/', 'http://x').pathname\n if (path === '/login' || path.startsWith('/api/auth/')) return 'exempt'\n if (isLoopbackRequest(req.socket.remoteAddress, req.headers.host)) return 'allow'\n const secret = this.store.sessionSecret\n const username = this.store.username\n if (secret && username) {\n const value = readCookie(req.headers.cookie, SESSION_COOKIE)\n if (verifySession(value, secret, username)) return 'allow'\n }\n if (this.isPageRequest(req)) return 'redirect-login'\n return 'reject'\n }\n\n private isPageRequest(req: IncomingMessage): boolean {\n if (req.method !== 'GET' && req.method !== 'HEAD') return false\n const path = new URL(req.url ?? '/', 'http://x').pathname\n if (path.startsWith('/api/')) return false\n const accept = req.headers.accept ?? ''\n return accept.includes('text/html') || accept.includes('*/*')\n }\n}\n\nexport function rewriteToLoopback(req: IncomingMessage, port?: number): void {\n const localPort = port ?? req.socket.localPort\n if (typeof localPort !== 'number') return\n const authority = `127.0.0.1:${localPort}`\n req.headers.host = authority\n for (const name of ['origin', 'referer'] as const) {\n const value = req.headers[name]\n if (typeof value !== 'string') continue\n try {\n const url = new URL(value)\n url.host = authority\n req.headers[name] = url.toString()\n } catch {}\n }\n}\n"],"mappings":";;;AAOA,IAAa,WAAb,MAAsB;CACS;CAA7B,YAAY,OAAyC;EAAxB,KAAA,QAAA;CAAyB;CAEtD,OAAO,KAAoC;EACzC,MAAM,OAAO,IAAI,IAAI,IAAI,OAAO,KAAK,UAAU,CAAC,CAAC;EACjD,IAAI,SAAS,YAAY,KAAK,WAAW,YAAY,GAAG,OAAO;EAC/D,IAAI,kBAAkB,IAAI,OAAO,eAAe,IAAI,QAAQ,IAAI,GAAG,OAAO;EAC1E,MAAM,SAAS,KAAK,MAAM;EAC1B,MAAM,WAAW,KAAK,MAAM;EAC5B,IAAI,UAAU,UAAU;GACtB,MAAM,QAAQ,WAAW,IAAI,QAAQ,QAAQ,cAAc;GAC3D,IAAI,cAAc,OAAO,QAAQ,QAAQ,GAAG,OAAO;EACrD;EACA,IAAI,KAAK,cAAc,GAAG,GAAG,OAAO;EACpC,OAAO;CACT;CAEA,cAAsB,KAA+B;EACnD,IAAI,IAAI,WAAW,SAAS,IAAI,WAAW,QAAQ,OAAO;EAE1D,IADa,IAAI,IAAI,IAAI,OAAO,KAAK,UAAU,CAAC,CAAC,SACxC,WAAW,OAAO,GAAG,OAAO;EACrC,MAAM,SAAS,IAAI,QAAQ,UAAU;EACrC,OAAO,OAAO,SAAS,WAAW,KAAK,OAAO,SAAS,KAAK;CAC9D;AACF;AAEA,SAAgB,kBAAkB,KAAsB,MAAqB;CAC3E,MAAM,YAAY,QAAQ,IAAI,OAAO;CACrC,IAAI,OAAO,cAAc,UAAU;CACnC,MAAM,YAAY,aAAa;CAC/B,IAAI,QAAQ,OAAO;CACnB,KAAK,MAAM,QAAQ,CAAC,UAAU,SAAS,GAAY;EACjD,MAAM,QAAQ,IAAI,QAAQ;EAC1B,IAAI,OAAO,UAAU,UAAU;EAC/B,IAAI;GACF,MAAM,MAAM,IAAI,IAAI,KAAK;GACzB,IAAI,OAAO;GACX,IAAI,QAAQ,QAAQ,IAAI,SAAS;EACnC,QAAQ,CAAC;CACX;AACF"}
@@ -36,10 +36,10 @@ function runAuthReset(args) {
36
36
  process.exit(0);
37
37
  }
38
38
  function webCommand() {
39
- return new Command().name("dsh --profile web").description("Serve the DeepSeek Harness browser UI.").helpOption("-h, --help", "show this help").option("--host <host>", "bind host").option("--no-open", "do not open the Web UI in the default browser").option("--port <port>", "listen port; pass 0 to let the OS pick a free one").option("--trusted-host <authority...>", "extra authority the /api browser-trust fence accepts (host or host:port; repeatable)").addHelpText("after", `
39
+ return new Command().name("dsh --profile web").description("Serve the DeepSeek Harness UI.").helpOption("-h, --help", "show this help").option("--host <host>", "bind host").option("--no-open", "do not open the UI in the default browser").option("--port <port>", "listen port; pass 0 to let the OS pick a free one").option("--trusted-host <authority...>", "extra authority the /api browser-trust fence accepts (host or host:port; repeatable)").addHelpText("after", `
40
40
  Examples:
41
41
  dsh --profile web serve on the composed host and port
42
- dsh --profile web --no-open serve without opening a browser
42
+ dsh --profile web --no-open serve without opening a browser (desktop app ignores this)
43
43
  dsh --profile web --port 8080 serve on another port
44
44
  dsh --profile web --host 0.0.0.0 serve on all interfaces (DSHB: auth plugin guards access)
45
45
  dsh --profile web auth-reset --password <新密码> reset admin credentials and revoke all sessions
@@ -1 +1 @@
1
- {"version":3,"file":"startup.js","names":[],"sources":["../../src/auth/startup.ts"],"sourcesContent":["import { Command } from 'commander'\nimport { parseCmdline } from '@deepseek-ai/dsh-cmdline'\nimport type { Context } from '@deepseek-ai/cordis'\nimport { sanitizeUsername, sharedCredentialStore } from './credentials.js'\n\nexport const name = 'web-startup'\n\nexport const inject = ['cmdlineArgs']\n\nexport const WEB_STARTUP_SERVICE = 'webStartup'\n\nexport interface WebStartupValues {\n openBrowser: boolean\n host?: string\n port?: number\n trustedHosts: string[]\n}\n\nfunction runAuthReset(args: readonly string[]): never {\n const store = sharedCredentialStore()\n if (!store.isInitialized()) {\n console.error('auth-reset: 尚未初始化管理员账号')\n process.exit(1)\n }\n let password: string | undefined\n let username: string | undefined\n for (let i = 1; i < args.length; i++) {\n if (args[i] === '--password') password = args[++i]\n else if (args[i] === '--username') username = args[++i]\n }\n if (password === undefined && username === undefined) {\n console.error('auth-reset: 需要 --password <新密码> 和/或 --username <新用户名>')\n process.exit(1)\n }\n if (password !== undefined && password.length < 8) {\n console.error('auth-reset: 密码至少 8 个字符')\n process.exit(1)\n }\n if (username !== undefined) {\n username = sanitizeUsername(username)\n if (!username) {\n console.error('auth-reset: 用户名不能为空')\n process.exit(1)\n }\n }\n if (password !== undefined) store.changePassword(username ?? store.username!, password)\n else store.changeUsername(username!)\n console.log('auth-reset: 已更新凭据并轮换会话密钥,全部已签发会话作废')\n process.exit(0)\n}\n\nfunction webCommand(): Command {\n return new Command()\n .name('dsh --profile web')\n .description('Serve the DeepSeek Harness browser UI.')\n .helpOption('-h, --help', 'show this help')\n .option('--host <host>', 'bind host')\n .option('--no-open', 'do not open the Web UI in the default browser')\n .option('--port <port>', 'listen port; pass 0 to let the OS pick a free one')\n .option(\n '--trusted-host <authority...>',\n 'extra authority the /api browser-trust fence accepts (host or host:port; repeatable)',\n )\n .addHelpText(\n 'after',\n `\nExamples:\n dsh --profile web serve on the composed host and port\n dsh --profile web --no-open serve without opening a browser\n dsh --profile web --port 8080 serve on another port\n dsh --profile web --host 0.0.0.0 serve on all interfaces (DSHB: auth plugin guards access)\n dsh --profile web auth-reset --password <新密码> reset admin credentials and revoke all sessions\n`,\n )\n}\n\nexport function apply(ctx: Context): void {\n const args = ctx.cmdlineArgs?.get() ?? []\n if (args[0] === 'auth-reset') runAuthReset(args)\n const program = webCommand()\n program.action(() => {\n const options = program.opts()\n if (options.port !== undefined && !/^\\d+$/.test(options.port)) {\n program.error(`error: --port must be a number, got ${JSON.stringify(options.port)}`)\n }\n const values: WebStartupValues = {\n openBrowser: options.open,\n ...(options.host !== undefined && { host: options.host }),\n ...(options.port !== undefined && { port: Number(options.port) }),\n trustedHosts: options.trustedHost ?? [],\n }\n ctx.provide(WEB_STARTUP_SERVICE, values)\n })\n parseCmdline(ctx, program)\n}\n"],"mappings":";;;;AAKA,MAAa,OAAO;AAEpB,MAAa,SAAS,CAAC,aAAa;AAEpC,MAAa,sBAAsB;AASnC,SAAS,aAAa,MAAgC;CACpD,MAAM,QAAQ,sBAAsB;CACpC,IAAI,CAAC,MAAM,cAAc,GAAG;EAC1B,QAAQ,MAAM,wBAAwB;EACtC,QAAQ,KAAK,CAAC;CAChB;CACA,IAAI;CACJ,IAAI;CACJ,KAAK,IAAI,IAAI,GAAG,IAAI,KAAK,QAAQ,KAC/B,IAAI,KAAK,OAAO,cAAc,WAAW,KAAK,EAAE;MAC3C,IAAI,KAAK,OAAO,cAAc,WAAW,KAAK,EAAE;CAEvD,IAAI,aAAa,KAAA,KAAa,aAAa,KAAA,GAAW;EACpD,QAAQ,MAAM,uDAAuD;EACrE,QAAQ,KAAK,CAAC;CAChB;CACA,IAAI,aAAa,KAAA,KAAa,SAAS,SAAS,GAAG;EACjD,QAAQ,MAAM,wBAAwB;EACtC,QAAQ,KAAK,CAAC;CAChB;CACA,IAAI,aAAa,KAAA,GAAW;EAC1B,WAAW,iBAAiB,QAAQ;EACpC,IAAI,CAAC,UAAU;GACb,QAAQ,MAAM,qBAAqB;GACnC,QAAQ,KAAK,CAAC;EAChB;CACF;CACA,IAAI,aAAa,KAAA,GAAW,MAAM,eAAe,YAAY,MAAM,UAAW,QAAQ;MACjF,MAAM,eAAe,QAAS;CACnC,QAAQ,IAAI,oCAAoC;CAChD,QAAQ,KAAK,CAAC;AAChB;AAEA,SAAS,aAAsB;CAC7B,OAAO,IAAI,QAAQ,CAAC,CACjB,KAAK,mBAAmB,CAAC,CACzB,YAAY,wCAAwC,CAAC,CACrD,WAAW,cAAc,gBAAgB,CAAC,CAC1C,OAAO,iBAAiB,WAAW,CAAC,CACpC,OAAO,aAAa,+CAA+C,CAAC,CACpE,OAAO,iBAAiB,mDAAmD,CAAC,CAC5E,OACC,iCACA,sFACF,CAAC,CACA,YACC,SACA;;;;;;;CAQF;AACJ;AAEA,SAAgB,MAAM,KAAoB;CACxC,MAAM,OAAO,IAAI,aAAa,IAAI,KAAK,CAAC;CACxC,IAAI,KAAK,OAAO,cAAc,aAAa,IAAI;CAC/C,MAAM,UAAU,WAAW;CAC3B,QAAQ,aAAa;EACnB,MAAM,UAAU,QAAQ,KAAK;EAC7B,IAAI,QAAQ,SAAS,KAAA,KAAa,CAAC,QAAQ,KAAK,QAAQ,IAAI,GAC1D,QAAQ,MAAM,uCAAuC,KAAK,UAAU,QAAQ,IAAI,GAAG;EAErF,MAAM,SAA2B;GAC/B,aAAa,QAAQ;GACrB,GAAI,QAAQ,SAAS,KAAA,KAAa,EAAE,MAAM,QAAQ,KAAK;GACvD,GAAI,QAAQ,SAAS,KAAA,KAAa,EAAE,MAAM,OAAO,QAAQ,IAAI,EAAE;GAC/D,cAAc,QAAQ,eAAe,CAAC;EACxC;EACA,IAAI,QAAQ,qBAAqB,MAAM;CACzC,CAAC;CACD,aAAa,KAAK,OAAO;AAC3B"}
1
+ {"version":3,"file":"startup.js","names":[],"sources":["../../src/auth/startup.ts"],"sourcesContent":["import { Command } from 'commander'\nimport { parseCmdline } from '@deepseek-ai/dsh-cmdline'\nimport type { Context } from '@deepseek-ai/cordis'\nimport { sanitizeUsername, sharedCredentialStore } from './credentials.js'\n\nexport const name = 'web-startup'\n\nexport const inject = ['cmdlineArgs']\n\nexport const WEB_STARTUP_SERVICE = 'webStartup'\n\nexport interface WebStartupValues {\n openBrowser: boolean\n host?: string\n port?: number\n trustedHosts: string[]\n}\n\nfunction runAuthReset(args: readonly string[]): never {\n const store = sharedCredentialStore()\n if (!store.isInitialized()) {\n console.error('auth-reset: 尚未初始化管理员账号')\n process.exit(1)\n }\n let password: string | undefined\n let username: string | undefined\n for (let i = 1; i < args.length; i++) {\n if (args[i] === '--password') password = args[++i]\n else if (args[i] === '--username') username = args[++i]\n }\n if (password === undefined && username === undefined) {\n console.error('auth-reset: 需要 --password <新密码> 和/或 --username <新用户名>')\n process.exit(1)\n }\n if (password !== undefined && password.length < 8) {\n console.error('auth-reset: 密码至少 8 个字符')\n process.exit(1)\n }\n if (username !== undefined) {\n username = sanitizeUsername(username)\n if (!username) {\n console.error('auth-reset: 用户名不能为空')\n process.exit(1)\n }\n }\n if (password !== undefined) store.changePassword(username ?? store.username!, password)\n else store.changeUsername(username!)\n console.log('auth-reset: 已更新凭据并轮换会话密钥,全部已签发会话作废')\n process.exit(0)\n}\n\nfunction webCommand(): Command {\n return new Command()\n .name('dsh --profile web')\n .description('Serve the DeepSeek Harness UI.')\n .helpOption('-h, --help', 'show this help')\n .option('--host <host>', 'bind host')\n .option('--no-open', 'do not open the UI in the default browser')\n .option('--port <port>', 'listen port; pass 0 to let the OS pick a free one')\n .option(\n '--trusted-host <authority...>',\n 'extra authority the /api browser-trust fence accepts (host or host:port; repeatable)',\n )\n .addHelpText(\n 'after',\n `\nExamples:\n dsh --profile web serve on the composed host and port\n dsh --profile web --no-open serve without opening a browser (desktop app ignores this)\n dsh --profile web --port 8080 serve on another port\n dsh --profile web --host 0.0.0.0 serve on all interfaces (DSHB: auth plugin guards access)\n dsh --profile web auth-reset --password <新密码> reset admin credentials and revoke all sessions\n`,\n )\n}\n\nexport function apply(ctx: Context): void {\n const args = ctx.cmdlineArgs?.get() ?? []\n if (args[0] === 'auth-reset') runAuthReset(args)\n const program = webCommand()\n program.action(() => {\n const options = program.opts()\n if (options.port !== undefined && !/^\\d+$/.test(options.port)) {\n program.error(`error: --port must be a number, got ${JSON.stringify(options.port)}`)\n }\n const values: WebStartupValues = {\n openBrowser: options.open,\n ...(options.host !== undefined && { host: options.host }),\n ...(options.port !== undefined && { port: Number(options.port) }),\n trustedHosts: options.trustedHost ?? [],\n }\n ctx.provide(WEB_STARTUP_SERVICE, values)\n })\n parseCmdline(ctx, program)\n}\n"],"mappings":";;;;AAKA,MAAa,OAAO;AAEpB,MAAa,SAAS,CAAC,aAAa;AAEpC,MAAa,sBAAsB;AASnC,SAAS,aAAa,MAAgC;CACpD,MAAM,QAAQ,sBAAsB;CACpC,IAAI,CAAC,MAAM,cAAc,GAAG;EAC1B,QAAQ,MAAM,wBAAwB;EACtC,QAAQ,KAAK,CAAC;CAChB;CACA,IAAI;CACJ,IAAI;CACJ,KAAK,IAAI,IAAI,GAAG,IAAI,KAAK,QAAQ,KAC/B,IAAI,KAAK,OAAO,cAAc,WAAW,KAAK,EAAE;MAC3C,IAAI,KAAK,OAAO,cAAc,WAAW,KAAK,EAAE;CAEvD,IAAI,aAAa,KAAA,KAAa,aAAa,KAAA,GAAW;EACpD,QAAQ,MAAM,uDAAuD;EACrE,QAAQ,KAAK,CAAC;CAChB;CACA,IAAI,aAAa,KAAA,KAAa,SAAS,SAAS,GAAG;EACjD,QAAQ,MAAM,wBAAwB;EACtC,QAAQ,KAAK,CAAC;CAChB;CACA,IAAI,aAAa,KAAA,GAAW;EAC1B,WAAW,iBAAiB,QAAQ;EACpC,IAAI,CAAC,UAAU;GACb,QAAQ,MAAM,qBAAqB;GACnC,QAAQ,KAAK,CAAC;EAChB;CACF;CACA,IAAI,aAAa,KAAA,GAAW,MAAM,eAAe,YAAY,MAAM,UAAW,QAAQ;MACjF,MAAM,eAAe,QAAS;CACnC,QAAQ,IAAI,oCAAoC;CAChD,QAAQ,KAAK,CAAC;AAChB;AAEA,SAAS,aAAsB;CAC7B,OAAO,IAAI,QAAQ,CAAC,CACjB,KAAK,mBAAmB,CAAC,CACzB,YAAY,gCAAgC,CAAC,CAC7C,WAAW,cAAc,gBAAgB,CAAC,CAC1C,OAAO,iBAAiB,WAAW,CAAC,CACpC,OAAO,aAAa,2CAA2C,CAAC,CAChE,OAAO,iBAAiB,mDAAmD,CAAC,CAC5E,OACC,iCACA,sFACF,CAAC,CACA,YACC,SACA;;;;;;;CAQF;AACJ;AAEA,SAAgB,MAAM,KAAoB;CACxC,MAAM,OAAO,IAAI,aAAa,IAAI,KAAK,CAAC;CACxC,IAAI,KAAK,OAAO,cAAc,aAAa,IAAI;CAC/C,MAAM,UAAU,WAAW;CAC3B,QAAQ,aAAa;EACnB,MAAM,UAAU,QAAQ,KAAK;EAC7B,IAAI,QAAQ,SAAS,KAAA,KAAa,CAAC,QAAQ,KAAK,QAAQ,IAAI,GAC1D,QAAQ,MAAM,uCAAuC,KAAK,UAAU,QAAQ,IAAI,GAAG;EAErF,MAAM,SAA2B;GAC/B,aAAa,QAAQ;GACrB,GAAI,QAAQ,SAAS,KAAA,KAAa,EAAE,MAAM,QAAQ,KAAK;GACvD,GAAI,QAAQ,SAAS,KAAA,KAAa,EAAE,MAAM,OAAO,QAAQ,IAAI,EAAE;GAC/D,cAAc,QAAQ,eAAe,CAAC;EACxC;EACA,IAAI,QAAQ,qBAAqB,MAAM;CACzC,CAAC;CACD,aAAa,KAAK,OAAO;AAC3B"}
@@ -4,7 +4,7 @@ import { Context } from "@deepseek-ai/cordis";
4
4
  declare class AuthedWebServer extends WebServer {
5
5
  private readonly gate;
6
6
  constructor(ctx: Context, config: {
7
- host: '127.0.0.1' | '0.0.0.0';
7
+ host: string;
8
8
  port: number;
9
9
  });
10
10
  register(route: WebRoute): () => void;
@@ -1 +1 @@
1
- {"version":3,"file":"webserver.js","names":[],"sources":["../../src/auth/webserver.ts"],"sourcesContent":["import type { IncomingMessage, ServerResponse } from 'node:http'\nimport type { Duplex } from 'node:stream'\nimport WebServer from '@deepseek-ai/dsh-host-webserver'\nimport type { WebRoute, WebUpgradeRoute } from '@deepseek-ai/dsh-host-webserver'\nimport type { Context } from '@deepseek-ai/cordis'\nimport { sharedCredentialStore } from './credentials.js'\nimport { AuthGate, rewriteToLoopback } from './gate.js'\n\nexport default class AuthedWebServer extends WebServer {\n private readonly gate = new AuthGate(sharedCredentialStore())\n\n constructor(ctx: Context, config: { host: '127.0.0.1' | '0.0.0.0'; port: number }) {\n super(ctx, config)\n }\n\n override register(route: WebRoute): () => void {\n return super.register({ ...route, handler: (req, res) => this.guarded(route, req, res) })\n }\n\n override registerFallback(handler: WebRoute['handler']): () => void {\n return super.registerFallback((req, res) =>\n this.guarded({ kind: 'exact', path: '/', handler }, req, res),\n )\n }\n\n override registerUpgrade(route: WebUpgradeRoute): () => void {\n return super.registerUpgrade({\n ...route,\n handler: (req, socket, head) => this.guardedUpgrade(route, req, socket, head),\n })\n }\n\n private guarded(route: WebRoute, req: IncomingMessage, res: ServerResponse): void | Promise<void> {\n const decision = this.gate.decide(req)\n if (decision === 'allow') {\n rewriteToLoopback(req, this.port)\n return route.handler(req, res)\n }\n if (decision === 'exempt') {\n return route.handler(req, res)\n }\n if (decision === 'redirect-login') {\n res.writeHead(302, { location: '/login' })\n res.end()\n return\n }\n res.writeHead(401, { 'content-type': 'application/json' })\n res.end(JSON.stringify({ ok: false, error: 'unauthorized' }))\n }\n\n private guardedUpgrade(\n route: WebUpgradeRoute,\n req: IncomingMessage,\n socket: Duplex,\n head: Buffer,\n ): void | Promise<void> {\n const decision = this.gate.decide(req)\n if (decision === 'allow' || decision === 'exempt') {\n if (decision === 'allow') rewriteToLoopback(req, this.port)\n return route.handler(req, socket, head)\n }\n socket.write('HTTP/1.1 401 Unauthorized\\r\\nConnection: close\\r\\n\\r\\n')\n socket.destroy()\n }\n}\n"],"mappings":";;;;AAQA,IAAqB,kBAArB,cAA6C,UAAU;CACrD,OAAwB,IAAI,SAAS,sBAAsB,CAAC;CAE5D,YAAY,KAAc,QAAyD;EACjF,MAAM,KAAK,MAAM;CACnB;CAEA,SAAkB,OAA6B;EAC7C,OAAO,MAAM,SAAS;GAAE,GAAG;GAAO,UAAU,KAAK,QAAQ,KAAK,QAAQ,OAAO,KAAK,GAAG;EAAE,CAAC;CAC1F;CAEA,iBAA0B,SAA0C;EAClE,OAAO,MAAM,kBAAkB,KAAK,QAClC,KAAK,QAAQ;GAAE,MAAM;GAAS,MAAM;GAAK;EAAQ,GAAG,KAAK,GAAG,CAC9D;CACF;CAEA,gBAAyB,OAAoC;EAC3D,OAAO,MAAM,gBAAgB;GAC3B,GAAG;GACH,UAAU,KAAK,QAAQ,SAAS,KAAK,eAAe,OAAO,KAAK,QAAQ,IAAI;EAC9E,CAAC;CACH;CAEA,QAAgB,OAAiB,KAAsB,KAA2C;EAChG,MAAM,WAAW,KAAK,KAAK,OAAO,GAAG;EACrC,IAAI,aAAa,SAAS;GACxB,kBAAkB,KAAK,KAAK,IAAI;GAChC,OAAO,MAAM,QAAQ,KAAK,GAAG;EAC/B;EACA,IAAI,aAAa,UACf,OAAO,MAAM,QAAQ,KAAK,GAAG;EAE/B,IAAI,aAAa,kBAAkB;GACjC,IAAI,UAAU,KAAK,EAAE,UAAU,SAAS,CAAC;GACzC,IAAI,IAAI;GACR;EACF;EACA,IAAI,UAAU,KAAK,EAAE,gBAAgB,mBAAmB,CAAC;EACzD,IAAI,IAAI,KAAK,UAAU;GAAE,IAAI;GAAO,OAAO;EAAe,CAAC,CAAC;CAC9D;CAEA,eACE,OACA,KACA,QACA,MACsB;EACtB,MAAM,WAAW,KAAK,KAAK,OAAO,GAAG;EACrC,IAAI,aAAa,WAAW,aAAa,UAAU;GACjD,IAAI,aAAa,SAAS,kBAAkB,KAAK,KAAK,IAAI;GAC1D,OAAO,MAAM,QAAQ,KAAK,QAAQ,IAAI;EACxC;EACA,OAAO,MAAM,wDAAwD;EACrE,OAAO,QAAQ;CACjB;AACF"}
1
+ {"version":3,"file":"webserver.js","names":[],"sources":["../../src/auth/webserver.ts"],"sourcesContent":["import type { IncomingMessage, ServerResponse } from 'node:http'\nimport type { Duplex } from 'node:stream'\nimport WebServer from '@deepseek-ai/dsh-host-webserver'\nimport type { WebRoute, WebUpgradeRoute } from '@deepseek-ai/dsh-host-webserver'\nimport type { Context } from '@deepseek-ai/cordis'\nimport { sharedCredentialStore } from './credentials.js'\nimport { AuthGate, rewriteToLoopback } from './gate.js'\n\nexport default class AuthedWebServer extends WebServer {\n private readonly gate = new AuthGate(sharedCredentialStore())\n\n constructor(ctx: Context, config: { host: string; port: number }) {\n super(ctx, config)\n }\n\n override register(route: WebRoute): () => void {\n return super.register({ ...route, handler: (req, res) => this.guarded(route, req, res) })\n }\n\n override registerFallback(handler: WebRoute['handler']): () => void {\n return super.registerFallback((req, res) =>\n this.guarded({ kind: 'exact', path: '/', handler }, req, res),\n )\n }\n\n override registerUpgrade(route: WebUpgradeRoute): () => void {\n return super.registerUpgrade({\n ...route,\n handler: (req, socket, head) => this.guardedUpgrade(route, req, socket, head),\n })\n }\n\n private guarded(route: WebRoute, req: IncomingMessage, res: ServerResponse): void | Promise<void> {\n const decision = this.gate.decide(req)\n if (decision === 'allow') {\n rewriteToLoopback(req, this.port)\n return route.handler(req, res)\n }\n if (decision === 'exempt') {\n return route.handler(req, res)\n }\n if (decision === 'redirect-login') {\n res.writeHead(302, { location: '/login' })\n res.end()\n return\n }\n res.writeHead(401, { 'content-type': 'application/json' })\n res.end(JSON.stringify({ ok: false, error: 'unauthorized' }))\n }\n\n private guardedUpgrade(\n route: WebUpgradeRoute,\n req: IncomingMessage,\n socket: Duplex,\n head: Buffer,\n ): void | Promise<void> {\n const decision = this.gate.decide(req)\n if (decision === 'allow' || decision === 'exempt') {\n if (decision === 'allow') rewriteToLoopback(req, this.port)\n return route.handler(req, socket, head)\n }\n socket.write('HTTP/1.1 401 Unauthorized\\r\\nConnection: close\\r\\n\\r\\n')\n socket.destroy()\n }\n}\n"],"mappings":";;;;AAQA,IAAqB,kBAArB,cAA6C,UAAU;CACrD,OAAwB,IAAI,SAAS,sBAAsB,CAAC;CAE5D,YAAY,KAAc,QAAwC;EAChE,MAAM,KAAK,MAAM;CACnB;CAEA,SAAkB,OAA6B;EAC7C,OAAO,MAAM,SAAS;GAAE,GAAG;GAAO,UAAU,KAAK,QAAQ,KAAK,QAAQ,OAAO,KAAK,GAAG;EAAE,CAAC;CAC1F;CAEA,iBAA0B,SAA0C;EAClE,OAAO,MAAM,kBAAkB,KAAK,QAClC,KAAK,QAAQ;GAAE,MAAM;GAAS,MAAM;GAAK;EAAQ,GAAG,KAAK,GAAG,CAC9D;CACF;CAEA,gBAAyB,OAAoC;EAC3D,OAAO,MAAM,gBAAgB;GAC3B,GAAG;GACH,UAAU,KAAK,QAAQ,SAAS,KAAK,eAAe,OAAO,KAAK,QAAQ,IAAI;EAC9E,CAAC;CACH;CAEA,QAAgB,OAAiB,KAAsB,KAA2C;EAChG,MAAM,WAAW,KAAK,KAAK,OAAO,GAAG;EACrC,IAAI,aAAa,SAAS;GACxB,kBAAkB,KAAK,KAAK,IAAI;GAChC,OAAO,MAAM,QAAQ,KAAK,GAAG;EAC/B;EACA,IAAI,aAAa,UACf,OAAO,MAAM,QAAQ,KAAK,GAAG;EAE/B,IAAI,aAAa,kBAAkB;GACjC,IAAI,UAAU,KAAK,EAAE,UAAU,SAAS,CAAC;GACzC,IAAI,IAAI;GACR;EACF;EACA,IAAI,UAAU,KAAK,EAAE,gBAAgB,mBAAmB,CAAC;EACzD,IAAI,IAAI,KAAK,UAAU;GAAE,IAAI;GAAO,OAAO;EAAe,CAAC,CAAC;CAC9D;CAEA,eACE,OACA,KACA,QACA,MACsB;EACtB,MAAM,WAAW,KAAK,KAAK,OAAO,GAAG;EACrC,IAAI,aAAa,WAAW,aAAa,UAAU;GACjD,IAAI,aAAa,SAAS,kBAAkB,KAAK,KAAK,IAAI;GAC1D,OAAO,MAAM,QAAQ,KAAK,QAAQ,IAAI;EACxC;EACA,OAAO,MAAM,wDAAwD;EACrE,OAAO,QAAQ;CACjB;AACF"}
package/lib/client.js CHANGED
@@ -2105,8 +2105,8 @@ window.__ModuleLoader__.load({
2105
2105
  if (row) {
2106
2106
  const title = getRowTitle(row);
2107
2107
  if (title) activeWorkspaceTitle = title;
2108
+ injectMenuItem(container);
2108
2109
  }
2109
- injectMenuItem(container);
2110
2110
  }
2111
2111
  }
2112
2112
  }