@fleetagent/pi-daemon 0.1.0 → 0.1.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.
- package/CHANGELOG.md +8 -0
- package/README.md +9 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +118 -5
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,12 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [0.1.1](https://github.com/fleetagent/pi/compare/@fleetagent/pi-daemon-v0.1.0...@fleetagent/pi-daemon-v0.1.1) (2026-06-12)
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
### Features
|
|
7
|
+
|
|
8
|
+
* **coding-agent:** add streamed sandbox file transfer ([4199641](https://github.com/fleetagent/pi/commit/41996414740a0a752c8eafac16f48d38ded76c78))
|
|
9
|
+
|
|
3
10
|
## [0.1.0](https://github.com/fleetagent/pi/compare/@fleetagent/pi-daemon-v0.0.12...@fleetagent/pi-daemon-v0.1.0) (2026-06-12)
|
|
4
11
|
|
|
5
12
|
|
|
@@ -11,4 +18,5 @@
|
|
|
11
18
|
|
|
12
19
|
### Added
|
|
13
20
|
|
|
21
|
+
- Added streamed file upload and download methods to the daemon WebSocket protocol.
|
|
14
22
|
- Added the initial Pi remote commander daemon package.
|
package/README.md
CHANGED
|
@@ -43,3 +43,12 @@ Then inside Pi:
|
|
|
43
43
|
```text
|
|
44
44
|
/remote daemon ws://127.0.0.1:8787?token=secret
|
|
45
45
|
```
|
|
46
|
+
|
|
47
|
+
## File transfers
|
|
48
|
+
|
|
49
|
+
The daemon protocol supports streamed file upload and download for binary and text files:
|
|
50
|
+
|
|
51
|
+
- `downloadFile` streams `{ event: "fileData", dataBase64 }` chunks followed by `fileEnd`.
|
|
52
|
+
- `uploadFileStart`, `uploadFileChunk`, and `uploadFileEnd` write chunked base64 data to a sandbox file.
|
|
53
|
+
|
|
54
|
+
Pi RPC exposes these as `upload_file` and `download_file` commands when the daemon is configured as the remote sandbox backend.
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"","sourcesContent":["#!/usr/bin/env node\n\nimport { type ChildProcessWithoutNullStreams, spawn } from \"node:child_process\";\nimport { createHash } from \"node:crypto\";\nimport { constants } from \"node:fs\";\nimport { access, mkdir, readdir, readFile, stat, writeFile } from \"node:fs/promises\";\nimport { createServer, type IncomingMessage } from \"node:http\";\nimport type { Socket } from \"node:net\";\nimport { resolve } from \"node:path\";\n\ninterface JsonRpcMessage {\n\tid?: unknown;\n\tmethod?: unknown;\n\tparams?: unknown;\n}\n\ninterface ClientConnection {\n\tsocket: Socket;\n\tbuffer: Buffer;\n\texecs: Map<string, ChildProcessWithoutNullStreams>;\n}\n\nconst port = Number(process.env.PORT ?? process.env.PI_DAEMON_PORT ?? \"8787\");\nconst host = process.env.HOST ?? process.env.PI_DAEMON_HOST ?? \"127.0.0.1\";\nconst cwd = resolve(process.env.PI_DAEMON_CWD ?? process.cwd());\nconst token = process.env.PI_DAEMON_TOKEN;\n\nfunction sendFrame(socket: Socket, payload: unknown): void {\n\tconst data = Buffer.from(JSON.stringify(payload));\n\tlet header: Buffer;\n\tif (data.length < 126) {\n\t\theader = Buffer.from([0x81, data.length]);\n\t} else if (data.length <= 0xffff) {\n\t\theader = Buffer.alloc(4);\n\t\theader[0] = 0x81;\n\t\theader[1] = 126;\n\t\theader.writeUInt16BE(data.length, 2);\n\t} else {\n\t\theader = Buffer.alloc(10);\n\t\theader[0] = 0x81;\n\t\theader[1] = 127;\n\t\theader.writeBigUInt64BE(BigInt(data.length), 2);\n\t}\n\tsocket.write(Buffer.concat([header, data]));\n}\n\nfunction sendResult(connection: ClientConnection, id: string, result: unknown): void {\n\tsendFrame(connection.socket, { id, result });\n}\n\nfunction sendError(connection: ClientConnection, id: string, error: unknown): void {\n\tconst message = error instanceof Error ? error.message : String(error);\n\tsendFrame(connection.socket, { id, error: { message } });\n}\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n\treturn typeof value === \"object\" && value !== null;\n}\n\nfunction requireString(value: unknown, name: string): string {\n\tif (typeof value !== \"string\") throw new Error(`Missing string param: ${name}`);\n\treturn value;\n}\n\nfunction optionalString(value: unknown): string | undefined {\n\treturn typeof value === \"string\" ? value : undefined;\n}\n\nfunction optionalNumber(value: unknown): number | undefined {\n\treturn typeof value === \"number\" ? value : undefined;\n}\n\nfunction accessModeToFsMode(mode: unknown): number {\n\tswitch (mode) {\n\t\tcase \"read\":\n\t\t\treturn constants.R_OK;\n\t\tcase \"write\":\n\t\t\treturn constants.W_OK;\n\t\tcase \"readwrite\":\n\t\t\treturn constants.R_OK | constants.W_OK;\n\t\tcase \"exists\":\n\t\tcase undefined:\n\t\t\treturn constants.F_OK;\n\t\tdefault:\n\t\t\tthrow new Error(`Invalid access mode: ${String(mode)}`);\n\t}\n}\n\nfunction buildFdArgs(pattern: string, searchPath: string, limit: number): string[] {\n\tconst args: string[] = [\"--glob\", \"--color=never\", \"--hidden\", \"--no-require-git\", \"--max-results\", String(limit)];\n\tlet effectivePattern = pattern;\n\tif (pattern.includes(\"/\")) {\n\t\targs.push(\"--full-path\");\n\t\tif (!pattern.startsWith(\"/\") && !pattern.startsWith(\"**/\") && pattern !== \"**\") {\n\t\t\teffectivePattern = `**/${pattern}`;\n\t\t}\n\t}\n\targs.push(\"--\", effectivePattern, searchPath);\n\treturn args;\n}\n\nfunction buildRgArgs(params: Record<string, unknown>): string[] {\n\tconst pattern = requireString(params.pattern, \"pattern\");\n\tconst path = requireString(params.path, \"path\");\n\tconst args: string[] = [\"--json\", \"--line-number\", \"--color=never\", \"--hidden\"];\n\tif (params.ignoreCase === true) args.push(\"--ignore-case\");\n\tif (params.literal === true) args.push(\"--fixed-strings\");\n\tconst glob = optionalString(params.glob);\n\tif (glob) args.push(\"--glob\", glob);\n\targs.push(\"--\", pattern, path);\n\treturn args;\n}\n\nasync function runBuffered(command: string, args: string[], runCwd: string): Promise<Buffer> {\n\treturn new Promise((resolvePromise, reject) => {\n\t\tconst child = spawn(command, args, { cwd: runCwd, stdio: [\"ignore\", \"pipe\", \"pipe\"] });\n\t\tconst stdout: Buffer[] = [];\n\t\tconst stderr: Buffer[] = [];\n\t\tchild.stdout.on(\"data\", (data: Buffer) => stdout.push(data));\n\t\tchild.stderr.on(\"data\", (data: Buffer) => stderr.push(data));\n\t\tchild.on(\"error\", reject);\n\t\tchild.on(\"close\", (code) => {\n\t\t\tif (code === 0) {\n\t\t\t\tresolvePromise(Buffer.concat(stdout));\n\t\t\t\treturn;\n\t\t\t}\n\t\t\treject(new Error(Buffer.concat(stderr).toString(\"utf-8\").trim() || `${command} exited with code ${code}`));\n\t\t});\n\t});\n}\n\nfunction handleExec(connection: ClientConnection, id: string, params: Record<string, unknown>): void {\n\tconst command = requireString(params.command, \"command\");\n\tconst runCwd = optionalString(params.cwd) ?? cwd;\n\tconst timeout = optionalNumber(params.timeout);\n\tconst env = isRecord(params.env)\n\t\t? Object.fromEntries(\n\t\t\t\tObject.entries(params.env).filter((entry): entry is [string, string] => typeof entry[1] === \"string\"),\n\t\t\t)\n\t\t: undefined;\n\tconst child = spawn(\"bash\", [\"-lc\", command], {\n\t\tcwd: runCwd,\n\t\tdetached: process.platform !== \"win32\",\n\t\tenv: env ? { ...process.env, ...env } : process.env,\n\t});\n\tconnection.execs.set(id, child);\n\tlet timeoutHandle: NodeJS.Timeout | undefined;\n\tif (timeout !== undefined && timeout > 0) {\n\t\ttimeoutHandle = setTimeout(() => {\n\t\t\tif (process.platform !== \"win32\" && child.pid) {\n\t\t\t\ttry {\n\t\t\t\t\tprocess.kill(-child.pid);\n\t\t\t\t} catch {\n\t\t\t\t\tchild.kill();\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tchild.kill();\n\t\t\t}\n\t\t}, timeout * 1000);\n\t}\n\tchild.stdout.on(\"data\", (data: Buffer) => {\n\t\tsendFrame(connection.socket, { id, event: \"data\", stream: \"stdout\", dataBase64: data.toString(\"base64\") });\n\t});\n\tchild.stderr.on(\"data\", (data: Buffer) => {\n\t\tsendFrame(connection.socket, { id, event: \"data\", stream: \"stderr\", dataBase64: data.toString(\"base64\") });\n\t});\n\tchild.on(\"error\", (error) => {\n\t\tif (timeoutHandle) clearTimeout(timeoutHandle);\n\t\tconnection.execs.delete(id);\n\t\tsendFrame(connection.socket, { id, event: \"error\", error: { message: error.message } });\n\t});\n\tchild.on(\"close\", (code) => {\n\t\tif (timeoutHandle) clearTimeout(timeoutHandle);\n\t\tconnection.execs.delete(id);\n\t\tsendFrame(connection.socket, { id, event: \"exit\", exitCode: code });\n\t});\n}\n\nfunction cancelExec(connection: ClientConnection, id: string): void {\n\tconst child = connection.execs.get(id);\n\tif (!child) return;\n\tconnection.execs.delete(id);\n\tif (process.platform !== \"win32\" && child.pid) {\n\t\ttry {\n\t\t\tprocess.kill(-child.pid);\n\t\t} catch {\n\t\t\tchild.kill();\n\t\t}\n\t} else {\n\t\tchild.kill();\n\t}\n\tsendFrame(connection.socket, { id, event: \"exit\", exitCode: null, cancelled: true });\n}\n\nasync function handleRequest(connection: ClientConnection, message: JsonRpcMessage): Promise<void> {\n\tconst id = requireString(message.id, \"id\");\n\tconst method = requireString(message.method, \"method\");\n\tconst params = isRecord(message.params) ? message.params : {};\n\ttry {\n\t\tif (method === \"cancel\") {\n\t\t\tcancelExec(connection, id);\n\t\t\treturn;\n\t\t}\n\t\tif (method === \"exec\") {\n\t\t\thandleExec(connection, id, params);\n\t\t\treturn;\n\t\t}\n\t\tif (method === \"capabilities\") {\n\t\t\tsendResult(connection, id, {\n\t\t\t\tcwd,\n\t\t\t\tfeatures: { exec: true, files: true, glob: true, grep: true, instructions: false },\n\t\t\t});\n\t\t\treturn;\n\t\t}\n\t\tif (method === \"access\") {\n\t\t\tawait access(requireString(params.path, \"path\"), accessModeToFsMode(params.mode));\n\t\t\tsendResult(connection, id, {});\n\t\t\treturn;\n\t\t}\n\t\tif (method === \"readFile\") {\n\t\t\tconst content = await readFile(requireString(params.path, \"path\"));\n\t\t\tsendResult(connection, id, { contentBase64: content.toString(\"base64\") });\n\t\t\treturn;\n\t\t}\n\t\tif (method === \"writeFile\") {\n\t\t\tawait writeFile(\n\t\t\t\trequireString(params.path, \"path\"),\n\t\t\t\tBuffer.from(requireString(params.contentBase64, \"contentBase64\"), \"base64\"),\n\t\t\t);\n\t\t\tsendResult(connection, id, {});\n\t\t\treturn;\n\t\t}\n\t\tif (method === \"mkdir\") {\n\t\t\tawait mkdir(requireString(params.path, \"path\"), { recursive: params.recursive === true });\n\t\t\tsendResult(connection, id, {});\n\t\t\treturn;\n\t\t}\n\t\tif (method === \"stat\") {\n\t\t\tconst result = await stat(requireString(params.path, \"path\"));\n\t\t\tsendResult(connection, id, { isDirectory: result.isDirectory(), isFile: result.isFile() });\n\t\t\treturn;\n\t\t}\n\t\tif (method === \"readdir\") {\n\t\t\tsendResult(connection, id, { entries: await readdir(requireString(params.path, \"path\")) });\n\t\t\treturn;\n\t\t}\n\t\tif (method === \"glob\") {\n\t\t\tconst pattern = requireString(params.pattern, \"pattern\");\n\t\t\tconst runCwd = requireString(params.cwd, \"cwd\");\n\t\t\tconst limit = optionalNumber(params.limit) ?? 1000;\n\t\t\tconst output = await runBuffered(\"fd\", buildFdArgs(pattern, runCwd, limit), runCwd);\n\t\t\tsendResult(connection, id, { matches: output.toString(\"utf-8\").split(\"\\n\").filter(Boolean) });\n\t\t\treturn;\n\t\t}\n\t\tif (method === \"grep\") {\n\t\t\tconst pathParam = requireString(params.path, \"path\");\n\t\t\tconst isDirectory = (await stat(pathParam)).isDirectory();\n\t\t\tconst output = await runBuffered(\"rg\", buildRgArgs(params), cwd);\n\t\t\tconst matches = output\n\t\t\t\t.toString(\"utf-8\")\n\t\t\t\t.split(\"\\n\")\n\t\t\t\t.filter(Boolean)\n\t\t\t\t.flatMap((line) => {\n\t\t\t\t\ttry {\n\t\t\t\t\t\tconst data = JSON.parse(line) as unknown;\n\t\t\t\t\t\tif (!isRecord(data) || data.type !== \"match\" || !isRecord(data.data)) return [];\n\t\t\t\t\t\tconst filePath = isRecord(data.data.path) ? optionalString(data.data.path.text) : undefined;\n\t\t\t\t\t\tconst lineNumber = optionalNumber(data.data.line_number);\n\t\t\t\t\t\tconst lineText = isRecord(data.data.lines) ? optionalString(data.data.lines.text) : undefined;\n\t\t\t\t\t\treturn filePath && lineNumber !== undefined ? [{ filePath, lineNumber, lineText }] : [];\n\t\t\t\t\t} catch {\n\t\t\t\t\t\treturn [];\n\t\t\t\t\t}\n\t\t\t\t});\n\t\t\tsendResult(connection, id, { isDirectory, matches });\n\t\t\treturn;\n\t\t}\n\t\tif (method === \"detectImageMimeType\") {\n\t\t\tconst output = await runBuffered(\"file\", [\"--mime-type\", \"-b\", requireString(params.path, \"path\")], cwd);\n\t\t\tconst mimeType = output.toString(\"utf-8\").trim();\n\t\t\tsendResult(connection, id, {\n\t\t\t\tmimeType: [\"image/jpeg\", \"image/png\", \"image/gif\", \"image/webp\"].includes(mimeType) ? mimeType : null,\n\t\t\t});\n\t\t\treturn;\n\t\t}\n\t\tthrow new Error(`Unknown method: ${method}`);\n\t} catch (error) {\n\t\tsendError(connection, id, error);\n\t}\n}\n\nfunction parseFrames(connection: ClientConnection): void {\n\twhile (connection.buffer.length >= 2) {\n\t\tconst first = connection.buffer[0];\n\t\tconst second = connection.buffer[1];\n\t\tconst opcode = first & 0x0f;\n\t\tconst masked = (second & 0x80) !== 0;\n\t\tlet payloadLength = second & 0x7f;\n\t\tlet offset = 2;\n\t\tif (payloadLength === 126) {\n\t\t\tif (connection.buffer.length < offset + 2) return;\n\t\t\tpayloadLength = connection.buffer.readUInt16BE(offset);\n\t\t\toffset += 2;\n\t\t} else if (payloadLength === 127) {\n\t\t\tif (connection.buffer.length < offset + 8) return;\n\t\t\tconst largeLength = connection.buffer.readBigUInt64BE(offset);\n\t\t\tif (largeLength > BigInt(Number.MAX_SAFE_INTEGER)) {\n\t\t\t\tconnection.socket.destroy(new Error(\"WebSocket frame too large\"));\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tpayloadLength = Number(largeLength);\n\t\t\toffset += 8;\n\t\t}\n\t\tif (!masked) {\n\t\t\tconnection.socket.destroy(new Error(\"Client WebSocket frames must be masked\"));\n\t\t\treturn;\n\t\t}\n\t\tif (connection.buffer.length < offset + 4 + payloadLength) return;\n\t\tconst mask = connection.buffer.subarray(offset, offset + 4);\n\t\toffset += 4;\n\t\tconst payload = Buffer.from(connection.buffer.subarray(offset, offset + payloadLength));\n\t\tconnection.buffer = connection.buffer.subarray(offset + payloadLength);\n\t\tfor (let index = 0; index < payload.length; index++) {\n\t\t\tpayload[index] ^= mask[index % 4];\n\t\t}\n\t\tif (opcode === 0x8) {\n\t\t\tconnection.socket.end();\n\t\t\treturn;\n\t\t}\n\t\tif (opcode !== 0x1) continue;\n\t\tlet message: unknown;\n\t\ttry {\n\t\t\tmessage = JSON.parse(payload.toString(\"utf-8\"));\n\t\t} catch {\n\t\t\tcontinue;\n\t\t}\n\t\tif (isRecord(message) && message.type === \"ping\") {\n\t\t\tsendFrame(connection.socket, { type: \"pong\", timestamp: message.timestamp });\n\t\t\tcontinue;\n\t\t}\n\t\tvoid handleRequest(connection, message as JsonRpcMessage);\n\t}\n}\n\nfunction isAuthorized(request: IncomingMessage): boolean {\n\tif (!token) return true;\n\tif (request.headers.authorization === `Bearer ${token}`) return true;\n\ttry {\n\t\tconst url = new URL(request.url ?? \"/\", `http://${request.headers.host ?? \"localhost\"}`);\n\t\treturn url.searchParams.get(\"token\") === token;\n\t} catch {\n\t\treturn false;\n\t}\n}\n\nconst server = createServer((_request, response) => {\n\tresponse.writeHead(404);\n\tresponse.end(\"pi-daemon only serves WebSocket remote commander connections\\n\");\n});\n\nserver.on(\"upgrade\", (request, socket) => {\n\tconst netSocket = socket as Socket;\n\tif (!isAuthorized(request)) {\n\t\tnetSocket.write(\"HTTP/1.1 401 Unauthorized\\r\\n\\r\\n\");\n\t\tnetSocket.destroy();\n\t\treturn;\n\t}\n\tconst key = request.headers[\"sec-websocket-key\"];\n\tif (typeof key !== \"string\") {\n\t\tnetSocket.write(\"HTTP/1.1 400 Bad Request\\r\\n\\r\\n\");\n\t\tnetSocket.destroy();\n\t\treturn;\n\t}\n\tconst accept = createHash(\"sha1\").update(`${key}258EAFA5-E914-47DA-95CA-C5AB0DC85B11`).digest(\"base64\");\n\tnetSocket.write(\n\t\t[\n\t\t\t\"HTTP/1.1 101 Switching Protocols\",\n\t\t\t\"Upgrade: websocket\",\n\t\t\t\"Connection: Upgrade\",\n\t\t\t`Sec-WebSocket-Accept: ${accept}`,\n\t\t\t\"\",\n\t\t\t\"\",\n\t\t].join(\"\\r\\n\"),\n\t);\n\tconst connection: ClientConnection = { socket: netSocket, buffer: Buffer.alloc(0), execs: new Map() };\n\tnetSocket.on(\"data\", (chunk: Buffer) => {\n\t\tconnection.buffer = Buffer.concat([connection.buffer, chunk]);\n\t\tparseFrames(connection);\n\t});\n\tnetSocket.on(\"close\", () => {\n\t\tfor (const child of connection.execs.values()) {\n\t\t\tchild.kill();\n\t\t}\n\t\tconnection.execs.clear();\n\t});\n});\n\nserver.listen(port, host, () => {\n\tconsole.log(`pi-daemon listening on ws://${host}:${port} cwd=${cwd}`);\n});\n"]}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"","sourcesContent":["#!/usr/bin/env node\n\nimport { type ChildProcessWithoutNullStreams, spawn } from \"node:child_process\";\nimport { createHash } from \"node:crypto\";\nimport { once } from \"node:events\";\nimport { constants, createReadStream, createWriteStream, type WriteStream } from \"node:fs\";\nimport { access, mkdir, readdir, readFile, stat, writeFile } from \"node:fs/promises\";\nimport { createServer, type IncomingMessage } from \"node:http\";\nimport type { Socket } from \"node:net\";\nimport { resolve } from \"node:path\";\n\ninterface JsonRpcMessage {\n\tid?: unknown;\n\tmethod?: unknown;\n\tparams?: unknown;\n}\n\ninterface ClientConnection {\n\tsocket: Socket;\n\tbuffer: Buffer;\n\texecs: Map<string, ChildProcessWithoutNullStreams>;\n\tuploads: Map<string, WriteStream>;\n}\n\nconst port = Number(process.env.PORT ?? process.env.PI_DAEMON_PORT ?? \"8787\");\nconst host = process.env.HOST ?? process.env.PI_DAEMON_HOST ?? \"127.0.0.1\";\nconst cwd = resolve(process.env.PI_DAEMON_CWD ?? process.cwd());\nconst token = process.env.PI_DAEMON_TOKEN;\nconst fileTransferChunkSize = 64 * 1024;\n\nfunction createFrame(payload: unknown): Buffer {\n\tconst data = Buffer.from(JSON.stringify(payload));\n\tlet header: Buffer;\n\tif (data.length < 126) {\n\t\theader = Buffer.from([0x81, data.length]);\n\t} else if (data.length <= 0xffff) {\n\t\theader = Buffer.alloc(4);\n\t\theader[0] = 0x81;\n\t\theader[1] = 126;\n\t\theader.writeUInt16BE(data.length, 2);\n\t} else {\n\t\theader = Buffer.alloc(10);\n\t\theader[0] = 0x81;\n\t\theader[1] = 127;\n\t\theader.writeBigUInt64BE(BigInt(data.length), 2);\n\t}\n\treturn Buffer.concat([header, data]);\n}\n\nfunction sendFrame(socket: Socket, payload: unknown): void {\n\tsocket.write(createFrame(payload));\n}\n\nasync function sendFrameAsync(socket: Socket, payload: unknown): Promise<void> {\n\tif (!socket.write(createFrame(payload))) {\n\t\tawait once(socket, \"drain\");\n\t}\n}\n\nfunction sendResult(connection: ClientConnection, id: string, result: unknown): void {\n\tsendFrame(connection.socket, { id, result });\n}\n\nfunction sendError(connection: ClientConnection, id: string, error: unknown): void {\n\tconst message = error instanceof Error ? error.message : String(error);\n\tsendFrame(connection.socket, { id, error: { message } });\n}\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n\treturn typeof value === \"object\" && value !== null;\n}\n\nfunction requireString(value: unknown, name: string): string {\n\tif (typeof value !== \"string\") throw new Error(`Missing string param: ${name}`);\n\treturn value;\n}\n\nfunction optionalString(value: unknown): string | undefined {\n\treturn typeof value === \"string\" ? value : undefined;\n}\n\nfunction optionalNumber(value: unknown): number | undefined {\n\treturn typeof value === \"number\" ? value : undefined;\n}\n\nfunction accessModeToFsMode(mode: unknown): number {\n\tswitch (mode) {\n\t\tcase \"read\":\n\t\t\treturn constants.R_OK;\n\t\tcase \"write\":\n\t\t\treturn constants.W_OK;\n\t\tcase \"readwrite\":\n\t\t\treturn constants.R_OK | constants.W_OK;\n\t\tcase \"exists\":\n\t\tcase undefined:\n\t\t\treturn constants.F_OK;\n\t\tdefault:\n\t\t\tthrow new Error(`Invalid access mode: ${String(mode)}`);\n\t}\n}\n\nfunction buildFdArgs(pattern: string, searchPath: string, limit: number): string[] {\n\tconst args: string[] = [\"--glob\", \"--color=never\", \"--hidden\", \"--no-require-git\", \"--max-results\", String(limit)];\n\tlet effectivePattern = pattern;\n\tif (pattern.includes(\"/\")) {\n\t\targs.push(\"--full-path\");\n\t\tif (!pattern.startsWith(\"/\") && !pattern.startsWith(\"**/\") && pattern !== \"**\") {\n\t\t\teffectivePattern = `**/${pattern}`;\n\t\t}\n\t}\n\targs.push(\"--\", effectivePattern, searchPath);\n\treturn args;\n}\n\nfunction buildRgArgs(params: Record<string, unknown>): string[] {\n\tconst pattern = requireString(params.pattern, \"pattern\");\n\tconst path = requireString(params.path, \"path\");\n\tconst args: string[] = [\"--json\", \"--line-number\", \"--color=never\", \"--hidden\"];\n\tif (params.ignoreCase === true) args.push(\"--ignore-case\");\n\tif (params.literal === true) args.push(\"--fixed-strings\");\n\tconst glob = optionalString(params.glob);\n\tif (glob) args.push(\"--glob\", glob);\n\targs.push(\"--\", pattern, path);\n\treturn args;\n}\n\nasync function runBuffered(command: string, args: string[], runCwd: string): Promise<Buffer> {\n\treturn new Promise((resolvePromise, reject) => {\n\t\tconst child = spawn(command, args, { cwd: runCwd, stdio: [\"ignore\", \"pipe\", \"pipe\"] });\n\t\tconst stdout: Buffer[] = [];\n\t\tconst stderr: Buffer[] = [];\n\t\tchild.stdout.on(\"data\", (data: Buffer) => stdout.push(data));\n\t\tchild.stderr.on(\"data\", (data: Buffer) => stderr.push(data));\n\t\tchild.on(\"error\", reject);\n\t\tchild.on(\"close\", (code) => {\n\t\t\tif (code === 0) {\n\t\t\t\tresolvePromise(Buffer.concat(stdout));\n\t\t\t\treturn;\n\t\t\t}\n\t\t\treject(new Error(Buffer.concat(stderr).toString(\"utf-8\").trim() || `${command} exited with code ${code}`));\n\t\t});\n\t});\n}\n\nfunction handleExec(connection: ClientConnection, id: string, params: Record<string, unknown>): void {\n\tconst command = requireString(params.command, \"command\");\n\tconst runCwd = optionalString(params.cwd) ?? cwd;\n\tconst timeout = optionalNumber(params.timeout);\n\tconst env = isRecord(params.env)\n\t\t? Object.fromEntries(\n\t\t\t\tObject.entries(params.env).filter((entry): entry is [string, string] => typeof entry[1] === \"string\"),\n\t\t\t)\n\t\t: undefined;\n\tconst child = spawn(\"bash\", [\"-lc\", command], {\n\t\tcwd: runCwd,\n\t\tdetached: process.platform !== \"win32\",\n\t\tenv: env ? { ...process.env, ...env } : process.env,\n\t});\n\tconnection.execs.set(id, child);\n\tlet timeoutHandle: NodeJS.Timeout | undefined;\n\tif (timeout !== undefined && timeout > 0) {\n\t\ttimeoutHandle = setTimeout(() => {\n\t\t\tif (process.platform !== \"win32\" && child.pid) {\n\t\t\t\ttry {\n\t\t\t\t\tprocess.kill(-child.pid);\n\t\t\t\t} catch {\n\t\t\t\t\tchild.kill();\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tchild.kill();\n\t\t\t}\n\t\t}, timeout * 1000);\n\t}\n\tchild.stdout.on(\"data\", (data: Buffer) => {\n\t\tsendFrame(connection.socket, { id, event: \"data\", stream: \"stdout\", dataBase64: data.toString(\"base64\") });\n\t});\n\tchild.stderr.on(\"data\", (data: Buffer) => {\n\t\tsendFrame(connection.socket, { id, event: \"data\", stream: \"stderr\", dataBase64: data.toString(\"base64\") });\n\t});\n\tchild.on(\"error\", (error) => {\n\t\tif (timeoutHandle) clearTimeout(timeoutHandle);\n\t\tconnection.execs.delete(id);\n\t\tsendFrame(connection.socket, { id, event: \"error\", error: { message: error.message } });\n\t});\n\tchild.on(\"close\", (code) => {\n\t\tif (timeoutHandle) clearTimeout(timeoutHandle);\n\t\tconnection.execs.delete(id);\n\t\tsendFrame(connection.socket, { id, event: \"exit\", exitCode: code });\n\t});\n}\n\nfunction cancelExec(connection: ClientConnection, id: string): void {\n\tconst child = connection.execs.get(id);\n\tif (!child) return;\n\tconnection.execs.delete(id);\n\tif (process.platform !== \"win32\" && child.pid) {\n\t\ttry {\n\t\t\tprocess.kill(-child.pid);\n\t\t} catch {\n\t\t\tchild.kill();\n\t\t}\n\t} else {\n\t\tchild.kill();\n\t}\n\tsendFrame(connection.socket, { id, event: \"exit\", exitCode: null, cancelled: true });\n}\n\nasync function handleDownloadFile(connection: ClientConnection, id: string, path: string): Promise<void> {\n\ttry {\n\t\tfor await (const chunk of createReadStream(path, { highWaterMark: fileTransferChunkSize })) {\n\t\t\tconst buffer = typeof chunk === \"string\" ? Buffer.from(chunk) : chunk;\n\t\t\tawait sendFrameAsync(connection.socket, { id, event: \"fileData\", dataBase64: buffer.toString(\"base64\") });\n\t\t}\n\t\tawait sendFrameAsync(connection.socket, { id, event: \"fileEnd\" });\n\t} catch (error) {\n\t\tsendFrame(connection.socket, {\n\t\t\tid,\n\t\t\tevent: \"fileError\",\n\t\t\terror: { message: error instanceof Error ? error.message : String(error) },\n\t\t});\n\t}\n}\n\nfunction writeUploadChunk(stream: WriteStream, chunk: Buffer): Promise<void> {\n\treturn new Promise((resolve, reject) => {\n\t\tconst onError = (error: Error) => {\n\t\t\tstream.off(\"error\", onError);\n\t\t\treject(error);\n\t\t};\n\t\tstream.once(\"error\", onError);\n\t\tstream.write(chunk, (error) => {\n\t\t\tstream.off(\"error\", onError);\n\t\t\tif (error) {\n\t\t\t\treject(error);\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tresolve();\n\t\t});\n\t});\n}\n\nfunction closeUploadStream(stream: WriteStream): Promise<void> {\n\treturn new Promise((resolve, reject) => {\n\t\tconst onError = (error: Error) => {\n\t\t\tstream.off(\"error\", onError);\n\t\t\treject(error);\n\t\t};\n\t\tstream.once(\"error\", onError);\n\t\tstream.end(() => {\n\t\t\tstream.off(\"error\", onError);\n\t\t\tresolve();\n\t\t});\n\t});\n}\n\nasync function handleRequest(connection: ClientConnection, message: JsonRpcMessage): Promise<void> {\n\tconst id = requireString(message.id, \"id\");\n\tconst method = requireString(message.method, \"method\");\n\tconst params = isRecord(message.params) ? message.params : {};\n\ttry {\n\t\tif (method === \"cancel\") {\n\t\t\tcancelExec(connection, id);\n\t\t\treturn;\n\t\t}\n\t\tif (method === \"exec\") {\n\t\t\thandleExec(connection, id, params);\n\t\t\treturn;\n\t\t}\n\t\tif (method === \"capabilities\") {\n\t\t\tsendResult(connection, id, {\n\t\t\t\tcwd,\n\t\t\t\tfeatures: {\n\t\t\t\t\texec: true,\n\t\t\t\t\tfiles: true,\n\t\t\t\t\tfileTransfer: true,\n\t\t\t\t\tglob: true,\n\t\t\t\t\tgrep: true,\n\t\t\t\t\tinstructions: false,\n\t\t\t\t},\n\t\t\t});\n\t\t\treturn;\n\t\t}\n\t\tif (method === \"access\") {\n\t\t\tawait access(requireString(params.path, \"path\"), accessModeToFsMode(params.mode));\n\t\t\tsendResult(connection, id, {});\n\t\t\treturn;\n\t\t}\n\t\tif (method === \"readFile\") {\n\t\t\tconst content = await readFile(requireString(params.path, \"path\"));\n\t\t\tsendResult(connection, id, { contentBase64: content.toString(\"base64\") });\n\t\t\treturn;\n\t\t}\n\t\tif (method === \"writeFile\") {\n\t\t\tawait writeFile(\n\t\t\t\trequireString(params.path, \"path\"),\n\t\t\t\tBuffer.from(requireString(params.contentBase64, \"contentBase64\"), \"base64\"),\n\t\t\t);\n\t\t\tsendResult(connection, id, {});\n\t\t\treturn;\n\t\t}\n\t\tif (method === \"downloadFile\") {\n\t\t\tawait handleDownloadFile(connection, id, requireString(params.path, \"path\"));\n\t\t\treturn;\n\t\t}\n\t\tif (method === \"uploadFileStart\") {\n\t\t\tconst path = requireString(params.path, \"path\");\n\t\t\tconst stream = createWriteStream(path);\n\t\t\tstream.on(\"error\", () => undefined);\n\t\t\tconnection.uploads.set(id, stream);\n\t\t\tsendResult(connection, id, {});\n\t\t\treturn;\n\t\t}\n\t\tif (method === \"uploadFileChunk\") {\n\t\t\tconst uploadId = requireString(params.uploadId, \"uploadId\");\n\t\t\tconst stream = connection.uploads.get(uploadId);\n\t\t\tif (!stream) throw new Error(`Unknown upload: ${uploadId}`);\n\t\t\tawait writeUploadChunk(stream, Buffer.from(requireString(params.dataBase64, \"dataBase64\"), \"base64\"));\n\t\t\tsendResult(connection, id, {});\n\t\t\treturn;\n\t\t}\n\t\tif (method === \"uploadFileEnd\") {\n\t\t\tconst uploadId = requireString(params.uploadId, \"uploadId\");\n\t\t\tconst stream = connection.uploads.get(uploadId);\n\t\t\tif (!stream) throw new Error(`Unknown upload: ${uploadId}`);\n\t\t\tconnection.uploads.delete(uploadId);\n\t\t\tawait closeUploadStream(stream);\n\t\t\tsendResult(connection, id, {});\n\t\t\treturn;\n\t\t}\n\t\tif (method === \"uploadFileCancel\") {\n\t\t\tconst uploadId = requireString(params.uploadId, \"uploadId\");\n\t\t\tconst stream = connection.uploads.get(uploadId);\n\t\t\tif (stream) {\n\t\t\t\tconnection.uploads.delete(uploadId);\n\t\t\t\tstream.destroy();\n\t\t\t}\n\t\t\tsendResult(connection, id, {});\n\t\t\treturn;\n\t\t}\n\t\tif (method === \"mkdir\") {\n\t\t\tawait mkdir(requireString(params.path, \"path\"), { recursive: params.recursive === true });\n\t\t\tsendResult(connection, id, {});\n\t\t\treturn;\n\t\t}\n\t\tif (method === \"stat\") {\n\t\t\tconst result = await stat(requireString(params.path, \"path\"));\n\t\t\tsendResult(connection, id, { isDirectory: result.isDirectory(), isFile: result.isFile() });\n\t\t\treturn;\n\t\t}\n\t\tif (method === \"readdir\") {\n\t\t\tsendResult(connection, id, { entries: await readdir(requireString(params.path, \"path\")) });\n\t\t\treturn;\n\t\t}\n\t\tif (method === \"glob\") {\n\t\t\tconst pattern = requireString(params.pattern, \"pattern\");\n\t\t\tconst runCwd = requireString(params.cwd, \"cwd\");\n\t\t\tconst limit = optionalNumber(params.limit) ?? 1000;\n\t\t\tconst output = await runBuffered(\"fd\", buildFdArgs(pattern, runCwd, limit), runCwd);\n\t\t\tsendResult(connection, id, { matches: output.toString(\"utf-8\").split(\"\\n\").filter(Boolean) });\n\t\t\treturn;\n\t\t}\n\t\tif (method === \"grep\") {\n\t\t\tconst pathParam = requireString(params.path, \"path\");\n\t\t\tconst isDirectory = (await stat(pathParam)).isDirectory();\n\t\t\tconst output = await runBuffered(\"rg\", buildRgArgs(params), cwd);\n\t\t\tconst matches = output\n\t\t\t\t.toString(\"utf-8\")\n\t\t\t\t.split(\"\\n\")\n\t\t\t\t.filter(Boolean)\n\t\t\t\t.flatMap((line) => {\n\t\t\t\t\ttry {\n\t\t\t\t\t\tconst data = JSON.parse(line) as unknown;\n\t\t\t\t\t\tif (!isRecord(data) || data.type !== \"match\" || !isRecord(data.data)) return [];\n\t\t\t\t\t\tconst filePath = isRecord(data.data.path) ? optionalString(data.data.path.text) : undefined;\n\t\t\t\t\t\tconst lineNumber = optionalNumber(data.data.line_number);\n\t\t\t\t\t\tconst lineText = isRecord(data.data.lines) ? optionalString(data.data.lines.text) : undefined;\n\t\t\t\t\t\treturn filePath && lineNumber !== undefined ? [{ filePath, lineNumber, lineText }] : [];\n\t\t\t\t\t} catch {\n\t\t\t\t\t\treturn [];\n\t\t\t\t\t}\n\t\t\t\t});\n\t\t\tsendResult(connection, id, { isDirectory, matches });\n\t\t\treturn;\n\t\t}\n\t\tif (method === \"detectImageMimeType\") {\n\t\t\tconst output = await runBuffered(\"file\", [\"--mime-type\", \"-b\", requireString(params.path, \"path\")], cwd);\n\t\t\tconst mimeType = output.toString(\"utf-8\").trim();\n\t\t\tsendResult(connection, id, {\n\t\t\t\tmimeType: [\"image/jpeg\", \"image/png\", \"image/gif\", \"image/webp\"].includes(mimeType) ? mimeType : null,\n\t\t\t});\n\t\t\treturn;\n\t\t}\n\t\tthrow new Error(`Unknown method: ${method}`);\n\t} catch (error) {\n\t\tsendError(connection, id, error);\n\t}\n}\n\nfunction parseFrames(connection: ClientConnection): void {\n\twhile (connection.buffer.length >= 2) {\n\t\tconst first = connection.buffer[0];\n\t\tconst second = connection.buffer[1];\n\t\tconst opcode = first & 0x0f;\n\t\tconst masked = (second & 0x80) !== 0;\n\t\tlet payloadLength = second & 0x7f;\n\t\tlet offset = 2;\n\t\tif (payloadLength === 126) {\n\t\t\tif (connection.buffer.length < offset + 2) return;\n\t\t\tpayloadLength = connection.buffer.readUInt16BE(offset);\n\t\t\toffset += 2;\n\t\t} else if (payloadLength === 127) {\n\t\t\tif (connection.buffer.length < offset + 8) return;\n\t\t\tconst largeLength = connection.buffer.readBigUInt64BE(offset);\n\t\t\tif (largeLength > BigInt(Number.MAX_SAFE_INTEGER)) {\n\t\t\t\tconnection.socket.destroy(new Error(\"WebSocket frame too large\"));\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tpayloadLength = Number(largeLength);\n\t\t\toffset += 8;\n\t\t}\n\t\tif (!masked) {\n\t\t\tconnection.socket.destroy(new Error(\"Client WebSocket frames must be masked\"));\n\t\t\treturn;\n\t\t}\n\t\tif (connection.buffer.length < offset + 4 + payloadLength) return;\n\t\tconst mask = connection.buffer.subarray(offset, offset + 4);\n\t\toffset += 4;\n\t\tconst payload = Buffer.from(connection.buffer.subarray(offset, offset + payloadLength));\n\t\tconnection.buffer = connection.buffer.subarray(offset + payloadLength);\n\t\tfor (let index = 0; index < payload.length; index++) {\n\t\t\tpayload[index] ^= mask[index % 4];\n\t\t}\n\t\tif (opcode === 0x8) {\n\t\t\tconnection.socket.end();\n\t\t\treturn;\n\t\t}\n\t\tif (opcode !== 0x1) continue;\n\t\tlet message: unknown;\n\t\ttry {\n\t\t\tmessage = JSON.parse(payload.toString(\"utf-8\"));\n\t\t} catch {\n\t\t\tcontinue;\n\t\t}\n\t\tif (isRecord(message) && message.type === \"ping\") {\n\t\t\tsendFrame(connection.socket, { type: \"pong\", timestamp: message.timestamp });\n\t\t\tcontinue;\n\t\t}\n\t\tvoid handleRequest(connection, message as JsonRpcMessage);\n\t}\n}\n\nfunction isAuthorized(request: IncomingMessage): boolean {\n\tif (!token) return true;\n\tif (request.headers.authorization === `Bearer ${token}`) return true;\n\ttry {\n\t\tconst url = new URL(request.url ?? \"/\", `http://${request.headers.host ?? \"localhost\"}`);\n\t\treturn url.searchParams.get(\"token\") === token;\n\t} catch {\n\t\treturn false;\n\t}\n}\n\nconst server = createServer((_request, response) => {\n\tresponse.writeHead(404);\n\tresponse.end(\"pi-daemon only serves WebSocket remote commander connections\\n\");\n});\n\nserver.on(\"upgrade\", (request, socket) => {\n\tconst netSocket = socket as Socket;\n\tif (!isAuthorized(request)) {\n\t\tnetSocket.write(\"HTTP/1.1 401 Unauthorized\\r\\n\\r\\n\");\n\t\tnetSocket.destroy();\n\t\treturn;\n\t}\n\tconst key = request.headers[\"sec-websocket-key\"];\n\tif (typeof key !== \"string\") {\n\t\tnetSocket.write(\"HTTP/1.1 400 Bad Request\\r\\n\\r\\n\");\n\t\tnetSocket.destroy();\n\t\treturn;\n\t}\n\tconst accept = createHash(\"sha1\").update(`${key}258EAFA5-E914-47DA-95CA-C5AB0DC85B11`).digest(\"base64\");\n\tnetSocket.write(\n\t\t[\n\t\t\t\"HTTP/1.1 101 Switching Protocols\",\n\t\t\t\"Upgrade: websocket\",\n\t\t\t\"Connection: Upgrade\",\n\t\t\t`Sec-WebSocket-Accept: ${accept}`,\n\t\t\t\"\",\n\t\t\t\"\",\n\t\t].join(\"\\r\\n\"),\n\t);\n\tconst connection: ClientConnection = {\n\t\tsocket: netSocket,\n\t\tbuffer: Buffer.alloc(0),\n\t\texecs: new Map(),\n\t\tuploads: new Map(),\n\t};\n\tnetSocket.on(\"data\", (chunk: Buffer) => {\n\t\tconnection.buffer = Buffer.concat([connection.buffer, chunk]);\n\t\tparseFrames(connection);\n\t});\n\tnetSocket.on(\"close\", () => {\n\t\tfor (const child of connection.execs.values()) {\n\t\t\tchild.kill();\n\t\t}\n\t\tconnection.execs.clear();\n\t\tfor (const stream of connection.uploads.values()) {\n\t\t\tstream.destroy();\n\t\t}\n\t\tconnection.uploads.clear();\n\t});\n});\n\nserver.listen(port, host, () => {\n\tconsole.log(`pi-daemon listening on ws://${host}:${port} cwd=${cwd}`);\n});\n"]}
|
package/dist/index.js
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { spawn } from "node:child_process";
|
|
3
3
|
import { createHash } from "node:crypto";
|
|
4
|
-
import {
|
|
4
|
+
import { once } from "node:events";
|
|
5
|
+
import { constants, createReadStream, createWriteStream } from "node:fs";
|
|
5
6
|
import { access, mkdir, readdir, readFile, stat, writeFile } from "node:fs/promises";
|
|
6
7
|
import { createServer } from "node:http";
|
|
7
8
|
import { resolve } from "node:path";
|
|
@@ -9,7 +10,8 @@ const port = Number(process.env.PORT ?? process.env.PI_DAEMON_PORT ?? "8787");
|
|
|
9
10
|
const host = process.env.HOST ?? process.env.PI_DAEMON_HOST ?? "127.0.0.1";
|
|
10
11
|
const cwd = resolve(process.env.PI_DAEMON_CWD ?? process.cwd());
|
|
11
12
|
const token = process.env.PI_DAEMON_TOKEN;
|
|
12
|
-
|
|
13
|
+
const fileTransferChunkSize = 64 * 1024;
|
|
14
|
+
function createFrame(payload) {
|
|
13
15
|
const data = Buffer.from(JSON.stringify(payload));
|
|
14
16
|
let header;
|
|
15
17
|
if (data.length < 126) {
|
|
@@ -27,7 +29,15 @@ function sendFrame(socket, payload) {
|
|
|
27
29
|
header[1] = 127;
|
|
28
30
|
header.writeBigUInt64BE(BigInt(data.length), 2);
|
|
29
31
|
}
|
|
30
|
-
|
|
32
|
+
return Buffer.concat([header, data]);
|
|
33
|
+
}
|
|
34
|
+
function sendFrame(socket, payload) {
|
|
35
|
+
socket.write(createFrame(payload));
|
|
36
|
+
}
|
|
37
|
+
async function sendFrameAsync(socket, payload) {
|
|
38
|
+
if (!socket.write(createFrame(payload))) {
|
|
39
|
+
await once(socket, "drain");
|
|
40
|
+
}
|
|
31
41
|
}
|
|
32
42
|
function sendResult(connection, id, result) {
|
|
33
43
|
sendFrame(connection.socket, { id, result });
|
|
@@ -174,6 +184,52 @@ function cancelExec(connection, id) {
|
|
|
174
184
|
}
|
|
175
185
|
sendFrame(connection.socket, { id, event: "exit", exitCode: null, cancelled: true });
|
|
176
186
|
}
|
|
187
|
+
async function handleDownloadFile(connection, id, path) {
|
|
188
|
+
try {
|
|
189
|
+
for await (const chunk of createReadStream(path, { highWaterMark: fileTransferChunkSize })) {
|
|
190
|
+
const buffer = typeof chunk === "string" ? Buffer.from(chunk) : chunk;
|
|
191
|
+
await sendFrameAsync(connection.socket, { id, event: "fileData", dataBase64: buffer.toString("base64") });
|
|
192
|
+
}
|
|
193
|
+
await sendFrameAsync(connection.socket, { id, event: "fileEnd" });
|
|
194
|
+
}
|
|
195
|
+
catch (error) {
|
|
196
|
+
sendFrame(connection.socket, {
|
|
197
|
+
id,
|
|
198
|
+
event: "fileError",
|
|
199
|
+
error: { message: error instanceof Error ? error.message : String(error) },
|
|
200
|
+
});
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
function writeUploadChunk(stream, chunk) {
|
|
204
|
+
return new Promise((resolve, reject) => {
|
|
205
|
+
const onError = (error) => {
|
|
206
|
+
stream.off("error", onError);
|
|
207
|
+
reject(error);
|
|
208
|
+
};
|
|
209
|
+
stream.once("error", onError);
|
|
210
|
+
stream.write(chunk, (error) => {
|
|
211
|
+
stream.off("error", onError);
|
|
212
|
+
if (error) {
|
|
213
|
+
reject(error);
|
|
214
|
+
return;
|
|
215
|
+
}
|
|
216
|
+
resolve();
|
|
217
|
+
});
|
|
218
|
+
});
|
|
219
|
+
}
|
|
220
|
+
function closeUploadStream(stream) {
|
|
221
|
+
return new Promise((resolve, reject) => {
|
|
222
|
+
const onError = (error) => {
|
|
223
|
+
stream.off("error", onError);
|
|
224
|
+
reject(error);
|
|
225
|
+
};
|
|
226
|
+
stream.once("error", onError);
|
|
227
|
+
stream.end(() => {
|
|
228
|
+
stream.off("error", onError);
|
|
229
|
+
resolve();
|
|
230
|
+
});
|
|
231
|
+
});
|
|
232
|
+
}
|
|
177
233
|
async function handleRequest(connection, message) {
|
|
178
234
|
const id = requireString(message.id, "id");
|
|
179
235
|
const method = requireString(message.method, "method");
|
|
@@ -190,7 +246,14 @@ async function handleRequest(connection, message) {
|
|
|
190
246
|
if (method === "capabilities") {
|
|
191
247
|
sendResult(connection, id, {
|
|
192
248
|
cwd,
|
|
193
|
-
features: {
|
|
249
|
+
features: {
|
|
250
|
+
exec: true,
|
|
251
|
+
files: true,
|
|
252
|
+
fileTransfer: true,
|
|
253
|
+
glob: true,
|
|
254
|
+
grep: true,
|
|
255
|
+
instructions: false,
|
|
256
|
+
},
|
|
194
257
|
});
|
|
195
258
|
return;
|
|
196
259
|
}
|
|
@@ -209,6 +272,47 @@ async function handleRequest(connection, message) {
|
|
|
209
272
|
sendResult(connection, id, {});
|
|
210
273
|
return;
|
|
211
274
|
}
|
|
275
|
+
if (method === "downloadFile") {
|
|
276
|
+
await handleDownloadFile(connection, id, requireString(params.path, "path"));
|
|
277
|
+
return;
|
|
278
|
+
}
|
|
279
|
+
if (method === "uploadFileStart") {
|
|
280
|
+
const path = requireString(params.path, "path");
|
|
281
|
+
const stream = createWriteStream(path);
|
|
282
|
+
stream.on("error", () => undefined);
|
|
283
|
+
connection.uploads.set(id, stream);
|
|
284
|
+
sendResult(connection, id, {});
|
|
285
|
+
return;
|
|
286
|
+
}
|
|
287
|
+
if (method === "uploadFileChunk") {
|
|
288
|
+
const uploadId = requireString(params.uploadId, "uploadId");
|
|
289
|
+
const stream = connection.uploads.get(uploadId);
|
|
290
|
+
if (!stream)
|
|
291
|
+
throw new Error(`Unknown upload: ${uploadId}`);
|
|
292
|
+
await writeUploadChunk(stream, Buffer.from(requireString(params.dataBase64, "dataBase64"), "base64"));
|
|
293
|
+
sendResult(connection, id, {});
|
|
294
|
+
return;
|
|
295
|
+
}
|
|
296
|
+
if (method === "uploadFileEnd") {
|
|
297
|
+
const uploadId = requireString(params.uploadId, "uploadId");
|
|
298
|
+
const stream = connection.uploads.get(uploadId);
|
|
299
|
+
if (!stream)
|
|
300
|
+
throw new Error(`Unknown upload: ${uploadId}`);
|
|
301
|
+
connection.uploads.delete(uploadId);
|
|
302
|
+
await closeUploadStream(stream);
|
|
303
|
+
sendResult(connection, id, {});
|
|
304
|
+
return;
|
|
305
|
+
}
|
|
306
|
+
if (method === "uploadFileCancel") {
|
|
307
|
+
const uploadId = requireString(params.uploadId, "uploadId");
|
|
308
|
+
const stream = connection.uploads.get(uploadId);
|
|
309
|
+
if (stream) {
|
|
310
|
+
connection.uploads.delete(uploadId);
|
|
311
|
+
stream.destroy();
|
|
312
|
+
}
|
|
313
|
+
sendResult(connection, id, {});
|
|
314
|
+
return;
|
|
315
|
+
}
|
|
212
316
|
if (method === "mkdir") {
|
|
213
317
|
await mkdir(requireString(params.path, "path"), { recursive: params.recursive === true });
|
|
214
318
|
sendResult(connection, id, {});
|
|
@@ -367,7 +471,12 @@ server.on("upgrade", (request, socket) => {
|
|
|
367
471
|
"",
|
|
368
472
|
"",
|
|
369
473
|
].join("\r\n"));
|
|
370
|
-
const connection = {
|
|
474
|
+
const connection = {
|
|
475
|
+
socket: netSocket,
|
|
476
|
+
buffer: Buffer.alloc(0),
|
|
477
|
+
execs: new Map(),
|
|
478
|
+
uploads: new Map(),
|
|
479
|
+
};
|
|
371
480
|
netSocket.on("data", (chunk) => {
|
|
372
481
|
connection.buffer = Buffer.concat([connection.buffer, chunk]);
|
|
373
482
|
parseFrames(connection);
|
|
@@ -377,6 +486,10 @@ server.on("upgrade", (request, socket) => {
|
|
|
377
486
|
child.kill();
|
|
378
487
|
}
|
|
379
488
|
connection.execs.clear();
|
|
489
|
+
for (const stream of connection.uploads.values()) {
|
|
490
|
+
stream.destroy();
|
|
491
|
+
}
|
|
492
|
+
connection.uploads.clear();
|
|
380
493
|
});
|
|
381
494
|
});
|
|
382
495
|
server.listen(port, host, () => {
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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","sourcesContent":["#!/usr/bin/env node\n\nimport { type ChildProcessWithoutNullStreams, spawn } from \"node:child_process\";\nimport { createHash } from \"node:crypto\";\nimport { constants } from \"node:fs\";\nimport { access, mkdir, readdir, readFile, stat, writeFile } from \"node:fs/promises\";\nimport { createServer, type IncomingMessage } from \"node:http\";\nimport type { Socket } from \"node:net\";\nimport { resolve } from \"node:path\";\n\ninterface JsonRpcMessage {\n\tid?: unknown;\n\tmethod?: unknown;\n\tparams?: unknown;\n}\n\ninterface ClientConnection {\n\tsocket: Socket;\n\tbuffer: Buffer;\n\texecs: Map<string, ChildProcessWithoutNullStreams>;\n}\n\nconst port = Number(process.env.PORT ?? process.env.PI_DAEMON_PORT ?? \"8787\");\nconst host = process.env.HOST ?? process.env.PI_DAEMON_HOST ?? \"127.0.0.1\";\nconst cwd = resolve(process.env.PI_DAEMON_CWD ?? process.cwd());\nconst token = process.env.PI_DAEMON_TOKEN;\n\nfunction sendFrame(socket: Socket, payload: unknown): void {\n\tconst data = Buffer.from(JSON.stringify(payload));\n\tlet header: Buffer;\n\tif (data.length < 126) {\n\t\theader = Buffer.from([0x81, data.length]);\n\t} else if (data.length <= 0xffff) {\n\t\theader = Buffer.alloc(4);\n\t\theader[0] = 0x81;\n\t\theader[1] = 126;\n\t\theader.writeUInt16BE(data.length, 2);\n\t} else {\n\t\theader = Buffer.alloc(10);\n\t\theader[0] = 0x81;\n\t\theader[1] = 127;\n\t\theader.writeBigUInt64BE(BigInt(data.length), 2);\n\t}\n\tsocket.write(Buffer.concat([header, data]));\n}\n\nfunction sendResult(connection: ClientConnection, id: string, result: unknown): void {\n\tsendFrame(connection.socket, { id, result });\n}\n\nfunction sendError(connection: ClientConnection, id: string, error: unknown): void {\n\tconst message = error instanceof Error ? error.message : String(error);\n\tsendFrame(connection.socket, { id, error: { message } });\n}\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n\treturn typeof value === \"object\" && value !== null;\n}\n\nfunction requireString(value: unknown, name: string): string {\n\tif (typeof value !== \"string\") throw new Error(`Missing string param: ${name}`);\n\treturn value;\n}\n\nfunction optionalString(value: unknown): string | undefined {\n\treturn typeof value === \"string\" ? value : undefined;\n}\n\nfunction optionalNumber(value: unknown): number | undefined {\n\treturn typeof value === \"number\" ? value : undefined;\n}\n\nfunction accessModeToFsMode(mode: unknown): number {\n\tswitch (mode) {\n\t\tcase \"read\":\n\t\t\treturn constants.R_OK;\n\t\tcase \"write\":\n\t\t\treturn constants.W_OK;\n\t\tcase \"readwrite\":\n\t\t\treturn constants.R_OK | constants.W_OK;\n\t\tcase \"exists\":\n\t\tcase undefined:\n\t\t\treturn constants.F_OK;\n\t\tdefault:\n\t\t\tthrow new Error(`Invalid access mode: ${String(mode)}`);\n\t}\n}\n\nfunction buildFdArgs(pattern: string, searchPath: string, limit: number): string[] {\n\tconst args: string[] = [\"--glob\", \"--color=never\", \"--hidden\", \"--no-require-git\", \"--max-results\", String(limit)];\n\tlet effectivePattern = pattern;\n\tif (pattern.includes(\"/\")) {\n\t\targs.push(\"--full-path\");\n\t\tif (!pattern.startsWith(\"/\") && !pattern.startsWith(\"**/\") && pattern !== \"**\") {\n\t\t\teffectivePattern = `**/${pattern}`;\n\t\t}\n\t}\n\targs.push(\"--\", effectivePattern, searchPath);\n\treturn args;\n}\n\nfunction buildRgArgs(params: Record<string, unknown>): string[] {\n\tconst pattern = requireString(params.pattern, \"pattern\");\n\tconst path = requireString(params.path, \"path\");\n\tconst args: string[] = [\"--json\", \"--line-number\", \"--color=never\", \"--hidden\"];\n\tif (params.ignoreCase === true) args.push(\"--ignore-case\");\n\tif (params.literal === true) args.push(\"--fixed-strings\");\n\tconst glob = optionalString(params.glob);\n\tif (glob) args.push(\"--glob\", glob);\n\targs.push(\"--\", pattern, path);\n\treturn args;\n}\n\nasync function runBuffered(command: string, args: string[], runCwd: string): Promise<Buffer> {\n\treturn new Promise((resolvePromise, reject) => {\n\t\tconst child = spawn(command, args, { cwd: runCwd, stdio: [\"ignore\", \"pipe\", \"pipe\"] });\n\t\tconst stdout: Buffer[] = [];\n\t\tconst stderr: Buffer[] = [];\n\t\tchild.stdout.on(\"data\", (data: Buffer) => stdout.push(data));\n\t\tchild.stderr.on(\"data\", (data: Buffer) => stderr.push(data));\n\t\tchild.on(\"error\", reject);\n\t\tchild.on(\"close\", (code) => {\n\t\t\tif (code === 0) {\n\t\t\t\tresolvePromise(Buffer.concat(stdout));\n\t\t\t\treturn;\n\t\t\t}\n\t\t\treject(new Error(Buffer.concat(stderr).toString(\"utf-8\").trim() || `${command} exited with code ${code}`));\n\t\t});\n\t});\n}\n\nfunction handleExec(connection: ClientConnection, id: string, params: Record<string, unknown>): void {\n\tconst command = requireString(params.command, \"command\");\n\tconst runCwd = optionalString(params.cwd) ?? cwd;\n\tconst timeout = optionalNumber(params.timeout);\n\tconst env = isRecord(params.env)\n\t\t? Object.fromEntries(\n\t\t\t\tObject.entries(params.env).filter((entry): entry is [string, string] => typeof entry[1] === \"string\"),\n\t\t\t)\n\t\t: undefined;\n\tconst child = spawn(\"bash\", [\"-lc\", command], {\n\t\tcwd: runCwd,\n\t\tdetached: process.platform !== \"win32\",\n\t\tenv: env ? { ...process.env, ...env } : process.env,\n\t});\n\tconnection.execs.set(id, child);\n\tlet timeoutHandle: NodeJS.Timeout | undefined;\n\tif (timeout !== undefined && timeout > 0) {\n\t\ttimeoutHandle = setTimeout(() => {\n\t\t\tif (process.platform !== \"win32\" && child.pid) {\n\t\t\t\ttry {\n\t\t\t\t\tprocess.kill(-child.pid);\n\t\t\t\t} catch {\n\t\t\t\t\tchild.kill();\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tchild.kill();\n\t\t\t}\n\t\t}, timeout * 1000);\n\t}\n\tchild.stdout.on(\"data\", (data: Buffer) => {\n\t\tsendFrame(connection.socket, { id, event: \"data\", stream: \"stdout\", dataBase64: data.toString(\"base64\") });\n\t});\n\tchild.stderr.on(\"data\", (data: Buffer) => {\n\t\tsendFrame(connection.socket, { id, event: \"data\", stream: \"stderr\", dataBase64: data.toString(\"base64\") });\n\t});\n\tchild.on(\"error\", (error) => {\n\t\tif (timeoutHandle) clearTimeout(timeoutHandle);\n\t\tconnection.execs.delete(id);\n\t\tsendFrame(connection.socket, { id, event: \"error\", error: { message: error.message } });\n\t});\n\tchild.on(\"close\", (code) => {\n\t\tif (timeoutHandle) clearTimeout(timeoutHandle);\n\t\tconnection.execs.delete(id);\n\t\tsendFrame(connection.socket, { id, event: \"exit\", exitCode: code });\n\t});\n}\n\nfunction cancelExec(connection: ClientConnection, id: string): void {\n\tconst child = connection.execs.get(id);\n\tif (!child) return;\n\tconnection.execs.delete(id);\n\tif (process.platform !== \"win32\" && child.pid) {\n\t\ttry {\n\t\t\tprocess.kill(-child.pid);\n\t\t} catch {\n\t\t\tchild.kill();\n\t\t}\n\t} else {\n\t\tchild.kill();\n\t}\n\tsendFrame(connection.socket, { id, event: \"exit\", exitCode: null, cancelled: true });\n}\n\nasync function handleRequest(connection: ClientConnection, message: JsonRpcMessage): Promise<void> {\n\tconst id = requireString(message.id, \"id\");\n\tconst method = requireString(message.method, \"method\");\n\tconst params = isRecord(message.params) ? message.params : {};\n\ttry {\n\t\tif (method === \"cancel\") {\n\t\t\tcancelExec(connection, id);\n\t\t\treturn;\n\t\t}\n\t\tif (method === \"exec\") {\n\t\t\thandleExec(connection, id, params);\n\t\t\treturn;\n\t\t}\n\t\tif (method === \"capabilities\") {\n\t\t\tsendResult(connection, id, {\n\t\t\t\tcwd,\n\t\t\t\tfeatures: { exec: true, files: true, glob: true, grep: true, instructions: false },\n\t\t\t});\n\t\t\treturn;\n\t\t}\n\t\tif (method === \"access\") {\n\t\t\tawait access(requireString(params.path, \"path\"), accessModeToFsMode(params.mode));\n\t\t\tsendResult(connection, id, {});\n\t\t\treturn;\n\t\t}\n\t\tif (method === \"readFile\") {\n\t\t\tconst content = await readFile(requireString(params.path, \"path\"));\n\t\t\tsendResult(connection, id, { contentBase64: content.toString(\"base64\") });\n\t\t\treturn;\n\t\t}\n\t\tif (method === \"writeFile\") {\n\t\t\tawait writeFile(\n\t\t\t\trequireString(params.path, \"path\"),\n\t\t\t\tBuffer.from(requireString(params.contentBase64, \"contentBase64\"), \"base64\"),\n\t\t\t);\n\t\t\tsendResult(connection, id, {});\n\t\t\treturn;\n\t\t}\n\t\tif (method === \"mkdir\") {\n\t\t\tawait mkdir(requireString(params.path, \"path\"), { recursive: params.recursive === true });\n\t\t\tsendResult(connection, id, {});\n\t\t\treturn;\n\t\t}\n\t\tif (method === \"stat\") {\n\t\t\tconst result = await stat(requireString(params.path, \"path\"));\n\t\t\tsendResult(connection, id, { isDirectory: result.isDirectory(), isFile: result.isFile() });\n\t\t\treturn;\n\t\t}\n\t\tif (method === \"readdir\") {\n\t\t\tsendResult(connection, id, { entries: await readdir(requireString(params.path, \"path\")) });\n\t\t\treturn;\n\t\t}\n\t\tif (method === \"glob\") {\n\t\t\tconst pattern = requireString(params.pattern, \"pattern\");\n\t\t\tconst runCwd = requireString(params.cwd, \"cwd\");\n\t\t\tconst limit = optionalNumber(params.limit) ?? 1000;\n\t\t\tconst output = await runBuffered(\"fd\", buildFdArgs(pattern, runCwd, limit), runCwd);\n\t\t\tsendResult(connection, id, { matches: output.toString(\"utf-8\").split(\"\\n\").filter(Boolean) });\n\t\t\treturn;\n\t\t}\n\t\tif (method === \"grep\") {\n\t\t\tconst pathParam = requireString(params.path, \"path\");\n\t\t\tconst isDirectory = (await stat(pathParam)).isDirectory();\n\t\t\tconst output = await runBuffered(\"rg\", buildRgArgs(params), cwd);\n\t\t\tconst matches = output\n\t\t\t\t.toString(\"utf-8\")\n\t\t\t\t.split(\"\\n\")\n\t\t\t\t.filter(Boolean)\n\t\t\t\t.flatMap((line) => {\n\t\t\t\t\ttry {\n\t\t\t\t\t\tconst data = JSON.parse(line) as unknown;\n\t\t\t\t\t\tif (!isRecord(data) || data.type !== \"match\" || !isRecord(data.data)) return [];\n\t\t\t\t\t\tconst filePath = isRecord(data.data.path) ? optionalString(data.data.path.text) : undefined;\n\t\t\t\t\t\tconst lineNumber = optionalNumber(data.data.line_number);\n\t\t\t\t\t\tconst lineText = isRecord(data.data.lines) ? optionalString(data.data.lines.text) : undefined;\n\t\t\t\t\t\treturn filePath && lineNumber !== undefined ? [{ filePath, lineNumber, lineText }] : [];\n\t\t\t\t\t} catch {\n\t\t\t\t\t\treturn [];\n\t\t\t\t\t}\n\t\t\t\t});\n\t\t\tsendResult(connection, id, { isDirectory, matches });\n\t\t\treturn;\n\t\t}\n\t\tif (method === \"detectImageMimeType\") {\n\t\t\tconst output = await runBuffered(\"file\", [\"--mime-type\", \"-b\", requireString(params.path, \"path\")], cwd);\n\t\t\tconst mimeType = output.toString(\"utf-8\").trim();\n\t\t\tsendResult(connection, id, {\n\t\t\t\tmimeType: [\"image/jpeg\", \"image/png\", \"image/gif\", \"image/webp\"].includes(mimeType) ? mimeType : null,\n\t\t\t});\n\t\t\treturn;\n\t\t}\n\t\tthrow new Error(`Unknown method: ${method}`);\n\t} catch (error) {\n\t\tsendError(connection, id, error);\n\t}\n}\n\nfunction parseFrames(connection: ClientConnection): void {\n\twhile (connection.buffer.length >= 2) {\n\t\tconst first = connection.buffer[0];\n\t\tconst second = connection.buffer[1];\n\t\tconst opcode = first & 0x0f;\n\t\tconst masked = (second & 0x80) !== 0;\n\t\tlet payloadLength = second & 0x7f;\n\t\tlet offset = 2;\n\t\tif (payloadLength === 126) {\n\t\t\tif (connection.buffer.length < offset + 2) return;\n\t\t\tpayloadLength = connection.buffer.readUInt16BE(offset);\n\t\t\toffset += 2;\n\t\t} else if (payloadLength === 127) {\n\t\t\tif (connection.buffer.length < offset + 8) return;\n\t\t\tconst largeLength = connection.buffer.readBigUInt64BE(offset);\n\t\t\tif (largeLength > BigInt(Number.MAX_SAFE_INTEGER)) {\n\t\t\t\tconnection.socket.destroy(new Error(\"WebSocket frame too large\"));\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tpayloadLength = Number(largeLength);\n\t\t\toffset += 8;\n\t\t}\n\t\tif (!masked) {\n\t\t\tconnection.socket.destroy(new Error(\"Client WebSocket frames must be masked\"));\n\t\t\treturn;\n\t\t}\n\t\tif (connection.buffer.length < offset + 4 + payloadLength) return;\n\t\tconst mask = connection.buffer.subarray(offset, offset + 4);\n\t\toffset += 4;\n\t\tconst payload = Buffer.from(connection.buffer.subarray(offset, offset + payloadLength));\n\t\tconnection.buffer = connection.buffer.subarray(offset + payloadLength);\n\t\tfor (let index = 0; index < payload.length; index++) {\n\t\t\tpayload[index] ^= mask[index % 4];\n\t\t}\n\t\tif (opcode === 0x8) {\n\t\t\tconnection.socket.end();\n\t\t\treturn;\n\t\t}\n\t\tif (opcode !== 0x1) continue;\n\t\tlet message: unknown;\n\t\ttry {\n\t\t\tmessage = JSON.parse(payload.toString(\"utf-8\"));\n\t\t} catch {\n\t\t\tcontinue;\n\t\t}\n\t\tif (isRecord(message) && message.type === \"ping\") {\n\t\t\tsendFrame(connection.socket, { type: \"pong\", timestamp: message.timestamp });\n\t\t\tcontinue;\n\t\t}\n\t\tvoid handleRequest(connection, message as JsonRpcMessage);\n\t}\n}\n\nfunction isAuthorized(request: IncomingMessage): boolean {\n\tif (!token) return true;\n\tif (request.headers.authorization === `Bearer ${token}`) return true;\n\ttry {\n\t\tconst url = new URL(request.url ?? \"/\", `http://${request.headers.host ?? \"localhost\"}`);\n\t\treturn url.searchParams.get(\"token\") === token;\n\t} catch {\n\t\treturn false;\n\t}\n}\n\nconst server = createServer((_request, response) => {\n\tresponse.writeHead(404);\n\tresponse.end(\"pi-daemon only serves WebSocket remote commander connections\\n\");\n});\n\nserver.on(\"upgrade\", (request, socket) => {\n\tconst netSocket = socket as Socket;\n\tif (!isAuthorized(request)) {\n\t\tnetSocket.write(\"HTTP/1.1 401 Unauthorized\\r\\n\\r\\n\");\n\t\tnetSocket.destroy();\n\t\treturn;\n\t}\n\tconst key = request.headers[\"sec-websocket-key\"];\n\tif (typeof key !== \"string\") {\n\t\tnetSocket.write(\"HTTP/1.1 400 Bad Request\\r\\n\\r\\n\");\n\t\tnetSocket.destroy();\n\t\treturn;\n\t}\n\tconst accept = createHash(\"sha1\").update(`${key}258EAFA5-E914-47DA-95CA-C5AB0DC85B11`).digest(\"base64\");\n\tnetSocket.write(\n\t\t[\n\t\t\t\"HTTP/1.1 101 Switching Protocols\",\n\t\t\t\"Upgrade: websocket\",\n\t\t\t\"Connection: Upgrade\",\n\t\t\t`Sec-WebSocket-Accept: ${accept}`,\n\t\t\t\"\",\n\t\t\t\"\",\n\t\t].join(\"\\r\\n\"),\n\t);\n\tconst connection: ClientConnection = { socket: netSocket, buffer: Buffer.alloc(0), execs: new Map() };\n\tnetSocket.on(\"data\", (chunk: Buffer) => {\n\t\tconnection.buffer = Buffer.concat([connection.buffer, chunk]);\n\t\tparseFrames(connection);\n\t});\n\tnetSocket.on(\"close\", () => {\n\t\tfor (const child of connection.execs.values()) {\n\t\t\tchild.kill();\n\t\t}\n\t\tconnection.execs.clear();\n\t});\n});\n\nserver.listen(port, host, () => {\n\tconsole.log(`pi-daemon listening on ws://${host}:${port} cwd=${cwd}`);\n});\n"]}
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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","sourcesContent":["#!/usr/bin/env node\n\nimport { type ChildProcessWithoutNullStreams, spawn } from \"node:child_process\";\nimport { createHash } from \"node:crypto\";\nimport { once } from \"node:events\";\nimport { constants, createReadStream, createWriteStream, type WriteStream } from \"node:fs\";\nimport { access, mkdir, readdir, readFile, stat, writeFile } from \"node:fs/promises\";\nimport { createServer, type IncomingMessage } from \"node:http\";\nimport type { Socket } from \"node:net\";\nimport { resolve } from \"node:path\";\n\ninterface JsonRpcMessage {\n\tid?: unknown;\n\tmethod?: unknown;\n\tparams?: unknown;\n}\n\ninterface ClientConnection {\n\tsocket: Socket;\n\tbuffer: Buffer;\n\texecs: Map<string, ChildProcessWithoutNullStreams>;\n\tuploads: Map<string, WriteStream>;\n}\n\nconst port = Number(process.env.PORT ?? process.env.PI_DAEMON_PORT ?? \"8787\");\nconst host = process.env.HOST ?? process.env.PI_DAEMON_HOST ?? \"127.0.0.1\";\nconst cwd = resolve(process.env.PI_DAEMON_CWD ?? process.cwd());\nconst token = process.env.PI_DAEMON_TOKEN;\nconst fileTransferChunkSize = 64 * 1024;\n\nfunction createFrame(payload: unknown): Buffer {\n\tconst data = Buffer.from(JSON.stringify(payload));\n\tlet header: Buffer;\n\tif (data.length < 126) {\n\t\theader = Buffer.from([0x81, data.length]);\n\t} else if (data.length <= 0xffff) {\n\t\theader = Buffer.alloc(4);\n\t\theader[0] = 0x81;\n\t\theader[1] = 126;\n\t\theader.writeUInt16BE(data.length, 2);\n\t} else {\n\t\theader = Buffer.alloc(10);\n\t\theader[0] = 0x81;\n\t\theader[1] = 127;\n\t\theader.writeBigUInt64BE(BigInt(data.length), 2);\n\t}\n\treturn Buffer.concat([header, data]);\n}\n\nfunction sendFrame(socket: Socket, payload: unknown): void {\n\tsocket.write(createFrame(payload));\n}\n\nasync function sendFrameAsync(socket: Socket, payload: unknown): Promise<void> {\n\tif (!socket.write(createFrame(payload))) {\n\t\tawait once(socket, \"drain\");\n\t}\n}\n\nfunction sendResult(connection: ClientConnection, id: string, result: unknown): void {\n\tsendFrame(connection.socket, { id, result });\n}\n\nfunction sendError(connection: ClientConnection, id: string, error: unknown): void {\n\tconst message = error instanceof Error ? error.message : String(error);\n\tsendFrame(connection.socket, { id, error: { message } });\n}\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n\treturn typeof value === \"object\" && value !== null;\n}\n\nfunction requireString(value: unknown, name: string): string {\n\tif (typeof value !== \"string\") throw new Error(`Missing string param: ${name}`);\n\treturn value;\n}\n\nfunction optionalString(value: unknown): string | undefined {\n\treturn typeof value === \"string\" ? value : undefined;\n}\n\nfunction optionalNumber(value: unknown): number | undefined {\n\treturn typeof value === \"number\" ? value : undefined;\n}\n\nfunction accessModeToFsMode(mode: unknown): number {\n\tswitch (mode) {\n\t\tcase \"read\":\n\t\t\treturn constants.R_OK;\n\t\tcase \"write\":\n\t\t\treturn constants.W_OK;\n\t\tcase \"readwrite\":\n\t\t\treturn constants.R_OK | constants.W_OK;\n\t\tcase \"exists\":\n\t\tcase undefined:\n\t\t\treturn constants.F_OK;\n\t\tdefault:\n\t\t\tthrow new Error(`Invalid access mode: ${String(mode)}`);\n\t}\n}\n\nfunction buildFdArgs(pattern: string, searchPath: string, limit: number): string[] {\n\tconst args: string[] = [\"--glob\", \"--color=never\", \"--hidden\", \"--no-require-git\", \"--max-results\", String(limit)];\n\tlet effectivePattern = pattern;\n\tif (pattern.includes(\"/\")) {\n\t\targs.push(\"--full-path\");\n\t\tif (!pattern.startsWith(\"/\") && !pattern.startsWith(\"**/\") && pattern !== \"**\") {\n\t\t\teffectivePattern = `**/${pattern}`;\n\t\t}\n\t}\n\targs.push(\"--\", effectivePattern, searchPath);\n\treturn args;\n}\n\nfunction buildRgArgs(params: Record<string, unknown>): string[] {\n\tconst pattern = requireString(params.pattern, \"pattern\");\n\tconst path = requireString(params.path, \"path\");\n\tconst args: string[] = [\"--json\", \"--line-number\", \"--color=never\", \"--hidden\"];\n\tif (params.ignoreCase === true) args.push(\"--ignore-case\");\n\tif (params.literal === true) args.push(\"--fixed-strings\");\n\tconst glob = optionalString(params.glob);\n\tif (glob) args.push(\"--glob\", glob);\n\targs.push(\"--\", pattern, path);\n\treturn args;\n}\n\nasync function runBuffered(command: string, args: string[], runCwd: string): Promise<Buffer> {\n\treturn new Promise((resolvePromise, reject) => {\n\t\tconst child = spawn(command, args, { cwd: runCwd, stdio: [\"ignore\", \"pipe\", \"pipe\"] });\n\t\tconst stdout: Buffer[] = [];\n\t\tconst stderr: Buffer[] = [];\n\t\tchild.stdout.on(\"data\", (data: Buffer) => stdout.push(data));\n\t\tchild.stderr.on(\"data\", (data: Buffer) => stderr.push(data));\n\t\tchild.on(\"error\", reject);\n\t\tchild.on(\"close\", (code) => {\n\t\t\tif (code === 0) {\n\t\t\t\tresolvePromise(Buffer.concat(stdout));\n\t\t\t\treturn;\n\t\t\t}\n\t\t\treject(new Error(Buffer.concat(stderr).toString(\"utf-8\").trim() || `${command} exited with code ${code}`));\n\t\t});\n\t});\n}\n\nfunction handleExec(connection: ClientConnection, id: string, params: Record<string, unknown>): void {\n\tconst command = requireString(params.command, \"command\");\n\tconst runCwd = optionalString(params.cwd) ?? cwd;\n\tconst timeout = optionalNumber(params.timeout);\n\tconst env = isRecord(params.env)\n\t\t? Object.fromEntries(\n\t\t\t\tObject.entries(params.env).filter((entry): entry is [string, string] => typeof entry[1] === \"string\"),\n\t\t\t)\n\t\t: undefined;\n\tconst child = spawn(\"bash\", [\"-lc\", command], {\n\t\tcwd: runCwd,\n\t\tdetached: process.platform !== \"win32\",\n\t\tenv: env ? { ...process.env, ...env } : process.env,\n\t});\n\tconnection.execs.set(id, child);\n\tlet timeoutHandle: NodeJS.Timeout | undefined;\n\tif (timeout !== undefined && timeout > 0) {\n\t\ttimeoutHandle = setTimeout(() => {\n\t\t\tif (process.platform !== \"win32\" && child.pid) {\n\t\t\t\ttry {\n\t\t\t\t\tprocess.kill(-child.pid);\n\t\t\t\t} catch {\n\t\t\t\t\tchild.kill();\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tchild.kill();\n\t\t\t}\n\t\t}, timeout * 1000);\n\t}\n\tchild.stdout.on(\"data\", (data: Buffer) => {\n\t\tsendFrame(connection.socket, { id, event: \"data\", stream: \"stdout\", dataBase64: data.toString(\"base64\") });\n\t});\n\tchild.stderr.on(\"data\", (data: Buffer) => {\n\t\tsendFrame(connection.socket, { id, event: \"data\", stream: \"stderr\", dataBase64: data.toString(\"base64\") });\n\t});\n\tchild.on(\"error\", (error) => {\n\t\tif (timeoutHandle) clearTimeout(timeoutHandle);\n\t\tconnection.execs.delete(id);\n\t\tsendFrame(connection.socket, { id, event: \"error\", error: { message: error.message } });\n\t});\n\tchild.on(\"close\", (code) => {\n\t\tif (timeoutHandle) clearTimeout(timeoutHandle);\n\t\tconnection.execs.delete(id);\n\t\tsendFrame(connection.socket, { id, event: \"exit\", exitCode: code });\n\t});\n}\n\nfunction cancelExec(connection: ClientConnection, id: string): void {\n\tconst child = connection.execs.get(id);\n\tif (!child) return;\n\tconnection.execs.delete(id);\n\tif (process.platform !== \"win32\" && child.pid) {\n\t\ttry {\n\t\t\tprocess.kill(-child.pid);\n\t\t} catch {\n\t\t\tchild.kill();\n\t\t}\n\t} else {\n\t\tchild.kill();\n\t}\n\tsendFrame(connection.socket, { id, event: \"exit\", exitCode: null, cancelled: true });\n}\n\nasync function handleDownloadFile(connection: ClientConnection, id: string, path: string): Promise<void> {\n\ttry {\n\t\tfor await (const chunk of createReadStream(path, { highWaterMark: fileTransferChunkSize })) {\n\t\t\tconst buffer = typeof chunk === \"string\" ? Buffer.from(chunk) : chunk;\n\t\t\tawait sendFrameAsync(connection.socket, { id, event: \"fileData\", dataBase64: buffer.toString(\"base64\") });\n\t\t}\n\t\tawait sendFrameAsync(connection.socket, { id, event: \"fileEnd\" });\n\t} catch (error) {\n\t\tsendFrame(connection.socket, {\n\t\t\tid,\n\t\t\tevent: \"fileError\",\n\t\t\terror: { message: error instanceof Error ? error.message : String(error) },\n\t\t});\n\t}\n}\n\nfunction writeUploadChunk(stream: WriteStream, chunk: Buffer): Promise<void> {\n\treturn new Promise((resolve, reject) => {\n\t\tconst onError = (error: Error) => {\n\t\t\tstream.off(\"error\", onError);\n\t\t\treject(error);\n\t\t};\n\t\tstream.once(\"error\", onError);\n\t\tstream.write(chunk, (error) => {\n\t\t\tstream.off(\"error\", onError);\n\t\t\tif (error) {\n\t\t\t\treject(error);\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tresolve();\n\t\t});\n\t});\n}\n\nfunction closeUploadStream(stream: WriteStream): Promise<void> {\n\treturn new Promise((resolve, reject) => {\n\t\tconst onError = (error: Error) => {\n\t\t\tstream.off(\"error\", onError);\n\t\t\treject(error);\n\t\t};\n\t\tstream.once(\"error\", onError);\n\t\tstream.end(() => {\n\t\t\tstream.off(\"error\", onError);\n\t\t\tresolve();\n\t\t});\n\t});\n}\n\nasync function handleRequest(connection: ClientConnection, message: JsonRpcMessage): Promise<void> {\n\tconst id = requireString(message.id, \"id\");\n\tconst method = requireString(message.method, \"method\");\n\tconst params = isRecord(message.params) ? message.params : {};\n\ttry {\n\t\tif (method === \"cancel\") {\n\t\t\tcancelExec(connection, id);\n\t\t\treturn;\n\t\t}\n\t\tif (method === \"exec\") {\n\t\t\thandleExec(connection, id, params);\n\t\t\treturn;\n\t\t}\n\t\tif (method === \"capabilities\") {\n\t\t\tsendResult(connection, id, {\n\t\t\t\tcwd,\n\t\t\t\tfeatures: {\n\t\t\t\t\texec: true,\n\t\t\t\t\tfiles: true,\n\t\t\t\t\tfileTransfer: true,\n\t\t\t\t\tglob: true,\n\t\t\t\t\tgrep: true,\n\t\t\t\t\tinstructions: false,\n\t\t\t\t},\n\t\t\t});\n\t\t\treturn;\n\t\t}\n\t\tif (method === \"access\") {\n\t\t\tawait access(requireString(params.path, \"path\"), accessModeToFsMode(params.mode));\n\t\t\tsendResult(connection, id, {});\n\t\t\treturn;\n\t\t}\n\t\tif (method === \"readFile\") {\n\t\t\tconst content = await readFile(requireString(params.path, \"path\"));\n\t\t\tsendResult(connection, id, { contentBase64: content.toString(\"base64\") });\n\t\t\treturn;\n\t\t}\n\t\tif (method === \"writeFile\") {\n\t\t\tawait writeFile(\n\t\t\t\trequireString(params.path, \"path\"),\n\t\t\t\tBuffer.from(requireString(params.contentBase64, \"contentBase64\"), \"base64\"),\n\t\t\t);\n\t\t\tsendResult(connection, id, {});\n\t\t\treturn;\n\t\t}\n\t\tif (method === \"downloadFile\") {\n\t\t\tawait handleDownloadFile(connection, id, requireString(params.path, \"path\"));\n\t\t\treturn;\n\t\t}\n\t\tif (method === \"uploadFileStart\") {\n\t\t\tconst path = requireString(params.path, \"path\");\n\t\t\tconst stream = createWriteStream(path);\n\t\t\tstream.on(\"error\", () => undefined);\n\t\t\tconnection.uploads.set(id, stream);\n\t\t\tsendResult(connection, id, {});\n\t\t\treturn;\n\t\t}\n\t\tif (method === \"uploadFileChunk\") {\n\t\t\tconst uploadId = requireString(params.uploadId, \"uploadId\");\n\t\t\tconst stream = connection.uploads.get(uploadId);\n\t\t\tif (!stream) throw new Error(`Unknown upload: ${uploadId}`);\n\t\t\tawait writeUploadChunk(stream, Buffer.from(requireString(params.dataBase64, \"dataBase64\"), \"base64\"));\n\t\t\tsendResult(connection, id, {});\n\t\t\treturn;\n\t\t}\n\t\tif (method === \"uploadFileEnd\") {\n\t\t\tconst uploadId = requireString(params.uploadId, \"uploadId\");\n\t\t\tconst stream = connection.uploads.get(uploadId);\n\t\t\tif (!stream) throw new Error(`Unknown upload: ${uploadId}`);\n\t\t\tconnection.uploads.delete(uploadId);\n\t\t\tawait closeUploadStream(stream);\n\t\t\tsendResult(connection, id, {});\n\t\t\treturn;\n\t\t}\n\t\tif (method === \"uploadFileCancel\") {\n\t\t\tconst uploadId = requireString(params.uploadId, \"uploadId\");\n\t\t\tconst stream = connection.uploads.get(uploadId);\n\t\t\tif (stream) {\n\t\t\t\tconnection.uploads.delete(uploadId);\n\t\t\t\tstream.destroy();\n\t\t\t}\n\t\t\tsendResult(connection, id, {});\n\t\t\treturn;\n\t\t}\n\t\tif (method === \"mkdir\") {\n\t\t\tawait mkdir(requireString(params.path, \"path\"), { recursive: params.recursive === true });\n\t\t\tsendResult(connection, id, {});\n\t\t\treturn;\n\t\t}\n\t\tif (method === \"stat\") {\n\t\t\tconst result = await stat(requireString(params.path, \"path\"));\n\t\t\tsendResult(connection, id, { isDirectory: result.isDirectory(), isFile: result.isFile() });\n\t\t\treturn;\n\t\t}\n\t\tif (method === \"readdir\") {\n\t\t\tsendResult(connection, id, { entries: await readdir(requireString(params.path, \"path\")) });\n\t\t\treturn;\n\t\t}\n\t\tif (method === \"glob\") {\n\t\t\tconst pattern = requireString(params.pattern, \"pattern\");\n\t\t\tconst runCwd = requireString(params.cwd, \"cwd\");\n\t\t\tconst limit = optionalNumber(params.limit) ?? 1000;\n\t\t\tconst output = await runBuffered(\"fd\", buildFdArgs(pattern, runCwd, limit), runCwd);\n\t\t\tsendResult(connection, id, { matches: output.toString(\"utf-8\").split(\"\\n\").filter(Boolean) });\n\t\t\treturn;\n\t\t}\n\t\tif (method === \"grep\") {\n\t\t\tconst pathParam = requireString(params.path, \"path\");\n\t\t\tconst isDirectory = (await stat(pathParam)).isDirectory();\n\t\t\tconst output = await runBuffered(\"rg\", buildRgArgs(params), cwd);\n\t\t\tconst matches = output\n\t\t\t\t.toString(\"utf-8\")\n\t\t\t\t.split(\"\\n\")\n\t\t\t\t.filter(Boolean)\n\t\t\t\t.flatMap((line) => {\n\t\t\t\t\ttry {\n\t\t\t\t\t\tconst data = JSON.parse(line) as unknown;\n\t\t\t\t\t\tif (!isRecord(data) || data.type !== \"match\" || !isRecord(data.data)) return [];\n\t\t\t\t\t\tconst filePath = isRecord(data.data.path) ? optionalString(data.data.path.text) : undefined;\n\t\t\t\t\t\tconst lineNumber = optionalNumber(data.data.line_number);\n\t\t\t\t\t\tconst lineText = isRecord(data.data.lines) ? optionalString(data.data.lines.text) : undefined;\n\t\t\t\t\t\treturn filePath && lineNumber !== undefined ? [{ filePath, lineNumber, lineText }] : [];\n\t\t\t\t\t} catch {\n\t\t\t\t\t\treturn [];\n\t\t\t\t\t}\n\t\t\t\t});\n\t\t\tsendResult(connection, id, { isDirectory, matches });\n\t\t\treturn;\n\t\t}\n\t\tif (method === \"detectImageMimeType\") {\n\t\t\tconst output = await runBuffered(\"file\", [\"--mime-type\", \"-b\", requireString(params.path, \"path\")], cwd);\n\t\t\tconst mimeType = output.toString(\"utf-8\").trim();\n\t\t\tsendResult(connection, id, {\n\t\t\t\tmimeType: [\"image/jpeg\", \"image/png\", \"image/gif\", \"image/webp\"].includes(mimeType) ? mimeType : null,\n\t\t\t});\n\t\t\treturn;\n\t\t}\n\t\tthrow new Error(`Unknown method: ${method}`);\n\t} catch (error) {\n\t\tsendError(connection, id, error);\n\t}\n}\n\nfunction parseFrames(connection: ClientConnection): void {\n\twhile (connection.buffer.length >= 2) {\n\t\tconst first = connection.buffer[0];\n\t\tconst second = connection.buffer[1];\n\t\tconst opcode = first & 0x0f;\n\t\tconst masked = (second & 0x80) !== 0;\n\t\tlet payloadLength = second & 0x7f;\n\t\tlet offset = 2;\n\t\tif (payloadLength === 126) {\n\t\t\tif (connection.buffer.length < offset + 2) return;\n\t\t\tpayloadLength = connection.buffer.readUInt16BE(offset);\n\t\t\toffset += 2;\n\t\t} else if (payloadLength === 127) {\n\t\t\tif (connection.buffer.length < offset + 8) return;\n\t\t\tconst largeLength = connection.buffer.readBigUInt64BE(offset);\n\t\t\tif (largeLength > BigInt(Number.MAX_SAFE_INTEGER)) {\n\t\t\t\tconnection.socket.destroy(new Error(\"WebSocket frame too large\"));\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tpayloadLength = Number(largeLength);\n\t\t\toffset += 8;\n\t\t}\n\t\tif (!masked) {\n\t\t\tconnection.socket.destroy(new Error(\"Client WebSocket frames must be masked\"));\n\t\t\treturn;\n\t\t}\n\t\tif (connection.buffer.length < offset + 4 + payloadLength) return;\n\t\tconst mask = connection.buffer.subarray(offset, offset + 4);\n\t\toffset += 4;\n\t\tconst payload = Buffer.from(connection.buffer.subarray(offset, offset + payloadLength));\n\t\tconnection.buffer = connection.buffer.subarray(offset + payloadLength);\n\t\tfor (let index = 0; index < payload.length; index++) {\n\t\t\tpayload[index] ^= mask[index % 4];\n\t\t}\n\t\tif (opcode === 0x8) {\n\t\t\tconnection.socket.end();\n\t\t\treturn;\n\t\t}\n\t\tif (opcode !== 0x1) continue;\n\t\tlet message: unknown;\n\t\ttry {\n\t\t\tmessage = JSON.parse(payload.toString(\"utf-8\"));\n\t\t} catch {\n\t\t\tcontinue;\n\t\t}\n\t\tif (isRecord(message) && message.type === \"ping\") {\n\t\t\tsendFrame(connection.socket, { type: \"pong\", timestamp: message.timestamp });\n\t\t\tcontinue;\n\t\t}\n\t\tvoid handleRequest(connection, message as JsonRpcMessage);\n\t}\n}\n\nfunction isAuthorized(request: IncomingMessage): boolean {\n\tif (!token) return true;\n\tif (request.headers.authorization === `Bearer ${token}`) return true;\n\ttry {\n\t\tconst url = new URL(request.url ?? \"/\", `http://${request.headers.host ?? \"localhost\"}`);\n\t\treturn url.searchParams.get(\"token\") === token;\n\t} catch {\n\t\treturn false;\n\t}\n}\n\nconst server = createServer((_request, response) => {\n\tresponse.writeHead(404);\n\tresponse.end(\"pi-daemon only serves WebSocket remote commander connections\\n\");\n});\n\nserver.on(\"upgrade\", (request, socket) => {\n\tconst netSocket = socket as Socket;\n\tif (!isAuthorized(request)) {\n\t\tnetSocket.write(\"HTTP/1.1 401 Unauthorized\\r\\n\\r\\n\");\n\t\tnetSocket.destroy();\n\t\treturn;\n\t}\n\tconst key = request.headers[\"sec-websocket-key\"];\n\tif (typeof key !== \"string\") {\n\t\tnetSocket.write(\"HTTP/1.1 400 Bad Request\\r\\n\\r\\n\");\n\t\tnetSocket.destroy();\n\t\treturn;\n\t}\n\tconst accept = createHash(\"sha1\").update(`${key}258EAFA5-E914-47DA-95CA-C5AB0DC85B11`).digest(\"base64\");\n\tnetSocket.write(\n\t\t[\n\t\t\t\"HTTP/1.1 101 Switching Protocols\",\n\t\t\t\"Upgrade: websocket\",\n\t\t\t\"Connection: Upgrade\",\n\t\t\t`Sec-WebSocket-Accept: ${accept}`,\n\t\t\t\"\",\n\t\t\t\"\",\n\t\t].join(\"\\r\\n\"),\n\t);\n\tconst connection: ClientConnection = {\n\t\tsocket: netSocket,\n\t\tbuffer: Buffer.alloc(0),\n\t\texecs: new Map(),\n\t\tuploads: new Map(),\n\t};\n\tnetSocket.on(\"data\", (chunk: Buffer) => {\n\t\tconnection.buffer = Buffer.concat([connection.buffer, chunk]);\n\t\tparseFrames(connection);\n\t});\n\tnetSocket.on(\"close\", () => {\n\t\tfor (const child of connection.execs.values()) {\n\t\t\tchild.kill();\n\t\t}\n\t\tconnection.execs.clear();\n\t\tfor (const stream of connection.uploads.values()) {\n\t\t\tstream.destroy();\n\t\t}\n\t\tconnection.uploads.clear();\n\t});\n});\n\nserver.listen(port, host, () => {\n\tconsole.log(`pi-daemon listening on ws://${host}:${port} cwd=${cwd}`);\n});\n"]}
|