@lijian-ui/dsh-term 0.2.0 → 0.3.1

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.
Files changed (37) hide show
  1. package/lib/client.js +475 -94
  2. package/lib/client.js.map +1 -1
  3. package/lib/index.js +454 -29
  4. package/lib/tsconfig.client.tsbuildinfo +1 -1
  5. package/lib/tsconfig.host.tsbuildinfo +1 -1
  6. package/lib/types/client/client-i18n.d.ts +29 -0
  7. package/lib/types/client/client-i18n.d.ts.map +1 -0
  8. package/lib/types/client/i18n-seat.d.ts +15 -0
  9. package/lib/types/client/i18n-seat.d.ts.map +1 -0
  10. package/lib/types/client/index.d.ts +7 -0
  11. package/lib/types/client/index.d.ts.map +1 -1
  12. package/lib/types/client/term/AnimatedDock.d.ts.map +1 -1
  13. package/lib/types/client/term/TerminalPanel.d.ts +8 -3
  14. package/lib/types/client/term/TerminalPanel.d.ts.map +1 -1
  15. package/lib/types/core/types.d.ts +1 -0
  16. package/lib/types/core/types.d.ts.map +1 -1
  17. package/lib/types/gateway/i18n.d.ts +17 -0
  18. package/lib/types/gateway/i18n.d.ts.map +1 -0
  19. package/lib/types/host/routes.d.ts +3 -1
  20. package/lib/types/host/routes.d.ts.map +1 -1
  21. package/lib/types/index.d.ts.map +1 -1
  22. package/package.json +1 -1
  23. package/src/client/client-i18n.ts +68 -0
  24. package/src/client/i18n-seat.ts +27 -0
  25. package/src/client/index.ts +22 -3
  26. package/src/client/term/AnimatedDock.tsx +3 -1
  27. package/src/client/term/DockItem.tsx +1 -1
  28. package/src/client/term/TerminalPanel.tsx +333 -50
  29. package/src/client/term/animated-dock.module.css +3 -3
  30. package/src/client/term/api.ts +17 -2
  31. package/src/client/term/chat-helper.ts +28 -0
  32. package/src/client/term/term.module.css +155 -18
  33. package/src/core/types.ts +22 -4
  34. package/src/gateway/i18n.ts +67 -0
  35. package/src/host/pty-service.ts +160 -17
  36. package/src/host/routes.ts +44 -4
  37. package/src/index.ts +31 -1
@@ -9,9 +9,10 @@
9
9
  import type { IncomingMessage, ServerResponse } from 'node:http'
10
10
  import type { Context } from '@deepseek-ai/cordis'
11
11
  import type {} from '@deepseek-ai/dsh-host-webserver'
12
- import type { TermEvent, TermSpawnRequest } from '../core/types.ts'
12
+ import type { ShellType, TermEvent, TermSpawnRequest } from '../core/types.ts'
13
13
  import type { PtyService } from './pty-service.ts'
14
14
  import { isLoopbackRequest } from './loopback.ts'
15
+ import type { Translator } from '../gateway/i18n.ts'
15
16
 
16
17
  /** JSON envelope mirrors the file-manager panel shape. */
17
18
  type Envelope<T> = { ok: true; value: T } | { ok: false; error: { code: string; message: string } }
@@ -63,9 +64,10 @@ function json(res: ServerResponse, envelope: Envelope<unknown>, status = 200): v
63
64
  * Register the /dsh-term routes.
64
65
  * @param ctx - context carrying the webServer service.
65
66
  * @param pty - the session registry.
67
+ * @param getT - lazy translator getter (reflects current language).
66
68
  * @returns route disposers.
67
69
  */
68
- export function registerTermRoutes(ctx: Context, pty: PtyService): () => void {
70
+ export function registerTermRoutes(ctx: Context, pty: PtyService, getT: () => Translator): () => void {
69
71
  const subscribers = new Set<Subscriber>()
70
72
  const push = (event: TermEvent): void => {
71
73
  for (const subscriber of subscribers) {
@@ -74,7 +76,12 @@ export function registerTermRoutes(ctx: Context, pty: PtyService): () => void {
74
76
  }
75
77
 
76
78
  pty.onOutput = (id, data) => push({ kind: 'output', id, data })
77
- pty.onExit = (id, exitCode) => push({ kind: 'exit', id, exitCode })
79
+ pty.onExit = (id, exitCode) => {
80
+ const msg = getT().t('msg.sessionExited', exitCode)
81
+ push({ kind: 'exit', id, exitCode, message: msg })
82
+ }
83
+ pty.onDetach = (id) => push({ kind: 'detached', id })
84
+ pty.onReattach = (session) => push({ kind: 'reattached', session })
78
85
 
79
86
  const handler = async (req: IncomingMessage, res: ServerResponse): Promise<void> => {
80
87
  if (!isLoopbackRequest(req)) {
@@ -87,6 +94,11 @@ export function registerTermRoutes(ctx: Context, pty: PtyService): () => void {
87
94
  json(res, OK({ sessions: pty.list() }))
88
95
  return
89
96
  }
97
+ if (req.method === 'GET' && url.pathname === '/dsh-term/shells') {
98
+ const shells = await pty.detectShells()
99
+ json(res, OK({ shells }))
100
+ return
101
+ }
90
102
  if (req.method !== 'POST') {
91
103
  json(res, MALFORMED, 405)
92
104
  return
@@ -105,13 +117,18 @@ export function registerTermRoutes(ctx: Context, pty: PtyService): () => void {
105
117
  json(res, MALFORMED, 400)
106
118
  return
107
119
  }
120
+ const validShells: readonly ShellType[] = ['bash', 'zsh', 'powershell', 'cmd', 'gitbash']
108
121
  const session = pty.spawn({
109
122
  name: typeof request.name === 'string' ? request.name : undefined,
110
123
  cwd: typeof request.cwd === 'string' ? request.cwd : undefined,
111
- shell: typeof request.shell === 'string' ? request.shell : undefined,
124
+ shell: typeof request.shell === 'string' && validShells.includes(request.shell as ShellType)
125
+ ? request.shell as ShellType : undefined,
112
126
  args: Array.isArray(request.args) ? request.args.filter((a): a is string => typeof a === 'string') : undefined,
113
127
  cols: typeof request.cols === 'number' ? request.cols : undefined,
114
128
  rows: typeof request.rows === 'number' ? request.rows : undefined,
129
+ env: typeof request.env === 'object' && request.env !== null
130
+ ? Object.fromEntries(Object.entries(request.env).filter(([, v]) => typeof v === 'string')) as Record<string, string>
131
+ : undefined,
115
132
  })
116
133
  push({ kind: 'start', session })
117
134
  json(res, OK(session))
@@ -153,6 +170,29 @@ export function registerTermRoutes(ctx: Context, pty: PtyService): () => void {
153
170
  json(res, OK({ ok: pty.close(body.id) }))
154
171
  return
155
172
  }
173
+ case '/dsh-term/detach': {
174
+ const body = payload as { id?: unknown }
175
+ if (typeof body?.id !== 'string') {
176
+ json(res, MALFORMED, 400)
177
+ return
178
+ }
179
+ json(res, OK({ ok: pty.detach(body.id) }))
180
+ return
181
+ }
182
+ case '/dsh-term/reattach': {
183
+ const body = payload as { id?: unknown }
184
+ if (typeof body?.id !== 'string') {
185
+ json(res, MALFORMED, 400)
186
+ return
187
+ }
188
+ const session = pty.reattach(body.id)
189
+ if (session === null) {
190
+ json(res, FAIL('session not found', 'not_found'), 404)
191
+ return
192
+ }
193
+ json(res, OK(session))
194
+ return
195
+ }
156
196
  default:
157
197
  json(res, MALFORMED, 404)
158
198
  }
package/src/index.ts CHANGED
@@ -7,9 +7,11 @@
7
7
 
8
8
  import type { Context } from '@deepseek-ai/cordis'
9
9
  import type {} from '@deepseek-ai/dsh-host-webserver'
10
+ import { settingsNamespace } from '@deepseek-ai/dsh-settings'
10
11
  import { mountOnce } from './mount-once.ts'
11
12
  import { PtyService } from './host/pty-service.ts'
12
13
  import { registerTermRoutes } from './host/routes.ts'
14
+ import { createTranslator, type Lang } from './gateway/i18n.ts'
13
15
 
14
16
  /** Required services: the route registry. */
15
17
  export const inject = ['webServer']
@@ -25,13 +27,41 @@ export const apply = mountOnce('@lijian-ui/dsh-term', applyImpl)
25
27
 
26
28
  function applyImpl(ctx: Context): void {
27
29
  const pty = new PtyService()
30
+
31
+ /** Current language; resolved from dsh global settings. */
32
+ let lang: Lang = 'zh'
33
+ const t = createTranslator(lang)
34
+
35
+ /** Resolve the user's language preference from dsh settings. */
36
+ function resolveLang(): Lang {
37
+ try {
38
+ const settings = ctx.get('settings') as
39
+ | { get(ns: unknown): { preference?: string } | undefined }
40
+ | undefined
41
+ if (!settings) return 'zh'
42
+ const section = settings.get(settingsNamespace('locale'))
43
+ return section?.preference === 'en' ? 'en' : 'zh'
44
+ } catch {
45
+ return 'zh'
46
+ }
47
+ }
48
+
28
49
  // Route registration + pty teardown both ride the effect fiber: the effect
29
50
  // callback runs immediately and its return value is the fiber disposer.
30
51
  ctx.effect(() => {
31
- const disposeRoutes = registerTermRoutes(ctx, pty)
52
+ lang = resolveLang()
53
+ // t is a closure over lang; no separate sync needed.
54
+ const disposeRoutes = registerTermRoutes(ctx, pty, () => createTranslator(lang))
32
55
  return () => {
33
56
  disposeRoutes()
34
57
  pty.dispose()
35
58
  }
36
59
  }, 'dsh-term: routes + pty lifecycle')
60
+
61
+ // Listen for global language changes (dsh settings → General → Language).
62
+ ctx.root.on('settings/updated', (ns, next) => {
63
+ if (ns !== settingsNamespace('locale')) return
64
+ const pref = (next as { preference?: string })?.preference
65
+ lang = pref === 'en' ? 'en' : 'zh'
66
+ })
37
67
  }