pockcode 0.0.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,436 @@
1
+ import { createRequire } from "node:module";
2
+ import { chmodSync, realpathSync, statSync } from "node:fs";
3
+ import { readFile, readdir, realpath, stat } from "node:fs/promises";
4
+ import path from "node:path";
5
+ import { randomUUID } from "node:crypto";
6
+ import os, { homedir } from "node:os";
7
+ import { Server } from "socket.io";
8
+ import * as pty from "node-pty";
9
+ //#region \0rolldown/runtime.js
10
+ var __defProp = Object.defineProperty;
11
+ var __exportAll = (all, no_symbols) => {
12
+ let target = {};
13
+ for (var name in all) __defProp(target, name, {
14
+ get: all[name],
15
+ enumerable: true
16
+ });
17
+ if (!no_symbols) __defProp(target, Symbol.toStringTag, { value: "Module" });
18
+ return target;
19
+ };
20
+ //#endregion
21
+ //#region app/server/http.server.ts
22
+ var HttpError = class extends Error {
23
+ constructor(status, message) {
24
+ super(message);
25
+ this.status = status;
26
+ }
27
+ };
28
+ function readStringField(value, field, options = {}) {
29
+ if (value === void 0 || value === null) {
30
+ if (options.required) throw new HttpError(400, `${field} is required.`);
31
+ return;
32
+ }
33
+ if (typeof value !== "string") throw new HttpError(400, `${field} must be a string.`);
34
+ const trimmed = value.trim();
35
+ if (!trimmed && options.required) throw new HttpError(400, `${field} is required.`);
36
+ if (options.maxLength && trimmed.length > options.maxLength) throw new HttpError(400, `${field} must be ${options.maxLength} characters or fewer.`);
37
+ return trimmed || void 0;
38
+ }
39
+ function readBooleanField(value, field) {
40
+ if (value === void 0) return;
41
+ if (typeof value !== "boolean") throw new HttpError(400, `${field} must be a boolean.`);
42
+ return value;
43
+ }
44
+ function readRecordField(value, field) {
45
+ if (value === void 0) return;
46
+ if (!value || typeof value !== "object" || Array.isArray(value)) throw new HttpError(400, `${field} must be an object.`);
47
+ return value;
48
+ }
49
+ //#endregion
50
+ //#region app/server/workspaces.server.ts
51
+ var workspaceRoot = realpathSync(homedir());
52
+ var maxDirectoryEntries = 500;
53
+ var maxTextFileBytes = 256 * 1024;
54
+ var skippedDirectoryNames = new Set([
55
+ ".git",
56
+ ".next",
57
+ ".turbo",
58
+ ".vite",
59
+ "build",
60
+ "coverage",
61
+ "dist",
62
+ "node_modules",
63
+ "target"
64
+ ]);
65
+ async function listWorkspaceDirectory(inputPath, includeHidden) {
66
+ const directoryPath = await resolveWorkspacePath(inputPath);
67
+ const entries = await readDirectoryEntries(directoryPath);
68
+ return {
69
+ root: workspaceRoot,
70
+ path: directoryPath,
71
+ parentPath: path.dirname(directoryPath) === directoryPath ? null : path.dirname(directoryPath),
72
+ entries: entries.filter((entry) => includeHidden || !isHiddenEntry(entry.name)).map((entry) => toWorkspaceEntry(directoryPath, entry.name, entry.isDirectory(), entry.isSymbolicLink())).sort(sortWorkspaceEntries).slice(0, maxDirectoryEntries)
73
+ };
74
+ }
75
+ async function readWorkspaceTree(inputPath, includeHidden) {
76
+ return readWorkspaceTreeDirectory(await resolveWorkspacePath(inputPath), includeHidden);
77
+ }
78
+ async function readWorkspaceResource(inputPath) {
79
+ const filePath = await resolveWorkspaceFilePath(inputPath);
80
+ const name = path.basename(filePath);
81
+ const stats = await readStats(filePath);
82
+ if (stats.size > maxTextFileBytes) return {
83
+ content: `File too large to preview (${formatBytes(stats.size)}).`,
84
+ name,
85
+ path: filePath,
86
+ type: "file"
87
+ };
88
+ if (!isTextFileName(name)) throw new HttpError(415, "Only text files can be opened.");
89
+ try {
90
+ return {
91
+ content: await readFile(filePath, "utf8"),
92
+ name,
93
+ path: filePath,
94
+ type: "file"
95
+ };
96
+ } catch (error) {
97
+ throw new HttpError(400, readError(error));
98
+ }
99
+ }
100
+ async function resolveWorkspaceDirectoryPath(inputPath) {
101
+ return resolveWorkspacePath(inputPath);
102
+ }
103
+ async function resolveWorkspacePath(inputPath) {
104
+ const requested = inputPath?.trim();
105
+ const resolvedPath = await resolveRealPath(requested ? resolveUserPath(requested) : workspaceRoot);
106
+ if (!isPathWithinWorkspaceRoot(resolvedPath)) throw new HttpError(403, "Path is outside the file browser home.");
107
+ if (!(await readStats(resolvedPath)).isDirectory()) throw new HttpError(400, "Path is not a directory.");
108
+ return resolvedPath;
109
+ }
110
+ async function resolveWorkspaceFilePath(inputPath) {
111
+ const requested = inputPath?.trim();
112
+ if (!requested) throw new HttpError(400, "path is required.");
113
+ const resolvedPath = await resolveRealPath(resolveUserPath(requested));
114
+ if (!isPathWithinWorkspaceRoot(resolvedPath)) throw new HttpError(403, "Path is outside the file browser home.");
115
+ if (!(await readStats(resolvedPath)).isFile()) throw new HttpError(400, "Path is not a file.");
116
+ return resolvedPath;
117
+ }
118
+ async function readWorkspaceTreeDirectory(directoryPath, includeHidden) {
119
+ const entry = {
120
+ name: path.basename(directoryPath) || directoryPath,
121
+ path: directoryPath,
122
+ type: "directory",
123
+ children: []
124
+ };
125
+ const children = await readTreeDirectoryEntries(directoryPath, entry);
126
+ for (const child of children.filter((entry) => includeHidden || !isHiddenEntry(entry.name)).sort((left, right) => sortWorkspaceEntries(toWorkspaceEntry(directoryPath, left.name, left.isDirectory(), left.isSymbolicLink()), toWorkspaceEntry(directoryPath, right.name, right.isDirectory(), right.isSymbolicLink()))).slice(0, maxDirectoryEntries)) {
127
+ if (child.isSymbolicLink()) {
128
+ entry.children?.push(toWorkspaceEntry(directoryPath, child.name, false, true));
129
+ continue;
130
+ }
131
+ if (child.isDirectory()) {
132
+ if (!skippedDirectoryNames.has(child.name)) entry.children?.push(toWorkspaceEntry(directoryPath, child.name, true, false));
133
+ continue;
134
+ }
135
+ entry.children?.push(toWorkspaceEntry(directoryPath, child.name, false, false));
136
+ }
137
+ return entry;
138
+ }
139
+ function toWorkspaceEntry(parentPath, name, isDirectory, isSymlink) {
140
+ return {
141
+ name,
142
+ path: path.join(parentPath, name),
143
+ type: isDirectory ? "directory" : isSymlink ? "symlink" : "file"
144
+ };
145
+ }
146
+ function sortWorkspaceEntries(left, right) {
147
+ if (left.type === "directory" && right.type !== "directory") return -1;
148
+ if (left.type !== "directory" && right.type === "directory") return 1;
149
+ return left.name.localeCompare(right.name);
150
+ }
151
+ function resolveUserPath(inputPath) {
152
+ if (inputPath === "~") return workspaceRoot;
153
+ if (inputPath.startsWith("~/")) return path.join(workspaceRoot, inputPath.slice(2));
154
+ if (path.isAbsolute(inputPath)) return inputPath;
155
+ return path.join(workspaceRoot, inputPath);
156
+ }
157
+ async function resolveRealPath(inputPath) {
158
+ try {
159
+ return await realpath(path.resolve(inputPath));
160
+ } catch (error) {
161
+ if (isNodeError(error) && error.code === "ENOENT") throw new HttpError(400, "Directory path does not exist.");
162
+ throw new HttpError(400, readError(error));
163
+ }
164
+ }
165
+ async function readStats(inputPath) {
166
+ try {
167
+ return await stat(inputPath);
168
+ } catch (error) {
169
+ throw new HttpError(400, readError(error));
170
+ }
171
+ }
172
+ async function readDirectoryEntries(directoryPath) {
173
+ try {
174
+ return await readdir(directoryPath, { withFileTypes: true });
175
+ } catch (error) {
176
+ throw new HttpError(400, readError(error));
177
+ }
178
+ }
179
+ async function readTreeDirectoryEntries(directoryPath, entry) {
180
+ try {
181
+ return await readdir(directoryPath, { withFileTypes: true });
182
+ } catch (error) {
183
+ entry.error = readError(error);
184
+ return [];
185
+ }
186
+ }
187
+ function isPathWithinWorkspaceRoot(inputPath) {
188
+ const relativePath = path.relative(workspaceRoot, inputPath);
189
+ return relativePath === "" || !relativePath.startsWith("..") && !path.isAbsolute(relativePath);
190
+ }
191
+ function isHiddenEntry(name) {
192
+ return name.startsWith(".");
193
+ }
194
+ function isTextFileName(name) {
195
+ return /(?:\.([cm]?[jt]sx?|json|md|txt|css|html?|ya?ml|toml|rs|sh|env|gitignore|dockerignore)|^Makefile$|^Dockerfile$|^\.env)/i.test(name);
196
+ }
197
+ function formatBytes(bytes) {
198
+ if (bytes < 1024) return `${bytes} B`;
199
+ if (bytes < 1024 * 1024) return `${Math.round(bytes / 1024)} KB`;
200
+ return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
201
+ }
202
+ function readError(error) {
203
+ return error instanceof Error ? error.message : "Request failed.";
204
+ }
205
+ function isNodeError(error) {
206
+ return error instanceof Error && "code" in error;
207
+ }
208
+ //#endregion
209
+ //#region app/server/terminal.server.ts
210
+ var terminalSessionsBySocket = /* @__PURE__ */ new Map();
211
+ var require = createRequire(import.meta.url);
212
+ var nodePtyHelperChecked = false;
213
+ function installTerminalSocketHandlers(socket) {
214
+ const sessions = /* @__PURE__ */ new Map();
215
+ terminalSessionsBySocket.set(socket.id, sessions);
216
+ socket.on("terminal.create", (payload, reply) => {
217
+ createTerminal(socket, sessions, payload, reply);
218
+ });
219
+ socket.on("terminal.input", (payload) => {
220
+ const record = readRecord(payload);
221
+ const terminalId = readString(record.id);
222
+ const data = readString(record.data);
223
+ if (!terminalId || data === void 0) return;
224
+ sessions.get(terminalId)?.process.write(data);
225
+ });
226
+ socket.on("terminal.resize", (payload) => {
227
+ const record = readRecord(payload);
228
+ const terminalId = readString(record.id);
229
+ const cols = readInt(record.cols, 80, 2, 500);
230
+ const rows = readInt(record.rows, 24, 2, 200);
231
+ if (!terminalId) return;
232
+ sessions.get(terminalId)?.process.resize(cols, rows);
233
+ });
234
+ socket.on("terminal.close", (payload) => {
235
+ const terminalId = readString(readRecord(payload).id);
236
+ if (!terminalId) return;
237
+ const session = sessions.get(terminalId);
238
+ if (!session) return;
239
+ sessions.delete(terminalId);
240
+ closeHostedTerminal(session);
241
+ socket.emit("terminal.closed", { id: terminalId });
242
+ });
243
+ socket.on("disconnect", () => {
244
+ for (const session of sessions.values()) closeHostedTerminal(session);
245
+ sessions.clear();
246
+ terminalSessionsBySocket.delete(socket.id);
247
+ });
248
+ }
249
+ async function createTerminal(socket, sessions, payload, reply) {
250
+ try {
251
+ const record = readRecord(payload);
252
+ const cwd = await resolveWorkspaceDirectoryPath(readString(record.workspacePath) ?? null);
253
+ const cols = readInt(record.cols, 80, 2, 500);
254
+ const rows = readInt(record.rows, 24, 2, 200);
255
+ const shell = resolveShell();
256
+ const name = path.basename(shell);
257
+ ensureNodePtySpawnHelperExecutable();
258
+ const terminal = pty.spawn(shell, [], {
259
+ cols,
260
+ cwd,
261
+ env: terminalEnv(),
262
+ name: "xterm-256color",
263
+ rows
264
+ });
265
+ const id = randomUUID();
266
+ const session = {
267
+ cwd,
268
+ dataListener: terminal.onData((data) => socket.emit("terminal.output", {
269
+ data,
270
+ id
271
+ })),
272
+ exitListener: terminal.onExit(({ exitCode, signal }) => {
273
+ sessions.delete(id);
274
+ socket.emit("terminal.exit", {
275
+ exitCode,
276
+ id,
277
+ signal
278
+ });
279
+ }),
280
+ id,
281
+ name,
282
+ process: terminal,
283
+ shell
284
+ };
285
+ sessions.set(id, session);
286
+ const metadata = terminalMetadata(session);
287
+ reply?.({
288
+ ok: true,
289
+ terminal: metadata
290
+ });
291
+ socket.emit("terminal.created", metadata);
292
+ } catch (error) {
293
+ const message = error instanceof Error ? error.message : "Unable to start terminal.";
294
+ reply?.({
295
+ error: message,
296
+ ok: false
297
+ });
298
+ socket.emit("terminal.error", { error: message });
299
+ }
300
+ }
301
+ function closeHostedTerminal(session) {
302
+ session.dataListener.dispose();
303
+ session.exitListener.dispose();
304
+ session.process.kill();
305
+ }
306
+ function ensureNodePtySpawnHelperExecutable() {
307
+ if (nodePtyHelperChecked || process.platform === "win32") return;
308
+ nodePtyHelperChecked = true;
309
+ try {
310
+ const packageJsonPath = require.resolve("node-pty/package.json");
311
+ const helperPath = path.join(path.dirname(packageJsonPath), "prebuilds", `${process.platform}-${process.arch}`, "spawn-helper");
312
+ const stats = statSync(helperPath);
313
+ if ((stats.mode & 73) === 0) chmodSync(helperPath, stats.mode | 73);
314
+ } catch {}
315
+ }
316
+ function terminalMetadata(session) {
317
+ return {
318
+ cwd: session.cwd,
319
+ id: session.id,
320
+ name: session.name,
321
+ shell: session.shell
322
+ };
323
+ }
324
+ function resolveShell() {
325
+ if (process.platform === "win32") return process.env.ComSpec || "powershell.exe";
326
+ return process.env.SHELL || "/bin/zsh";
327
+ }
328
+ function terminalEnv() {
329
+ const env = {};
330
+ for (const [key, value] of Object.entries(process.env)) if (value !== void 0) env[key] = value;
331
+ env.COLORTERM = env.COLORTERM || "truecolor";
332
+ env.TERM = "xterm-256color";
333
+ env.USER = env.USER || os.userInfo().username;
334
+ return env;
335
+ }
336
+ function readRecord(value) {
337
+ return value && typeof value === "object" && !Array.isArray(value) ? value : {};
338
+ }
339
+ function readString(value) {
340
+ return typeof value === "string" ? value : void 0;
341
+ }
342
+ function readInt(value, fallback, min, max) {
343
+ if (typeof value !== "number" || !Number.isFinite(value)) return fallback;
344
+ return Math.min(max, Math.max(min, Math.round(value)));
345
+ }
346
+ //#endregion
347
+ //#region app/server/socket.server.ts
348
+ var socket_server_exports = /* @__PURE__ */ __exportAll({
349
+ installProviderSocketServer: () => installProviderSocketServer,
350
+ listWatchedWorkspacePaths: () => listWatchedWorkspacePaths,
351
+ onProviderEvent: () => onProviderEvent,
352
+ onWorkspaceWatchChange: () => onWorkspaceWatchChange,
353
+ publishProviderEvent: () => publishProviderEvent
354
+ });
355
+ var installedServers = /* @__PURE__ */ new WeakSet();
356
+ var providerEventListeners = /* @__PURE__ */ new Set();
357
+ var workspaceWatchListeners = /* @__PURE__ */ new Set();
358
+ var workspacePathsBySocket = /* @__PURE__ */ new Map();
359
+ var io = null;
360
+ function installProviderSocketServer(server, options = {}) {
361
+ if (installedServers.has(server)) return;
362
+ installedServers.add(server);
363
+ io = new Server(server, {
364
+ allowRequest: options.authenticateRequest ? (req, callback) => {
365
+ Promise.resolve(options.authenticateRequest?.(req) ?? true).then((allowed) => callback(null, allowed)).catch((error) => callback(error instanceof Error ? error.message : "Unauthorized.", false));
366
+ } : void 0,
367
+ cors: {
368
+ origin: true,
369
+ credentials: true
370
+ },
371
+ path: "/socket.io"
372
+ });
373
+ io.on("connection", (socket) => {
374
+ installTerminalSocketHandlers(socket);
375
+ socket.on("chat.join", (threadId) => {
376
+ if (threadId) socket.join(chatRoom(threadId));
377
+ });
378
+ socket.on("workspace.join", (workspacePath) => {
379
+ const path = normalizeWorkspacePath(workspacePath);
380
+ if (!path) return;
381
+ const paths = workspacePathsBySocket.get(socket.id) ?? /* @__PURE__ */ new Set();
382
+ paths.add(path);
383
+ workspacePathsBySocket.set(socket.id, paths);
384
+ notifyWorkspaceWatchListeners();
385
+ });
386
+ socket.on("workspace.leave", (workspacePath) => {
387
+ const path = normalizeWorkspacePath(workspacePath);
388
+ if (!path) return;
389
+ const paths = workspacePathsBySocket.get(socket.id);
390
+ paths?.delete(path);
391
+ if (!paths?.size) workspacePathsBySocket.delete(socket.id);
392
+ notifyWorkspaceWatchListeners();
393
+ });
394
+ socket.on("chat.leave", (threadId) => {
395
+ if (threadId) socket.leave(chatRoom(threadId));
396
+ });
397
+ socket.on("disconnect", () => {
398
+ if (workspacePathsBySocket.delete(socket.id)) notifyWorkspaceWatchListeners();
399
+ });
400
+ });
401
+ }
402
+ function listWatchedWorkspacePaths() {
403
+ const paths = /* @__PURE__ */ new Set();
404
+ for (const socketPaths of workspacePathsBySocket.values()) for (const path of socketPaths) paths.add(path);
405
+ return [...paths];
406
+ }
407
+ function onWorkspaceWatchChange(listener) {
408
+ workspaceWatchListeners.add(listener);
409
+ }
410
+ function onProviderEvent(listener) {
411
+ providerEventListeners.add(listener);
412
+ return () => {
413
+ providerEventListeners.delete(listener);
414
+ };
415
+ }
416
+ function publishProviderEvent(event) {
417
+ for (const listener of providerEventListeners) Promise.resolve(listener(event)).catch(() => void 0);
418
+ if (!io) return;
419
+ if (event.threadId) io.to(chatRoom(event.threadId)).emit(event.type, event.payload);
420
+ if (isChatDeltaEvent(event.type)) return;
421
+ io.emit("provider.event", event);
422
+ }
423
+ function chatRoom(threadId) {
424
+ return `chat:${threadId}`;
425
+ }
426
+ function isChatDeltaEvent(type) {
427
+ return type === "message.created" || type === "message.deleted" || type === "messages.replaced";
428
+ }
429
+ function normalizeWorkspacePath(value) {
430
+ return typeof value === "string" && value.trim() ? value.trim() : null;
431
+ }
432
+ function notifyWorkspaceWatchListeners() {
433
+ for (const listener of workspaceWatchListeners) listener();
434
+ }
435
+ //#endregion
436
+ export { socket_server_exports as a, readWorkspaceTree as c, readRecordField as d, readStringField as f, publishProviderEvent as i, HttpError as l, onProviderEvent as n, listWorkspaceDirectory as o, __exportAll as p, onWorkspaceWatchChange as r, readWorkspaceResource as s, listWatchedWorkspacePaths as t, readBooleanField as u };