@cloudflare/sandbox 0.0.0-db09b4d → 0.0.0-eb0ea62

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,25 @@
1
1
  # @cloudflare/sandbox
2
2
 
3
+ ## 0.0.4
4
+
5
+ ### Patch Changes
6
+
7
+ - [`c0d9d33`](https://github.com/cloudflare/sandbox-sdk/commit/c0d9d3396badee1eab45e6b4a73d48957f31409b) Thanks [@threepointone](https://github.com/threepointone)! - actually work
8
+
9
+ - [`444d2da`](https://github.com/cloudflare/sandbox-sdk/commit/444d2dafde9a0f190e50c879b0e768da1b289b51) Thanks [@threepointone](https://github.com/threepointone)! - add experimental label
10
+
11
+ ## 0.0.3
12
+
13
+ ### Patch Changes
14
+
15
+ - [`2b087c4`](https://github.com/cloudflare/sandbox-sdk/commit/2b087c40a29697c20dad19b4e3b8512f5d404bd3) Thanks [@ghostwriternr](https://github.com/ghostwriternr)! - Fix worker unable to find container port
16
+
17
+ ## 0.0.2
18
+
19
+ ### Patch Changes
20
+
21
+ - [`52f02f0`](https://github.com/cloudflare/sandbox-sdk/commit/52f02f0625ef9f8eac695e51f93fa79651c0206d) Thanks [@threepointone](https://github.com/threepointone)! - readFile
22
+
3
23
  ## 0.0.1
4
24
 
5
25
  ### Patch Changes
package/README.md CHANGED
@@ -1,5 +1,7 @@
1
1
  ## @cloudflare/sandbox
2
2
 
3
+ > **⚠️ Experimental** - This library is currently experimental and we're actively seeking feedback. Please try it out and let us know what you think!
4
+
3
5
  A library to spin up a sandboxed environment.
4
6
 
5
7
  First, setup your wrangler.json to use the sandbox:
@@ -56,6 +58,7 @@ export default {
56
58
  - `gitCheckout(repoUrl: string, options: { branch?: string; targetDir?: string; stream?: boolean })`: Checkout a git repository in the sandbox.
57
59
  - `mkdir(path: string, options: { recursive?: boolean; stream?: boolean })`: Create a directory in the sandbox.
58
60
  - `writeFile(path: string, content: string, options: { encoding?: string; stream?: boolean })`: Write content to a file in the sandbox.
61
+ - `readFile(path: string, options: { encoding?: string; stream?: boolean })`: Read content from a file in the sandbox.
59
62
  - `deleteFile(path: string, options?: { stream?: boolean })`: Delete a file from the sandbox.
60
63
  - `renameFile(oldPath: string, newPath: string, options?: { stream?: boolean })`: Rename a file in the sandbox.
61
64
  - `moveFile(sourcePath: string, destinationPath: string, options?: { stream?: boolean })`: Move a file from one location to another in the sandbox.
@@ -1,5 +1,5 @@
1
1
  import { spawn } from "node:child_process";
2
- import { mkdir, rename, unlink, writeFile } from "node:fs/promises";
2
+ import { mkdir, readFile, rename, unlink, writeFile } from "node:fs/promises";
3
3
  import { dirname } from "node:path";
4
4
  import { serve } from "bun";
5
5
 
@@ -28,6 +28,12 @@ interface WriteFileRequest {
28
28
  sessionId?: string;
29
29
  }
30
30
 
31
+ interface ReadFileRequest {
32
+ path: string;
33
+ encoding?: string;
34
+ sessionId?: string;
35
+ }
36
+
31
37
  interface DeleteFileRequest {
32
38
  path: string;
33
39
  sessionId?: string;
@@ -297,6 +303,18 @@ const server = serve({
297
303
  }
298
304
  break;
299
305
 
306
+ case "/api/read":
307
+ if (req.method === "POST") {
308
+ return handleReadFileRequest(req, corsHeaders);
309
+ }
310
+ break;
311
+
312
+ case "/api/read/stream":
313
+ if (req.method === "POST") {
314
+ return handleStreamingReadFileRequest(req, corsHeaders);
315
+ }
316
+ break;
317
+
300
318
  case "/api/delete":
301
319
  if (req.method === "POST") {
302
320
  return handleDeleteFileRequest(req, corsHeaders);
@@ -1476,6 +1494,235 @@ async function handleStreamingWriteFileRequest(
1476
1494
  }
1477
1495
  }
1478
1496
 
1497
+ async function handleReadFileRequest(
1498
+ req: Request,
1499
+ corsHeaders: Record<string, string>
1500
+ ): Promise<Response> {
1501
+ try {
1502
+ const body = (await req.json()) as ReadFileRequest;
1503
+ const { path, encoding = "utf-8", sessionId } = body;
1504
+
1505
+ if (!path || typeof path !== "string") {
1506
+ return new Response(
1507
+ JSON.stringify({
1508
+ error: "Path is required and must be a string",
1509
+ }),
1510
+ {
1511
+ headers: {
1512
+ "Content-Type": "application/json",
1513
+ ...corsHeaders,
1514
+ },
1515
+ status: 400,
1516
+ }
1517
+ );
1518
+ }
1519
+
1520
+ // Basic safety check - prevent dangerous paths
1521
+ const dangerousPatterns = [
1522
+ /^\/$/, // Root directory
1523
+ /^\/etc/, // System directories
1524
+ /^\/var/, // System directories
1525
+ /^\/usr/, // System directories
1526
+ /^\/bin/, // System directories
1527
+ /^\/sbin/, // System directories
1528
+ /^\/boot/, // System directories
1529
+ /^\/dev/, // System directories
1530
+ /^\/proc/, // System directories
1531
+ /^\/sys/, // System directories
1532
+ /^\/tmp\/\.\./, // Path traversal attempts
1533
+ /\.\./, // Path traversal attempts
1534
+ ];
1535
+
1536
+ if (dangerousPatterns.some((pattern) => pattern.test(path))) {
1537
+ return new Response(
1538
+ JSON.stringify({
1539
+ error: "Dangerous path not allowed",
1540
+ }),
1541
+ {
1542
+ headers: {
1543
+ "Content-Type": "application/json",
1544
+ ...corsHeaders,
1545
+ },
1546
+ status: 400,
1547
+ }
1548
+ );
1549
+ }
1550
+
1551
+ console.log(`[Server] Reading file: ${path}`);
1552
+
1553
+ const result = await executeReadFile(path, encoding, sessionId);
1554
+
1555
+ return new Response(
1556
+ JSON.stringify({
1557
+ content: result.content,
1558
+ exitCode: result.exitCode,
1559
+ path,
1560
+ success: result.success,
1561
+ timestamp: new Date().toISOString(),
1562
+ }),
1563
+ {
1564
+ headers: {
1565
+ "Content-Type": "application/json",
1566
+ ...corsHeaders,
1567
+ },
1568
+ }
1569
+ );
1570
+ } catch (error) {
1571
+ console.error("[Server] Error in handleReadFileRequest:", error);
1572
+ return new Response(
1573
+ JSON.stringify({
1574
+ error: "Failed to read file",
1575
+ message: error instanceof Error ? error.message : "Unknown error",
1576
+ }),
1577
+ {
1578
+ headers: {
1579
+ "Content-Type": "application/json",
1580
+ ...corsHeaders,
1581
+ },
1582
+ status: 500,
1583
+ }
1584
+ );
1585
+ }
1586
+ }
1587
+
1588
+ async function handleStreamingReadFileRequest(
1589
+ req: Request,
1590
+ corsHeaders: Record<string, string>
1591
+ ): Promise<Response> {
1592
+ try {
1593
+ const body = (await req.json()) as ReadFileRequest;
1594
+ const { path, encoding = "utf-8", sessionId } = body;
1595
+
1596
+ if (!path || typeof path !== "string") {
1597
+ return new Response(
1598
+ JSON.stringify({
1599
+ error: "Path is required and must be a string",
1600
+ }),
1601
+ {
1602
+ headers: {
1603
+ "Content-Type": "application/json",
1604
+ ...corsHeaders,
1605
+ },
1606
+ status: 400,
1607
+ }
1608
+ );
1609
+ }
1610
+
1611
+ // Basic safety check - prevent dangerous paths
1612
+ const dangerousPatterns = [
1613
+ /^\/$/, // Root directory
1614
+ /^\/etc/, // System directories
1615
+ /^\/var/, // System directories
1616
+ /^\/usr/, // System directories
1617
+ /^\/bin/, // System directories
1618
+ /^\/sbin/, // System directories
1619
+ /^\/boot/, // System directories
1620
+ /^\/dev/, // System directories
1621
+ /^\/proc/, // System directories
1622
+ /^\/sys/, // System directories
1623
+ /^\/tmp\/\.\./, // Path traversal attempts
1624
+ /\.\./, // Path traversal attempts
1625
+ ];
1626
+
1627
+ if (dangerousPatterns.some((pattern) => pattern.test(path))) {
1628
+ return new Response(
1629
+ JSON.stringify({
1630
+ error: "Dangerous path not allowed",
1631
+ }),
1632
+ {
1633
+ headers: {
1634
+ "Content-Type": "application/json",
1635
+ ...corsHeaders,
1636
+ },
1637
+ status: 400,
1638
+ }
1639
+ );
1640
+ }
1641
+
1642
+ console.log(`[Server] Reading file (streaming): ${path}`);
1643
+
1644
+ const stream = new ReadableStream({
1645
+ start(controller) {
1646
+ (async () => {
1647
+ try {
1648
+ // Send command start event
1649
+ controller.enqueue(
1650
+ new TextEncoder().encode(
1651
+ `data: ${JSON.stringify({
1652
+ path,
1653
+ timestamp: new Date().toISOString(),
1654
+ type: "command_start",
1655
+ })}\n\n`
1656
+ )
1657
+ );
1658
+
1659
+ // Read the file
1660
+ const content = await readFile(path, {
1661
+ encoding: encoding as BufferEncoding,
1662
+ });
1663
+
1664
+ console.log(`[Server] File read successfully: ${path}`);
1665
+
1666
+ // Send command completion event
1667
+ controller.enqueue(
1668
+ new TextEncoder().encode(
1669
+ `data: ${JSON.stringify({
1670
+ content,
1671
+ path,
1672
+ success: true,
1673
+ timestamp: new Date().toISOString(),
1674
+ type: "command_complete",
1675
+ })}\n\n`
1676
+ )
1677
+ );
1678
+
1679
+ controller.close();
1680
+ } catch (error) {
1681
+ console.error(`[Server] Error reading file: ${path}`, error);
1682
+
1683
+ controller.enqueue(
1684
+ new TextEncoder().encode(
1685
+ `data: ${JSON.stringify({
1686
+ error:
1687
+ error instanceof Error ? error.message : "Unknown error",
1688
+ path,
1689
+ type: "error",
1690
+ })}\n\n`
1691
+ )
1692
+ );
1693
+
1694
+ controller.close();
1695
+ }
1696
+ })();
1697
+ },
1698
+ });
1699
+
1700
+ return new Response(stream, {
1701
+ headers: {
1702
+ "Cache-Control": "no-cache",
1703
+ Connection: "keep-alive",
1704
+ "Content-Type": "text/event-stream",
1705
+ ...corsHeaders,
1706
+ },
1707
+ });
1708
+ } catch (error) {
1709
+ console.error("[Server] Error in handleStreamingReadFileRequest:", error);
1710
+ return new Response(
1711
+ JSON.stringify({
1712
+ error: "Failed to read file",
1713
+ message: error instanceof Error ? error.message : "Unknown error",
1714
+ }),
1715
+ {
1716
+ headers: {
1717
+ "Content-Type": "application/json",
1718
+ ...corsHeaders,
1719
+ },
1720
+ status: 500,
1721
+ }
1722
+ );
1723
+ }
1724
+ }
1725
+
1479
1726
  async function handleDeleteFileRequest(
1480
1727
  req: Request,
1481
1728
  corsHeaders: Record<string, string>
@@ -2506,6 +2753,37 @@ function executeWriteFile(
2506
2753
  });
2507
2754
  }
2508
2755
 
2756
+ function executeReadFile(
2757
+ path: string,
2758
+ encoding: string,
2759
+ sessionId?: string
2760
+ ): Promise<{
2761
+ success: boolean;
2762
+ exitCode: number;
2763
+ content: string;
2764
+ }> {
2765
+ return new Promise((resolve, reject) => {
2766
+ (async () => {
2767
+ try {
2768
+ // Read the file
2769
+ const content = await readFile(path, {
2770
+ encoding: encoding as BufferEncoding,
2771
+ });
2772
+
2773
+ console.log(`[Server] File read successfully: ${path}`);
2774
+ resolve({
2775
+ content,
2776
+ exitCode: 0,
2777
+ success: true,
2778
+ });
2779
+ } catch (error) {
2780
+ console.error(`[Server] Error reading file: ${path}`, error);
2781
+ reject(error);
2782
+ }
2783
+ })();
2784
+ });
2785
+ }
2786
+
2509
2787
  function executeDeleteFile(
2510
2788
  path: string,
2511
2789
  sessionId?: string
@@ -2610,6 +2888,8 @@ console.log(` POST /api/mkdir - Create a directory`);
2610
2888
  console.log(` POST /api/mkdir/stream - Create a directory (streaming)`);
2611
2889
  console.log(` POST /api/write - Write a file`);
2612
2890
  console.log(` POST /api/write/stream - Write a file (streaming)`);
2891
+ console.log(` POST /api/read - Read a file`);
2892
+ console.log(` POST /api/read/stream - Read a file (streaming)`);
2613
2893
  console.log(` POST /api/delete - Delete a file`);
2614
2894
  console.log(` POST /api/delete/stream - Delete a file (streaming)`);
2615
2895
  console.log(` POST /api/rename - Rename a file`);
package/package.json CHANGED
@@ -1,13 +1,13 @@
1
1
  {
2
2
  "name": "@cloudflare/sandbox",
3
- "version": "0.0.0-db09b4d",
3
+ "version": "0.0.0-eb0ea62",
4
4
  "repository": {
5
5
  "type": "git",
6
6
  "url": "https://github.com/cloudflare/sandbox-sdk"
7
7
  },
8
8
  "description": "A sandboxed environment for running commands",
9
9
  "dependencies": {
10
- "@cloudflare/containers": "^0.0.8"
10
+ "@cloudflare/containers": "^0.0.13"
11
11
  },
12
12
  "tags": [
13
13
  "sandbox",
@@ -17,7 +17,7 @@
17
17
  "durable objects"
18
18
  ],
19
19
  "scripts": {
20
- "build": "tsup src/*.ts --outDir dist --dts --sourcemap --format esm"
20
+ "build": "rm -rf dist && tsup src/*.ts --outDir dist --dts --sourcemap --format esm"
21
21
  },
22
22
  "exports": {
23
23
  ".": {
package/src/client.ts CHANGED
@@ -85,6 +85,20 @@ export interface WriteFileResponse {
85
85
  timestamp: string;
86
86
  }
87
87
 
88
+ interface ReadFileRequest {
89
+ path: string;
90
+ encoding?: string;
91
+ sessionId?: string;
92
+ }
93
+
94
+ export interface ReadFileResponse {
95
+ success: boolean;
96
+ exitCode: number;
97
+ path: string;
98
+ content: string;
99
+ timestamp: string;
100
+ }
101
+
88
102
  interface DeleteFileRequest {
89
103
  path: string;
90
104
  sessionId?: string;
@@ -142,6 +156,7 @@ interface StreamEvent {
142
156
  newPath?: string;
143
157
  sourcePath?: string;
144
158
  destinationPath?: string;
159
+ content?: string;
145
160
  success?: boolean;
146
161
  exitCode?: number;
147
162
  stdout?: string;
@@ -978,6 +993,164 @@ export class HttpClient {
978
993
  }
979
994
  }
980
995
 
996
+ async readFile(
997
+ path: string,
998
+ encoding: string = "utf-8",
999
+ sessionId?: string
1000
+ ): Promise<ReadFileResponse> {
1001
+ try {
1002
+ const targetSessionId = sessionId || this.sessionId;
1003
+
1004
+ const response = await this.doFetch(`/api/read`, {
1005
+ body: JSON.stringify({
1006
+ encoding,
1007
+ path,
1008
+ sessionId: targetSessionId,
1009
+ }),
1010
+ headers: {
1011
+ "Content-Type": "application/json",
1012
+ },
1013
+ method: "POST",
1014
+ });
1015
+
1016
+ if (!response.ok) {
1017
+ const errorData = (await response.json().catch(() => ({}))) as {
1018
+ error?: string;
1019
+ };
1020
+ throw new Error(
1021
+ errorData.error || `HTTP error! status: ${response.status}`
1022
+ );
1023
+ }
1024
+
1025
+ const data: ReadFileResponse = await response.json();
1026
+ console.log(
1027
+ `[HTTP Client] File read: ${path}, Success: ${data.success}, Content length: ${data.content.length}`
1028
+ );
1029
+
1030
+ return data;
1031
+ } catch (error) {
1032
+ console.error("[HTTP Client] Error reading file:", error);
1033
+ throw error;
1034
+ }
1035
+ }
1036
+
1037
+ async readFileStream(
1038
+ path: string,
1039
+ encoding: string = "utf-8",
1040
+ sessionId?: string
1041
+ ): Promise<void> {
1042
+ try {
1043
+ const targetSessionId = sessionId || this.sessionId;
1044
+
1045
+ const response = await this.doFetch(`/api/read/stream`, {
1046
+ body: JSON.stringify({
1047
+ encoding,
1048
+ path,
1049
+ sessionId: targetSessionId,
1050
+ }),
1051
+ headers: {
1052
+ "Content-Type": "application/json",
1053
+ },
1054
+ method: "POST",
1055
+ });
1056
+
1057
+ if (!response.ok) {
1058
+ const errorData = (await response.json().catch(() => ({}))) as {
1059
+ error?: string;
1060
+ };
1061
+ throw new Error(
1062
+ errorData.error || `HTTP error! status: ${response.status}`
1063
+ );
1064
+ }
1065
+
1066
+ if (!response.body) {
1067
+ throw new Error("No response body for streaming request");
1068
+ }
1069
+
1070
+ const reader = response.body.getReader();
1071
+ const decoder = new TextDecoder();
1072
+
1073
+ try {
1074
+ while (true) {
1075
+ const { done, value } = await reader.read();
1076
+
1077
+ if (done) {
1078
+ break;
1079
+ }
1080
+
1081
+ const chunk = decoder.decode(value, { stream: true });
1082
+ const lines = chunk.split("\n");
1083
+
1084
+ for (const line of lines) {
1085
+ if (line.startsWith("data: ")) {
1086
+ try {
1087
+ const eventData = line.slice(6); // Remove 'data: ' prefix
1088
+ const event: StreamEvent = JSON.parse(eventData);
1089
+
1090
+ console.log(
1091
+ `[HTTP Client] Read file stream event: ${event.type}`
1092
+ );
1093
+ this.options.onStreamEvent?.(event);
1094
+
1095
+ switch (event.type) {
1096
+ case "command_start":
1097
+ console.log(
1098
+ `[HTTP Client] Read file started: ${event.path}`
1099
+ );
1100
+ this.options.onCommandStart?.("read", [path, encoding]);
1101
+ break;
1102
+
1103
+ case "command_complete":
1104
+ console.log(
1105
+ `[HTTP Client] Read file completed: ${
1106
+ event.path
1107
+ }, Success: ${event.success}, Content length: ${
1108
+ event.content?.length || 0
1109
+ }`
1110
+ );
1111
+ this.options.onCommandComplete?.(
1112
+ event.success!,
1113
+ 0,
1114
+ event.content || "",
1115
+ "",
1116
+ "read",
1117
+ [path, encoding]
1118
+ );
1119
+ break;
1120
+
1121
+ case "error":
1122
+ console.error(
1123
+ `[HTTP Client] Read file error: ${event.error}`
1124
+ );
1125
+ this.options.onError?.(event.error!, "read", [
1126
+ path,
1127
+ encoding,
1128
+ ]);
1129
+ break;
1130
+ }
1131
+ } catch (parseError) {
1132
+ console.warn(
1133
+ "[HTTP Client] Failed to parse read file stream event:",
1134
+ parseError
1135
+ );
1136
+ }
1137
+ }
1138
+ }
1139
+ }
1140
+ } finally {
1141
+ reader.releaseLock();
1142
+ }
1143
+ } catch (error) {
1144
+ console.error("[HTTP Client] Error in streaming read file:", error);
1145
+ this.options.onError?.(
1146
+ error instanceof Error ? error.message : "Unknown error",
1147
+ "read",
1148
+ [path, encoding]
1149
+ );
1150
+ throw error;
1151
+ }
1152
+ }
1153
+
981
1154
  async deleteFile(
982
1155
  path: string,
983
1156
  sessionId?: string
@@ -1629,6 +1802,38 @@ export async function quickWriteFileStream(
1629
1802
  }
1630
1803
  }
1631
1804
 
1805
+ // Convenience function for quick file reading
1806
+ export async function quickReadFile(
1807
+ path: string,
1808
+ encoding: string = "utf-8",
1809
+ options?: HttpClientOptions
1810
+ ): Promise<ReadFileResponse> {
1811
+ const client = createClient(options);
1812
+ await client.createSession();
1813
+
1814
+ try {
1815
+ return await client.readFile(path, encoding);
1816
+ } finally {
1817
+ client.clearSession();
1818
+ }
1819
+ }
1820
+
1821
+ // Convenience function for quick streaming file reading
1822
+ export async function quickReadFileStream(
1823
+ path: string,
1824
+ encoding: string = "utf-8",
1825
+ options?: HttpClientOptions
1826
+ ): Promise<void> {
1827
+ const client = createClient(options);
1828
+ await client.createSession();
1829
+
1830
+ try {
1831
+ await client.readFileStream(path, encoding);
1832
+ } finally {
1833
+ client.clearSession();
1834
+ }
1835
+ }
1836
+
1632
1837
  // Convenience function for quick file deletion
1633
1838
  export async function quickDeleteFile(
1634
1839
  path: string,
package/src/index.ts CHANGED
@@ -76,4 +76,54 @@ export class Sandbox<Env = unknown> extends Container<Env> {
76
76
  }
77
77
  return this.client.mkdir(path, options.recursive);
78
78
  }
79
+
80
+ async writeFile(
81
+ path: string,
82
+ content: string,
83
+ options: { encoding?: string; stream?: boolean }
84
+ ) {
85
+ if (options?.stream) {
86
+ return this.client.writeFileStream(path, content, options.encoding);
87
+ }
88
+ return this.client.writeFile(path, content, options.encoding);
89
+ }
90
+
91
+ async deleteFile(path: string, options: { stream?: boolean }) {
92
+ if (options?.stream) {
93
+ return this.client.deleteFileStream(path);
94
+ }
95
+ return this.client.deleteFile(path);
96
+ }
97
+
98
+ async renameFile(
99
+ oldPath: string,
100
+ newPath: string,
101
+ options: { stream?: boolean }
102
+ ) {
103
+ if (options?.stream) {
104
+ return this.client.renameFileStream(oldPath, newPath);
105
+ }
106
+ return this.client.renameFile(oldPath, newPath);
107
+ }
108
+
109
+ async moveFile(
110
+ sourcePath: string,
111
+ destinationPath: string,
112
+ options: { stream?: boolean }
113
+ ) {
114
+ if (options?.stream) {
115
+ return this.client.moveFileStream(sourcePath, destinationPath);
116
+ }
117
+ return this.client.moveFile(sourcePath, destinationPath);
118
+ }
119
+
120
+ async readFile(
121
+ path: string,
122
+ options: { encoding?: string; stream?: boolean }
123
+ ) {
124
+ if (options?.stream) {
125
+ return this.client.readFileStream(path, options.encoding);
126
+ }
127
+ return this.client.readFile(path, options.encoding);
128
+ }
79
129
  }
package/tests/test2.ts CHANGED
@@ -7,6 +7,8 @@ import {
7
7
  quickExecuteStream,
8
8
  quickMoveFile,
9
9
  quickMoveFileStream,
10
+ quickReadFile,
11
+ quickReadFileStream,
10
12
  quickRenameFile,
11
13
  quickRenameFileStream,
12
14
  quickWriteFile,
@@ -683,6 +685,223 @@ async function testHttpClient() {
683
685
  );
684
686
  }
685
687
 
688
+ // Test 25: File reading
689
+ console.log("Test 25: File reading");
690
+ try {
691
+ // First create a file to read
692
+ const readContent = "This is a test file for reading\nLine 2\nLine 3";
693
+ await quickWriteFile("file-to-read.txt", readContent);
694
+ console.log("✅ Test file created for reading");
695
+
696
+ // Read the file
697
+ const result = await quickReadFile("file-to-read.txt");
698
+ console.log("✅ File read successfully:", result.success);
699
+ console.log(" Path:", result.path);
700
+ console.log(" Content length:", result.content.length, "characters");
701
+ console.log(" Exit code:", result.exitCode);
702
+
703
+ // Verify the content
704
+ console.log("✅ File content verified:", result.content === readContent);
705
+ console.log(" Content:", result.content);
706
+ console.log("✅ File reading test completed\n");
707
+ } catch (error) {
708
+ console.error("❌ Test 25 failed:", error);
709
+ }
710
+
711
+ // Test 26: File reading with custom encoding
712
+ console.log("Test 26: File reading with custom encoding");
713
+ try {
714
+ // Create a file with special characters
715
+ const specialContent = "Hello 世界! 🌍 Test with emojis and unicode";
716
+ await quickWriteFile("special-chars.txt", specialContent, "utf-8");
717
+ console.log("✅ Special characters file created");
718
+
719
+ // Read with explicit UTF-8 encoding
720
+ const result = await quickReadFile("special-chars.txt", "utf-8");
721
+ console.log(
722
+ "✅ Special characters file read successfully:",
723
+ result.success
724
+ );
725
+ console.log(
726
+ "✅ Special characters verified:",
727
+ result.content === specialContent
728
+ );
729
+ console.log(" Content:", result.content);
730
+ console.log("✅ Custom encoding test completed\n");
731
+ } catch (error) {
732
+ console.error("❌ Test 26 failed:", error);
733
+ }
734
+
735
+ // Test 27: File reading in nested directories
736
+ console.log("Test 27: File reading in nested directories");
737
+ try {
738
+ // Create a file in a nested directory
739
+ const nestedContent = "This file is in a nested directory for reading";
740
+ await quickWriteFile("nested/read/test-nested-read.txt", nestedContent);
741
+ console.log("✅ Nested file created for reading");
742
+
743
+ // Read the nested file
744
+ const result = await quickReadFile("nested/read/test-nested-read.txt");
745
+ console.log("✅ Nested file read successfully:", result.success);
746
+ console.log(
747
+ "✅ Nested file content verified:",
748
+ result.content === nestedContent
749
+ );
750
+ console.log(" Content:", result.content);
751
+ console.log("✅ Nested directory reading test completed\n");
752
+ } catch (error) {
753
+ console.error("❌ Test 27 failed:", error);
754
+ }
755
+
756
+ // Test 28: Streaming file reading
757
+ console.log("Test 28: Streaming file reading");
758
+ try {
759
+ const client = createClient();
760
+ await client.createSession();
761
+
762
+ // Create a file to read via streaming
763
+ const streamReadContent =
764
+ "This file will be read via streaming\nLine 2\nLine 3";
765
+ await client.writeFile("stream-read-file.txt", streamReadContent);
766
+ console.log("✅ Test file created for streaming reading");
767
+
768
+ let readContent = "";
769
+ let readCompleted = false;
770
+
771
+ // Set up event handlers for streaming
772
+ client.setOnStreamEvent((event) => {
773
+ if (event.type === "command_complete" && event.content) {
774
+ readContent = event.content;
775
+ readCompleted = true;
776
+ }
777
+ });
778
+
779
+ console.log(" Starting streaming file reading...");
780
+ await client.readFileStream("stream-read-file.txt");
781
+ console.log("✅ Streaming file reading completed");
782
+
783
+ // Verify the content was read correctly
784
+ console.log(
785
+ "✅ Streaming read content verified:",
786
+ readContent === streamReadContent
787
+ );
788
+ console.log(" Content length:", readContent.length, "characters");
789
+
790
+ client.clearSession();
791
+ console.log("✅ Streaming file reading test completed\n");
792
+ } catch (error) {
793
+ console.error("❌ Test 28 failed:", error);
794
+ }
795
+
796
+ // Test 29: Quick streaming file reading
797
+ console.log("Test 29: Quick streaming file reading");
798
+ try {
799
+ // Create a file for quick streaming read
800
+ const quickReadContent = "Quick streaming read test content";
801
+ await quickWriteFile("quick-read-stream.txt", quickReadContent);
802
+ console.log("✅ Test file created for quick streaming reading");
803
+
804
+ let quickReadContentReceived = "";
805
+ let quickReadCompleted = false;
806
+
807
+ console.log(" Starting quick streaming file reading...");
808
+ await quickReadFileStream("quick-read-stream.txt", "utf-8", {
809
+ onStreamEvent: (event) => {
810
+ if (event.type === "command_complete" && event.content) {
811
+ quickReadContentReceived = event.content;
812
+ quickReadCompleted = true;
813
+ }
814
+ },
815
+ });
816
+ console.log("✅ Quick streaming file reading completed");
817
+
818
+ // Verify the content
819
+ console.log(
820
+ "✅ Quick streaming read verified:",
821
+ quickReadContentReceived === quickReadContent
822
+ );
823
+ console.log(" Content:", quickReadContentReceived);
824
+ console.log("✅ Quick streaming file reading test completed\n");
825
+ } catch (error) {
826
+ console.error("❌ Test 29 failed:", error);
827
+ }
828
+
829
+ // Test 30: File reading with session management
830
+ console.log("Test 30: File reading with session management");
831
+ try {
832
+ const client = createClient();
833
+ const sessionId = await client.createSession();
834
+
835
+ // Create a file in session
836
+ const sessionReadContent =
837
+ "This file was created and read with session management";
838
+ await client.writeFile(
839
+ "session-read-file.txt",
840
+ sessionReadContent,
841
+ "utf-8",
842
+ sessionId
843
+ );
844
+ console.log("✅ Session file created for reading");
845
+
846
+ // Read the file in the same session
847
+ const result = await client.readFile(
848
+ "session-read-file.txt",
849
+ "utf-8",
850
+ sessionId
851
+ );
852
+ console.log("✅ Session file read successfully:", result.success);
853
+ console.log(
854
+ "✅ Session file content verified:",
855
+ result.content === sessionReadContent
856
+ );
857
+ console.log(" Session ID:", sessionId);
858
+ console.log(" Content:", result.content);
859
+
860
+ client.clearSession();
861
+ console.log("✅ Session file reading test completed\n");
862
+ } catch (error) {
863
+ console.error("❌ Test 30 failed:", error);
864
+ }
865
+
866
+ // Test 31: Error handling for file reading
867
+ console.log("Test 31: Error handling for file reading");
868
+ try {
869
+ // Try to read a non-existent file
870
+ await quickReadFile("non-existent-read-file.txt");
871
+ console.log("❌ Should have failed for non-existent file");
872
+ } catch (error) {
873
+ console.log("✅ Error handling works for non-existent files");
874
+ console.log(
875
+ " Error:",
876
+ error instanceof Error ? error.message : "Unknown error"
877
+ );
878
+ }
879
+
880
+ try {
881
+ // Try to read from a dangerous path
882
+ await quickReadFile("/etc/passwd");
883
+ console.log("❌ Should have failed for dangerous path");
884
+ } catch (error) {
885
+ console.log("✅ Error handling works for dangerous paths");
886
+ console.log(
887
+ " Error:",
888
+ error instanceof Error ? error.message : "Unknown error"
889
+ );
890
+ }
891
+
892
+ try {
893
+ // Try to read with empty path
894
+ await quickReadFile("");
895
+ console.log("❌ Should have failed for empty path");
896
+ } catch (error) {
897
+ console.log("✅ Error handling works for empty paths");
898
+ console.log(
899
+ " Error:",
900
+ error instanceof Error ? error.message : "Unknown error",
901
+ "\n"
902
+ );
903
+ }
904
+
686
905
  console.log("🎉 All tests completed!");
687
906
  }
688
907