@superhq/shuru 0.3.1 → 0.4.0

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/README.md CHANGED
@@ -34,6 +34,16 @@ await sb.watch("/workspace", (event) => {
34
34
  await sb.writeFile("/tmp/app.ts", "console.log('hi')");
35
35
  const data = await sb.readFile("/tmp/app.ts"); // Uint8Array
36
36
 
37
+ // Filesystem operations
38
+ await sb.mkdir("/workspace/src");
39
+ const entries = await sb.readDir("/workspace"); // { name, type, size }[]
40
+ const info = await sb.stat("/tmp/app.ts"); // { size, mode, mtime, isDir, ... }
41
+ await sb.copy("/tmp/app.ts", "/tmp/backup.ts");
42
+ await sb.rename("/tmp/backup.ts", "/tmp/old.ts");
43
+ await sb.chmod("/tmp/app.ts", 0o755);
44
+ await sb.remove("/tmp/old.ts");
45
+ if (await sb.exists("/tmp/app.ts")) { /* ... */ }
46
+
37
47
  // Checkpoint — save disk state and stop
38
48
  await sb.checkpoint("my-env");
39
49
  ```
@@ -133,6 +143,50 @@ Read a file from the VM. Returns raw bytes. Use `new TextDecoder().decode(data)`
133
143
 
134
144
  Write a file to the VM. Accepts raw bytes or a string.
135
145
 
146
+ ### `sandbox.mkdir(path, opts?): Promise<void>`
147
+
148
+ Create a directory. Creates parent directories by default.
149
+
150
+ | Option | Type | Default | Description |
151
+ |--------|------|---------|-------------|
152
+ | `recursive` | `boolean` | `true` | Create parent directories |
153
+
154
+ ### `sandbox.readDir(path): Promise<DirEntry[]>`
155
+
156
+ List the contents of a directory. Each entry has `name`, `type` (`"file"`, `"dir"`, or `"symlink"`), and `size` in bytes.
157
+
158
+ ### `sandbox.stat(path): Promise<StatResult>`
159
+
160
+ Get file metadata. Returns `{ size, mode, mtime, isDir, isFile, isSymlink }`. `mtime` is seconds since the Unix epoch. `mode` includes the file type bits (e.g. `0o100644` for a regular file with 644 permissions).
161
+
162
+ ### `sandbox.remove(path, opts?): Promise<void>`
163
+
164
+ Delete a file or empty directory. To remove a non-empty directory, pass `{ recursive: true }`.
165
+
166
+ | Option | Type | Default | Description |
167
+ |--------|------|---------|-------------|
168
+ | `recursive` | `boolean` | `false` | Remove directories and their contents |
169
+
170
+ ### `sandbox.rename(oldPath, newPath): Promise<void>`
171
+
172
+ Move or rename a file or directory within the guest filesystem. Atomic on the same filesystem.
173
+
174
+ ### `sandbox.copy(src, dst, opts?): Promise<void>`
175
+
176
+ Copy a file. To copy a directory tree, pass `{ recursive: true }`.
177
+
178
+ | Option | Type | Default | Description |
179
+ |--------|------|---------|-------------|
180
+ | `recursive` | `boolean` | `false` | Copy directories recursively |
181
+
182
+ ### `sandbox.chmod(path, mode): Promise<void>`
183
+
184
+ Change file permissions. `mode` is a numeric permission value (e.g. `0o755`).
185
+
186
+ ### `sandbox.exists(path): Promise<boolean>`
187
+
188
+ Check if a path exists. Returns `true` if it does, `false` otherwise.
189
+
136
190
  ### `sandbox.checkpoint(name): Promise<void>`
137
191
 
138
192
  Save the VM's disk state and stop the VM. To continue working, call `Sandbox.start({ from: name })`.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@superhq/shuru",
3
- "version": "0.3.1",
3
+ "version": "0.4.0",
4
4
  "type": "module",
5
5
  "main": "src/index.ts",
6
6
  "types": "src/index.ts",
package/src/index.ts CHANGED
@@ -1,12 +1,17 @@
1
1
  export { SandboxProcess } from "./process-handle";
2
2
  export { Sandbox } from "./sandbox";
3
3
  export type {
4
+ CopyOptions,
5
+ DirEntry,
4
6
  ExecOptions,
5
7
  ExecResult,
6
8
  FileChangeEvent,
9
+ MkdirOptions,
7
10
  NetworkConfig,
11
+ RemoveOptions,
8
12
  SecretConfig,
9
13
  SpawnOptions,
10
14
  StartOptions,
15
+ StatResult,
11
16
  WatchOptions,
12
17
  } from "./types";
package/src/sandbox.ts CHANGED
@@ -1,11 +1,16 @@
1
1
  import { ShuruProcess } from "./process";
2
2
  import { SandboxProcess } from "./process-handle";
3
3
  import type {
4
+ CopyOptions,
5
+ DirEntry,
4
6
  ExecOptions,
5
7
  ExecResult,
6
8
  FileChangeEvent,
9
+ MkdirOptions,
10
+ RemoveOptions,
7
11
  SpawnOptions,
8
12
  StartOptions,
13
+ StatResult,
9
14
  WatchOptions,
10
15
  } from "./types";
11
16
 
@@ -16,6 +21,13 @@ const Method = {
16
21
  WRITE_FILE: "write_file",
17
22
  CHECKPOINT: "checkpoint",
18
23
  WATCH: "watch",
24
+ MKDIR: "mkdir",
25
+ READ_DIR: "read_dir",
26
+ STAT: "stat",
27
+ REMOVE: "remove",
28
+ RENAME: "rename",
29
+ COPY: "copy",
30
+ CHMOD: "chmod",
19
31
  } as const;
20
32
 
21
33
  export class Sandbox {
@@ -97,6 +109,74 @@ export class Sandbox {
97
109
  await this.proc.send(Method.WRITE_FILE, { path, content: b64 });
98
110
  }
99
111
 
112
+ async mkdir(path: string, opts?: MkdirOptions): Promise<void> {
113
+ await this.proc.send(Method.MKDIR, {
114
+ path,
115
+ recursive: opts?.recursive ?? true,
116
+ });
117
+ }
118
+
119
+ async readDir(path: string): Promise<DirEntry[]> {
120
+ const resp = await this.proc.send(Method.READ_DIR, { path });
121
+ const r = resp.result as { entries: DirEntry[] };
122
+ return r.entries;
123
+ }
124
+
125
+ async stat(path: string): Promise<StatResult> {
126
+ const resp = await this.proc.send(Method.STAT, { path });
127
+ const r = resp.result as {
128
+ size: number;
129
+ mode: number;
130
+ mtime: number;
131
+ is_dir: boolean;
132
+ is_file: boolean;
133
+ is_symlink: boolean;
134
+ };
135
+ return {
136
+ size: r.size,
137
+ mode: r.mode,
138
+ mtime: r.mtime,
139
+ isDir: r.is_dir,
140
+ isFile: r.is_file,
141
+ isSymlink: r.is_symlink,
142
+ };
143
+ }
144
+
145
+ async remove(path: string, opts?: RemoveOptions): Promise<void> {
146
+ await this.proc.send(Method.REMOVE, {
147
+ path,
148
+ recursive: opts?.recursive ?? false,
149
+ });
150
+ }
151
+
152
+ async rename(oldPath: string, newPath: string): Promise<void> {
153
+ await this.proc.send(Method.RENAME, {
154
+ old_path: oldPath,
155
+ new_path: newPath,
156
+ });
157
+ }
158
+
159
+ async copy(src: string, dst: string, opts?: CopyOptions): Promise<void> {
160
+ await this.proc.send(Method.COPY, {
161
+ src,
162
+ dst,
163
+ recursive: opts?.recursive ?? false,
164
+ });
165
+ }
166
+
167
+ async chmod(path: string, mode: number): Promise<void> {
168
+ await this.proc.send(Method.CHMOD, { path, mode });
169
+ }
170
+
171
+ async exists(path: string): Promise<boolean> {
172
+ try {
173
+ await this.stat(path);
174
+ return true;
175
+ } catch {
176
+ return false;
177
+ }
178
+ }
179
+
100
180
  async checkpoint(name: string): Promise<void> {
101
181
  await this.proc.send(Method.CHECKPOINT, { name });
102
182
  this.stopped = true;
package/src/types.ts CHANGED
@@ -50,6 +50,36 @@ export interface FileChangeEvent {
50
50
  event: "create" | "modify" | "delete" | "rename";
51
51
  }
52
52
 
53
+ // --- Filesystem types ---
54
+
55
+ export interface DirEntry {
56
+ name: string;
57
+ type: "file" | "dir" | "symlink";
58
+ size: number;
59
+ }
60
+
61
+ export interface StatResult {
62
+ size: number;
63
+ mode: number;
64
+ /** Seconds since Unix epoch */
65
+ mtime: number;
66
+ isDir: boolean;
67
+ isFile: boolean;
68
+ isSymlink: boolean;
69
+ }
70
+
71
+ export interface MkdirOptions {
72
+ recursive?: boolean;
73
+ }
74
+
75
+ export interface RemoveOptions {
76
+ recursive?: boolean;
77
+ }
78
+
79
+ export interface CopyOptions {
80
+ recursive?: boolean;
81
+ }
82
+
53
83
  // --- JSON-RPC 2.0 wire types (internal) ---
54
84
 
55
85
  export interface JsonRpcResult {