@slicervm/sdk 0.1.2 → 0.1.3

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/dist/index.d.cts CHANGED
@@ -143,6 +143,42 @@ interface FSMkdirRequest {
143
143
  recursive?: boolean;
144
144
  mode?: string;
145
145
  }
146
+ type FSWatchEventType = 'create' | 'write' | 'remove' | 'rename' | 'chmod';
147
+ interface FSWatchRequest {
148
+ /** Absolute paths inside the VM to watch. At least one is required. */
149
+ paths: string[];
150
+ /** Optional glob patterns to filter event paths (e.g. `["*.go", "bin/*"]`). */
151
+ patterns?: string[];
152
+ /**
153
+ * Restrict to a subset of event types. When omitted, all types are delivered.
154
+ */
155
+ events?: FSWatchEventType[];
156
+ /** UID used by the agent to resolve `~` in paths. Default: 0 (root). */
157
+ uid?: number;
158
+ /** Watch directories recursively. */
159
+ recursive?: boolean;
160
+ /** Stop the stream after the first matching event. */
161
+ oneShot?: boolean;
162
+ /** Coalesce events arriving within this window. Go-duration string e.g. `"100ms"`. */
163
+ debounce?: string;
164
+ /** Server-side wall-clock cap on the stream. Go-duration string e.g. `"5m"`. */
165
+ timeout?: string;
166
+ /** Stop after delivering this many events. */
167
+ maxEvents?: number;
168
+ /** Forwarded as the SSE `Last-Event-ID` header for cross-connection resume. */
169
+ lastEventId?: string;
170
+ }
171
+ interface FSWatchEvent {
172
+ /** Monotonic per-stream ID (from the SSE `id:` line). */
173
+ id: number;
174
+ type: FSWatchEventType | string;
175
+ path: string;
176
+ /** RFC3339Nano string (when present). */
177
+ timestamp: string;
178
+ size: number;
179
+ isDir: boolean;
180
+ message?: string;
181
+ }
146
182
  interface ShutdownRequest {
147
183
  action?: 'shutdown' | 'reboot';
148
184
  }
@@ -271,7 +307,7 @@ declare class TransportClient {
271
307
  /** Raw-bytes request (for binary cp endpoints). */
272
308
  requestRaw(method: string, reqPath: string, body?: Buffer, contentType?: string): Promise<Buffer>;
273
309
  /** Streaming request producing a Node Readable of the response body. */
274
- requestStreamRaw(method: string, reqPath: string, body?: Buffer | Readable, contentType?: string): Promise<IncomingMessage>;
310
+ requestStreamRaw(method: string, reqPath: string, body?: Buffer | Readable, contentType?: string, extraHeaders?: Record<string, string>): Promise<IncomingMessage>;
275
311
  /** Yields decoded JSON frames from an NDJSON response (one JSON object per line). */
276
312
  requestNDJSON<Frame = unknown>(method: string, reqPath: string, body?: Buffer | Readable): AsyncGenerator<Frame, void, void>;
277
313
  }
@@ -387,6 +423,22 @@ declare class VMFileSystem {
387
423
  }): Promise<void>;
388
424
  /** Upload a tar archive, expanded into the VM at `path`. */
389
425
  tarTo(path: string, tar: Buffer | Readable): Promise<void>;
426
+ /**
427
+ * Open a Server-Sent Events stream of filesystem events from the VM.
428
+ * Yields one `FSWatchEvent` per agent-side event. The stream stays open
429
+ * until the supplied request's `timeout` / `maxEvents` is hit, the daemon
430
+ * tears it down, or the caller breaks out of the loop.
431
+ *
432
+ * Heartbeat SSE comments and named `event:` lines are silently dropped.
433
+ *
434
+ * Example:
435
+ * ```ts
436
+ * for await (const e of vm.fs.watch({ paths: ['/tmp'], recursive: true })) {
437
+ * console.log(e.type, e.path);
438
+ * }
439
+ * ```
440
+ */
441
+ watch(req: FSWatchRequest): AsyncGenerator<FSWatchEvent, void, void>;
390
442
  /** Download `path` from the VM as a tar archive. */
391
443
  tarFrom(path: string): Promise<Buffer>;
392
444
  }
@@ -523,4 +575,4 @@ declare class SlicerClient {
523
575
  getInfo(): Promise<SlicerInfo>;
524
576
  }
525
577
 
526
- export { type AddressMapping, type AgentHealth, type CreateSecretRequest, type CreateVMOptions, type CreateVMRequest, type CreateVMResponse, type DeleteResponse, type ExecFrame, type ExecRequest, type ExecResult, type ExecResultBinary, type ExecStdio, ExecStdioBase64, ExecStdioText, type FSEntry, type FSMkdirRequest, Forwarder, type ForwarderListener, type ForwarderOptions, GiB, type HostGroup, HostGroupsAPI, type ListOptions, MiB, NonRootUser, type Secret, SecretExistsError, SecretsAPI, type ShutdownRequest, SlicerAPIError, SlicerClient, type SlicerClientOptions, type SlicerInfo, type UpdateSecretRequest, VM, VMFileSystem, type VMInfo, type VMInit, type VMLogs, type VMSnapshot, type VMStat, VMsAPI, type WaitOptions, parseAddressMapping, resolveTransport };
578
+ export { type AddressMapping, type AgentHealth, type CreateSecretRequest, type CreateVMOptions, type CreateVMRequest, type CreateVMResponse, type DeleteResponse, type ExecFrame, type ExecRequest, type ExecResult, type ExecResultBinary, type ExecStdio, ExecStdioBase64, ExecStdioText, type FSEntry, type FSMkdirRequest, type FSWatchEvent, type FSWatchEventType, type FSWatchRequest, Forwarder, type ForwarderListener, type ForwarderOptions, GiB, type HostGroup, HostGroupsAPI, type ListOptions, MiB, NonRootUser, type Secret, SecretExistsError, SecretsAPI, type ShutdownRequest, SlicerAPIError, SlicerClient, type SlicerClientOptions, type SlicerInfo, type UpdateSecretRequest, VM, VMFileSystem, type VMInfo, type VMInit, type VMLogs, type VMSnapshot, type VMStat, VMsAPI, type WaitOptions, parseAddressMapping, resolveTransport };
package/dist/index.d.ts CHANGED
@@ -143,6 +143,42 @@ interface FSMkdirRequest {
143
143
  recursive?: boolean;
144
144
  mode?: string;
145
145
  }
146
+ type FSWatchEventType = 'create' | 'write' | 'remove' | 'rename' | 'chmod';
147
+ interface FSWatchRequest {
148
+ /** Absolute paths inside the VM to watch. At least one is required. */
149
+ paths: string[];
150
+ /** Optional glob patterns to filter event paths (e.g. `["*.go", "bin/*"]`). */
151
+ patterns?: string[];
152
+ /**
153
+ * Restrict to a subset of event types. When omitted, all types are delivered.
154
+ */
155
+ events?: FSWatchEventType[];
156
+ /** UID used by the agent to resolve `~` in paths. Default: 0 (root). */
157
+ uid?: number;
158
+ /** Watch directories recursively. */
159
+ recursive?: boolean;
160
+ /** Stop the stream after the first matching event. */
161
+ oneShot?: boolean;
162
+ /** Coalesce events arriving within this window. Go-duration string e.g. `"100ms"`. */
163
+ debounce?: string;
164
+ /** Server-side wall-clock cap on the stream. Go-duration string e.g. `"5m"`. */
165
+ timeout?: string;
166
+ /** Stop after delivering this many events. */
167
+ maxEvents?: number;
168
+ /** Forwarded as the SSE `Last-Event-ID` header for cross-connection resume. */
169
+ lastEventId?: string;
170
+ }
171
+ interface FSWatchEvent {
172
+ /** Monotonic per-stream ID (from the SSE `id:` line). */
173
+ id: number;
174
+ type: FSWatchEventType | string;
175
+ path: string;
176
+ /** RFC3339Nano string (when present). */
177
+ timestamp: string;
178
+ size: number;
179
+ isDir: boolean;
180
+ message?: string;
181
+ }
146
182
  interface ShutdownRequest {
147
183
  action?: 'shutdown' | 'reboot';
148
184
  }
@@ -271,7 +307,7 @@ declare class TransportClient {
271
307
  /** Raw-bytes request (for binary cp endpoints). */
272
308
  requestRaw(method: string, reqPath: string, body?: Buffer, contentType?: string): Promise<Buffer>;
273
309
  /** Streaming request producing a Node Readable of the response body. */
274
- requestStreamRaw(method: string, reqPath: string, body?: Buffer | Readable, contentType?: string): Promise<IncomingMessage>;
310
+ requestStreamRaw(method: string, reqPath: string, body?: Buffer | Readable, contentType?: string, extraHeaders?: Record<string, string>): Promise<IncomingMessage>;
275
311
  /** Yields decoded JSON frames from an NDJSON response (one JSON object per line). */
276
312
  requestNDJSON<Frame = unknown>(method: string, reqPath: string, body?: Buffer | Readable): AsyncGenerator<Frame, void, void>;
277
313
  }
@@ -387,6 +423,22 @@ declare class VMFileSystem {
387
423
  }): Promise<void>;
388
424
  /** Upload a tar archive, expanded into the VM at `path`. */
389
425
  tarTo(path: string, tar: Buffer | Readable): Promise<void>;
426
+ /**
427
+ * Open a Server-Sent Events stream of filesystem events from the VM.
428
+ * Yields one `FSWatchEvent` per agent-side event. The stream stays open
429
+ * until the supplied request's `timeout` / `maxEvents` is hit, the daemon
430
+ * tears it down, or the caller breaks out of the loop.
431
+ *
432
+ * Heartbeat SSE comments and named `event:` lines are silently dropped.
433
+ *
434
+ * Example:
435
+ * ```ts
436
+ * for await (const e of vm.fs.watch({ paths: ['/tmp'], recursive: true })) {
437
+ * console.log(e.type, e.path);
438
+ * }
439
+ * ```
440
+ */
441
+ watch(req: FSWatchRequest): AsyncGenerator<FSWatchEvent, void, void>;
390
442
  /** Download `path` from the VM as a tar archive. */
391
443
  tarFrom(path: string): Promise<Buffer>;
392
444
  }
@@ -523,4 +575,4 @@ declare class SlicerClient {
523
575
  getInfo(): Promise<SlicerInfo>;
524
576
  }
525
577
 
526
- export { type AddressMapping, type AgentHealth, type CreateSecretRequest, type CreateVMOptions, type CreateVMRequest, type CreateVMResponse, type DeleteResponse, type ExecFrame, type ExecRequest, type ExecResult, type ExecResultBinary, type ExecStdio, ExecStdioBase64, ExecStdioText, type FSEntry, type FSMkdirRequest, Forwarder, type ForwarderListener, type ForwarderOptions, GiB, type HostGroup, HostGroupsAPI, type ListOptions, MiB, NonRootUser, type Secret, SecretExistsError, SecretsAPI, type ShutdownRequest, SlicerAPIError, SlicerClient, type SlicerClientOptions, type SlicerInfo, type UpdateSecretRequest, VM, VMFileSystem, type VMInfo, type VMInit, type VMLogs, type VMSnapshot, type VMStat, VMsAPI, type WaitOptions, parseAddressMapping, resolveTransport };
578
+ export { type AddressMapping, type AgentHealth, type CreateSecretRequest, type CreateVMOptions, type CreateVMRequest, type CreateVMResponse, type DeleteResponse, type ExecFrame, type ExecRequest, type ExecResult, type ExecResultBinary, type ExecStdio, ExecStdioBase64, ExecStdioText, type FSEntry, type FSMkdirRequest, type FSWatchEvent, type FSWatchEventType, type FSWatchRequest, Forwarder, type ForwarderListener, type ForwarderOptions, GiB, type HostGroup, HostGroupsAPI, type ListOptions, MiB, NonRootUser, type Secret, SecretExistsError, SecretsAPI, type ShutdownRequest, SlicerAPIError, SlicerClient, type SlicerClientOptions, type SlicerInfo, type UpdateSecretRequest, VM, VMFileSystem, type VMInfo, type VMInit, type VMLogs, type VMSnapshot, type VMStat, VMsAPI, type WaitOptions, parseAddressMapping, resolveTransport };
package/dist/index.js CHANGED
@@ -150,9 +150,9 @@ var TransportClient = class {
150
150
  });
151
151
  }
152
152
  /** Streaming request producing a Node Readable of the response body. */
153
- requestStreamRaw(method, reqPath, body, contentType = "application/octet-stream") {
153
+ requestStreamRaw(method, reqPath, body, contentType = "application/octet-stream", extraHeaders = {}) {
154
154
  return new Promise((resolve, reject) => {
155
- const headers = {};
155
+ const headers = { ...extraHeaders };
156
156
  if (body instanceof Buffer) {
157
157
  headers["Content-Type"] = contentType;
158
158
  headers["Content-Length"] = String(body.length);
@@ -639,6 +639,85 @@ var VMFileSystem = class {
639
639
  );
640
640
  for await (const _ of res) void _;
641
641
  }
642
+ /**
643
+ * Open a Server-Sent Events stream of filesystem events from the VM.
644
+ * Yields one `FSWatchEvent` per agent-side event. The stream stays open
645
+ * until the supplied request's `timeout` / `maxEvents` is hit, the daemon
646
+ * tears it down, or the caller breaks out of the loop.
647
+ *
648
+ * Heartbeat SSE comments and named `event:` lines are silently dropped.
649
+ *
650
+ * Example:
651
+ * ```ts
652
+ * for await (const e of vm.fs.watch({ paths: ['/tmp'], recursive: true })) {
653
+ * console.log(e.type, e.path);
654
+ * }
655
+ * ```
656
+ */
657
+ async *watch(req) {
658
+ if (!req.paths || req.paths.length === 0) {
659
+ throw new Error("vm.fs.watch: paths is required");
660
+ }
661
+ const q = new URLSearchParams();
662
+ for (const p of req.paths) if (p) q.append("paths", p);
663
+ for (const p of req.patterns ?? []) if (p) q.append("patterns", p);
664
+ for (const e of req.events ?? []) if (e) q.append("events", e);
665
+ if (req.uid !== void 0 && req.uid !== 0) q.set("uid", String(req.uid));
666
+ if (req.recursive) q.set("recursive", "true");
667
+ if (req.oneShot) q.set("one_shot", "true");
668
+ if (req.debounce) q.set("debounce", req.debounce);
669
+ if (req.timeout) q.set("timeout", req.timeout);
670
+ if (req.maxEvents !== void 0 && req.maxEvents > 0) {
671
+ q.set("max_events", String(req.maxEvents));
672
+ }
673
+ const extraHeaders = { Accept: "text/event-stream" };
674
+ if (req.lastEventId) extraHeaders["Last-Event-ID"] = req.lastEventId;
675
+ const res = await this.transport.requestStreamRaw(
676
+ "GET",
677
+ `/vm/${encodeURIComponent(this.hostname)}/fs/watch?${q.toString()}`,
678
+ void 0,
679
+ void 0,
680
+ extraHeaders
681
+ );
682
+ res.setEncoding("utf8");
683
+ let buffer = "";
684
+ let dataLines = [];
685
+ let pendingId = 0;
686
+ for await (const chunk of res) {
687
+ buffer += chunk;
688
+ let nl;
689
+ while ((nl = buffer.indexOf("\n")) >= 0) {
690
+ const raw = buffer.slice(0, nl);
691
+ buffer = buffer.slice(nl + 1);
692
+ const line = raw.endsWith("\r") ? raw.slice(0, -1) : raw;
693
+ if (line === "") {
694
+ if (dataLines.length > 0) {
695
+ const payload = dataLines.join("\n");
696
+ dataLines = [];
697
+ try {
698
+ const parsed = JSON.parse(payload);
699
+ const evt = {
700
+ id: typeof parsed.id === "number" && parsed.id !== 0 ? parsed.id : pendingId,
701
+ type: parsed.type ?? "",
702
+ path: parsed.path ?? "",
703
+ timestamp: parsed.timestamp ?? "",
704
+ size: parsed.size ?? 0,
705
+ isDir: parsed.isDir ?? false,
706
+ ...parsed.message !== void 0 && { message: parsed.message }
707
+ };
708
+ yield evt;
709
+ } catch {
710
+ }
711
+ }
712
+ } else if (line.startsWith(":")) ; else if (line.startsWith("data:")) {
713
+ dataLines.push(line.slice(5).replace(/^ /, ""));
714
+ } else if (line.startsWith("id:")) {
715
+ const v = parseInt(line.slice(3).trim(), 10);
716
+ if (!Number.isNaN(v)) pendingId = v;
717
+ }
718
+ }
719
+ }
720
+ }
642
721
  /** Download `path` from the VM as a tar archive. */
643
722
  async tarFrom(path3) {
644
723
  const q = new URLSearchParams({ path: path3, mode: "tar" });