dsh-explorer 0.0.0-stage → 0.1.0

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.
@@ -0,0 +1,35 @@
1
+ import { homedir } from 'node:os'
2
+ import type { Context } from './index.ts'
3
+
4
+ export function resolveSessionCwd(ctx: Context, sessionId?: string): string {
5
+ try {
6
+ const sessionService = (ctx.get('sessions') ?? (ctx as any).sessions) as unknown as {
7
+ get?: (id: string) => { cwd?: string; workingDirectory?: string; header?: { cwd?: string } } | undefined
8
+ list?: () => Array<{ cwd?: string; workingDirectory?: string; header?: { cwd?: string } }>
9
+ } | undefined
10
+
11
+ if (sessionService) {
12
+ if (sessionId) {
13
+ const direct = sessionService.get?.(sessionId)
14
+ if (direct?.header?.cwd) return direct.header.cwd
15
+ if (direct?.cwd) return direct.cwd
16
+ if (direct?.workingDirectory) return direct.workingDirectory
17
+ }
18
+
19
+ // If no sessionId or direct session not found, pick the latest session
20
+ if (typeof sessionService.list === 'function') {
21
+ const all = sessionService.list()
22
+ for (let i = all.length - 1; i >= 0; i--) {
23
+ const s = all[i]
24
+ if (s?.header?.cwd) return s.header.cwd
25
+ if (s?.cwd) return s.cwd
26
+ if (s?.workingDirectory) return s.workingDirectory
27
+ }
28
+ }
29
+ }
30
+ } catch {
31
+ // Ignore and fallback
32
+ }
33
+ return homedir()
34
+ }
35
+
package/src/wire.ts ADDED
@@ -0,0 +1,63 @@
1
+ import type { IncomingMessage, ServerResponse } from 'node:http'
2
+
3
+ export class ExplorerError extends Error {
4
+ readonly code: string
5
+ readonly status: number
6
+
7
+ constructor(code: string, message: string, status: number = 400) {
8
+ super(message)
9
+ this.name = 'ExplorerError'
10
+ this.code = code
11
+ this.status = status
12
+ }
13
+ }
14
+
15
+ export function writeJson(res: ServerResponse, data: unknown, status = 200): void {
16
+ const payload = JSON.stringify(data)
17
+ res.writeHead(status, {
18
+ 'Content-Type': 'application/json; charset=utf-8',
19
+ 'Content-Length': Buffer.byteLength(payload),
20
+ 'Cache-Control': 'no-store',
21
+ })
22
+ res.end(payload)
23
+ }
24
+
25
+ export function writeOk(res: ServerResponse, data: Record<string, unknown> = {}): void {
26
+ writeJson(res, { ok: true, ...data })
27
+ }
28
+
29
+ export function writeError(res: ServerResponse, error: unknown): void {
30
+ if (error instanceof ExplorerError) {
31
+ writeJson(res, { ok: false, code: error.code, message: error.message }, error.status)
32
+ return
33
+ }
34
+ const message = error instanceof Error ? error.message : String(error)
35
+ writeJson(res, { ok: false, code: 'internal-error', message }, 500)
36
+ }
37
+
38
+ export async function readJsonBody<T = unknown>(req: IncomingMessage, limitBytes = 10 * 1024 * 1024): Promise<T> {
39
+ const text = await readTextBody(req, limitBytes)
40
+ if (!text.trim()) {
41
+ throw new ExplorerError('bad-request', 'empty request body', 400)
42
+ }
43
+ try {
44
+ return JSON.parse(text) as T
45
+ } catch {
46
+ throw new ExplorerError('bad-request', 'invalid json body', 400)
47
+ }
48
+ }
49
+
50
+ export async function readTextBody(req: IncomingMessage, limitBytes = 10 * 1024 * 1024): Promise<string> {
51
+ let size = 0
52
+ const chunks: Buffer[] = []
53
+ for await (const chunk of req) {
54
+ const buf = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk)
55
+ size += buf.length
56
+ if (size > limitBytes) {
57
+ throw new ExplorerError('payload-too-large', `request body exceeded ${limitBytes} bytes`, 413)
58
+ }
59
+ chunks.push(buf)
60
+ }
61
+ return Buffer.concat(chunks).toString('utf-8')
62
+ }
63
+