pi-bridge 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.
@@ -0,0 +1,188 @@
1
+ 'use strict';
2
+
3
+ const fs = require('fs');
4
+
5
+ /**
6
+ * Collect full request body as a string.
7
+ * @param {import('http').IncomingMessage} req
8
+ * @returns {Promise<string>}
9
+ */
10
+ function collectBody(req) {
11
+ return new Promise((resolve, reject) => {
12
+ const chunks = [];
13
+ req.on('data', (chunk) => chunks.push(chunk));
14
+ req.on('end', () => resolve(Buffer.concat(chunks).toString('utf8')));
15
+ req.on('error', reject);
16
+ });
17
+ }
18
+
19
+ /**
20
+ * Send a JSON error response.
21
+ */
22
+ function sendError(res, status, message) {
23
+ res.writeHead(status, { 'Content-Type': 'application/json' });
24
+ res.end(JSON.stringify({ error: message }));
25
+ }
26
+
27
+ /**
28
+ * Create the file operation handler function.
29
+ * Expects URL parsing to have already happened by the time this is called.
30
+ *
31
+ * @returns {function(req, res): void} - Express-style handler (no next, terminal)
32
+ */
33
+ function createHandlers() {
34
+ return async function handleRequest(req, res) {
35
+ const url = new URL(req.url, 'http://localhost');
36
+ const pathname = url.pathname;
37
+
38
+ try {
39
+ if (req.method === 'GET' && pathname === '/list') {
40
+ const p = url.searchParams.get('path');
41
+ if (!p) return sendError(res, 400, 'Missing path parameter');
42
+ const withTypes = url.searchParams.get('withFileTypes') === '1';
43
+ if (withTypes) {
44
+ const dirents = fs.readdirSync(p, { withFileTypes: true });
45
+ const result = dirents.map(d => ({
46
+ name: d.name,
47
+ isFile: d.isFile(),
48
+ isDirectory: d.isDirectory(),
49
+ isSymbolicLink: d.isSymbolicLink(),
50
+ }));
51
+ res.writeHead(200, { 'Content-Type': 'application/json' });
52
+ res.end(JSON.stringify(result));
53
+ } else {
54
+ const entries = fs.readdirSync(p);
55
+ res.writeHead(200, { 'Content-Type': 'application/json' });
56
+ res.end(JSON.stringify(entries));
57
+ }
58
+ return;
59
+ }
60
+
61
+ if (req.method === 'GET' && pathname === '/read') {
62
+ const p = url.searchParams.get('path');
63
+ if (!p) return sendError(res, 400, 'Missing path parameter');
64
+ const content = fs.readFileSync(p, 'utf8');
65
+ res.writeHead(200, { 'Content-Type': 'text/plain; charset=utf-8' });
66
+ res.end(content);
67
+ return;
68
+ }
69
+
70
+ if (req.method === 'GET' && pathname === '/exists') {
71
+ const p = url.searchParams.get('path');
72
+ if (!p) return sendError(res, 400, 'Missing path parameter');
73
+ const exists = fs.existsSync(p);
74
+ res.writeHead(200, { 'Content-Type': 'text/plain' });
75
+ res.end(exists ? '1' : '0');
76
+ return;
77
+ }
78
+
79
+ if (req.method === 'GET' && pathname === '/liststat') {
80
+ const p = url.searchParams.get('path');
81
+ if (!p) return sendError(res, 400, 'Missing path parameter');
82
+ const dirents = fs.readdirSync(p, { withFileTypes: true });
83
+ const result = dirents.map(d => {
84
+ let mtimeMs = 0;
85
+ try { mtimeMs = fs.statSync(require('path').join(p, d.name)).mtimeMs; } catch (_) {}
86
+ return { name: d.name, isFile: d.isFile(), isDirectory: d.isDirectory(), mtimeMs };
87
+ });
88
+ res.writeHead(200, { 'Content-Type': 'application/json' });
89
+ res.end(JSON.stringify(result));
90
+ return;
91
+ }
92
+
93
+ if (req.method === 'GET' && pathname === '/stat') {
94
+ const p = url.searchParams.get('path');
95
+ if (!p) return sendError(res, 400, 'Missing path parameter');
96
+ const st = fs.statSync(p);
97
+ res.writeHead(200, { 'Content-Type': 'application/json' });
98
+ res.end(JSON.stringify({
99
+ isFile: st.isFile(),
100
+ isDirectory: st.isDirectory(),
101
+ size: st.size,
102
+ mtimeMs: st.mtimeMs,
103
+ }));
104
+ return;
105
+ }
106
+
107
+ if (req.method === 'POST' && pathname === '/write') {
108
+ const p = url.searchParams.get('path');
109
+ if (!p) return sendError(res, 400, 'Missing path parameter');
110
+ const body = await collectBody(req);
111
+ fs.writeFileSync(p, body, 'utf8');
112
+ res.writeHead(200, { 'Content-Type': 'text/plain' });
113
+ res.end('ok');
114
+ return;
115
+ }
116
+
117
+ if (req.method === 'POST' && pathname === '/mkdir') {
118
+ const p = url.searchParams.get('path');
119
+ if (!p) return sendError(res, 400, 'Missing path parameter');
120
+ fs.mkdirSync(p, { recursive: true });
121
+ res.writeHead(200, { 'Content-Type': 'text/plain' });
122
+ res.end('ok');
123
+ return;
124
+ }
125
+
126
+ if (req.method === 'POST' && pathname === '/delete') {
127
+ const p = url.searchParams.get('path');
128
+ if (!p) return sendError(res, 400, 'Missing path parameter');
129
+ fs.unlinkSync(p);
130
+ res.writeHead(200, { 'Content-Type': 'text/plain' });
131
+ res.end('ok');
132
+ return;
133
+ }
134
+
135
+ if (req.method === 'POST' && pathname === '/rename') {
136
+ const from = url.searchParams.get('from');
137
+ const to = url.searchParams.get('to');
138
+ if (!from || !to) return sendError(res, 400, 'Missing from or to parameter');
139
+ fs.renameSync(from, to);
140
+ res.writeHead(200, { 'Content-Type': 'text/plain' });
141
+ res.end('ok');
142
+ return;
143
+ }
144
+
145
+ if (req.method === 'POST' && pathname === '/rm') {
146
+ const p = url.searchParams.get('path');
147
+ if (!p) return sendError(res, 400, 'Missing path parameter');
148
+ const recursive = url.searchParams.get('recursive') === '1';
149
+ fs.rmSync(p, { recursive, force: true });
150
+ res.writeHead(200, { 'Content-Type': 'text/plain' });
151
+ res.end('ok');
152
+ return;
153
+ }
154
+
155
+ if (req.method === 'POST' && pathname === '/copy') {
156
+ const from = url.searchParams.get('from');
157
+ const to = url.searchParams.get('to');
158
+ if (!from || !to) return sendError(res, 400, 'Missing from or to parameter');
159
+ fs.copyFileSync(from, to);
160
+ res.writeHead(200, { 'Content-Type': 'text/plain' });
161
+ res.end('ok');
162
+ return;
163
+ }
164
+
165
+ if (req.method === 'POST' && pathname === '/append') {
166
+ const p = url.searchParams.get('path');
167
+ if (!p) return sendError(res, 400, 'Missing path parameter');
168
+ const body = await collectBody(req);
169
+ fs.appendFileSync(p, body, 'utf8');
170
+ res.writeHead(200, { 'Content-Type': 'text/plain' });
171
+ res.end('ok');
172
+ return;
173
+ }
174
+
175
+ // Unknown endpoint
176
+ res.writeHead(404, { 'Content-Type': 'text/plain' });
177
+ res.end('Not Found');
178
+ } catch (err) {
179
+ if (err.code === 'ENOENT') {
180
+ sendError(res, 404, 'Not found: ' + err.message);
181
+ } else {
182
+ sendError(res, 500, 'Internal error: ' + err.message);
183
+ }
184
+ }
185
+ };
186
+ }
187
+
188
+ module.exports = { createHandlers };
@@ -0,0 +1,43 @@
1
+ 'use strict';
2
+
3
+ // NOTE: This file is bundled by upload.js into a single self-contained script.
4
+ // All dependencies (auth, guard, handlers) are inlined — no cross-file requires here.
5
+
6
+ const http = require('http');
7
+
8
+ // These symbols are provided by the bundle (inlined by upload.js):
9
+ // generateToken, createAuthMiddleware — from auth.js
10
+ // createGuard — from guard.js
11
+ // createHandlers — from handlers.js
12
+
13
+ function startServer(remoteCwd) {
14
+ const token = generateToken();
15
+ const authMiddleware = createAuthMiddleware(token);
16
+ const guardMiddleware = createGuard(remoteCwd);
17
+ const handleRequest = createHandlers();
18
+
19
+ const server = http.createServer((req, res) => {
20
+ authMiddleware(req, res, () => {
21
+ guardMiddleware(req, res, () => {
22
+ handleRequest(req, res);
23
+ });
24
+ });
25
+ });
26
+
27
+ server.listen(0, '127.0.0.1', () => {
28
+ const port = server.address().port;
29
+ // Print PORT:TOKEN:PID to stdout so the local preload can capture it.
30
+ // PID is used by cleanup to kill this specific server process on exit.
31
+ process.stdout.write(port + ':' + token + ':' + process.pid + '\n');
32
+ });
33
+
34
+ return server;
35
+ }
36
+
37
+ const remoteCwd = process.argv[2];
38
+ if (!remoteCwd) {
39
+ process.stderr.write('Usage: node index.js <remoteCwd>\n');
40
+ process.exit(1);
41
+ }
42
+
43
+ startServer(remoteCwd);
@@ -0,0 +1,21 @@
1
+ 'use strict';
2
+
3
+ const ENDPOINTS = {
4
+ list: '/list',
5
+ read: '/read',
6
+ exists: '/exists',
7
+ write: '/write',
8
+ mkdir: '/mkdir',
9
+ delete: '/delete',
10
+ rename: '/rename',
11
+ };
12
+
13
+ const TOKEN_HEADER = 'x-token';
14
+
15
+ const ERRORS = {
16
+ unauthorized: 401,
17
+ forbidden: 403,
18
+ notFound: 404,
19
+ };
20
+
21
+ module.exports = { ENDPOINTS, TOKEN_HEADER, ERRORS };