@fleetagent/pi-daemon 0.1.0 → 0.1.2

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 CHANGED
@@ -1,5 +1,19 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.1.2](https://github.com/fleetagent/pi/compare/@fleetagent/pi-daemon-v0.1.1...@fleetagent/pi-daemon-v0.1.2) (2026-06-13)
4
+
5
+
6
+ ### Miscellaneous Chores
7
+
8
+ * **@fleetagent/pi-daemon:** Synchronize pi versions
9
+
10
+ ## [0.1.1](https://github.com/fleetagent/pi/compare/@fleetagent/pi-daemon-v0.1.0...@fleetagent/pi-daemon-v0.1.1) (2026-06-12)
11
+
12
+
13
+ ### Features
14
+
15
+ * **coding-agent:** add streamed sandbox file transfer ([4199641](https://github.com/fleetagent/pi/commit/41996414740a0a752c8eafac16f48d38ded76c78))
16
+
3
17
  ## [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
18
 
5
19
 
@@ -11,4 +25,5 @@
11
25
 
12
26
  ### Added
13
27
 
28
+ - Added streamed file upload and download methods to the daemon WebSocket protocol.
14
29
  - 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.
@@ -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 { constants } from "node:fs";
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
- function sendFrame(socket, payload) {
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
- socket.write(Buffer.concat([header, data]));
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: { exec: true, files: true, glob: true, grep: true, instructions: false },
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 = { socket: netSocket, buffer: Buffer.alloc(0), execs: new Map() };
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"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fleetagent/pi-daemon",
3
- "version": "0.1.0",
3
+ "version": "0.1.2",
4
4
  "description": "Remote commander daemon for Pi",
5
5
  "type": "module",
6
6
  "bin": {