skyffla 1.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/PUBLISHING.md ADDED
@@ -0,0 +1,79 @@
1
+ # Publishing
2
+
3
+ The Node.js wrapper lives at `wrappers/node` and publishes to npm as `skyffla`.
4
+
5
+ ## Versioning
6
+
7
+ - Release versions:
8
+ - the npm package version lives in `wrappers/node/package.json`
9
+ - the runtime wrapper version also lives in `wrappers/node/src/version.js`
10
+ - both should track the repo release tag version exactly
11
+ - a Git tag `vX.Y.Z` should only be pushed after the Node.js package version
12
+ is set to `X.Y.Z`
13
+
14
+ - Protocol compatibility is a separate contract. See:
15
+ - `docs/versioning.md`
16
+ - `docs/machine-protocol.md`
17
+
18
+ The wrapper validates machine protocol compatibility from `room_welcome` and
19
+ requires the same machine protocol major version as the published package.
20
+
21
+ Official packages also probe `skyffla --version` before starting a room and
22
+ reject a CLI binary whose release version does not exactly match the npm
23
+ package version. This is a wrapper packaging policy on top of machine protocol
24
+ compatibility. `SKYFFLA_SKIP_VERSION_CHECK=1` bypasses only the release-version
25
+ pairing check.
26
+
27
+ ## Release Flow
28
+
29
+ 1. Update the release version in:
30
+ - `Cargo.toml`
31
+ - `wrappers/node/package.json`
32
+ - `wrappers/node/src/version.js`
33
+ 2. Run the wrapper checks locally:
34
+
35
+ ```sh
36
+ cargo test -p skyffla --test machine_local_end_to_end
37
+ npm test --prefix wrappers/node
38
+ ```
39
+
40
+ 3. Push the release tag:
41
+
42
+ ```sh
43
+ git tag vX.Y.Z
44
+ git push origin vX.Y.Z
45
+ ```
46
+
47
+ 4. GitHub Actions will:
48
+ - build the local `skyffla` binary needed by the smoke tests
49
+ - run the Node.js wrapper test suite
50
+ - pack the npm tarball
51
+ - verify `package.json` matches the tag version
52
+ - publish `skyffla` to npm only if the repository variable
53
+ `PUBLISH_NODE_PACKAGE=1`
54
+
55
+ By default, tagged releases do not publish the npm package. This keeps normal
56
+ release tags safe until the npm package is claimed and trusted publishing is
57
+ configured.
58
+
59
+ ## npm Setup
60
+
61
+ The `Node Wrapper` workflow uses npm trusted publishing.
62
+
63
+ Configure trusted publishing on npm for the GitHub repository that owns
64
+ `.github/workflows/node-package.yml`:
65
+
66
+ - workflow: `Node Wrapper`
67
+ - workflow file: `.github/workflows/node-package.yml`
68
+ - environment: not required
69
+
70
+ No npm automation token should be stored once trusted publishing is configured.
71
+
72
+ ## Enable First Publish
73
+
74
+ When the `skyffla` npm name is ready:
75
+
76
+ 1. Create or claim the `skyffla` package on npm.
77
+ 2. Configure trusted publishing for the `Node Wrapper` workflow.
78
+ 3. Set the repository variable `PUBLISH_NODE_PACKAGE=1`.
79
+ 4. Cut or re-push the intended `vX.Y.Z` release tag.
package/README.md ADDED
@@ -0,0 +1,118 @@
1
+ # Skyffla Node.js Wrapper
2
+
3
+ Use this package to control an installed `skyffla` CLI from Node.js.
4
+
5
+ `npm install skyffla` installs the Node.js wrapper only. Install the `skyffla`
6
+ binary separately first.
7
+
8
+ ## 1. Install the `skyffla` binary
9
+
10
+ Add the Homebrew tap once:
11
+
12
+ ```sh
13
+ brew tap skyffla/skyffla
14
+ ```
15
+
16
+ Install the CLI:
17
+
18
+ ```sh
19
+ brew install skyffla
20
+ ```
21
+
22
+ Check that the binary is available:
23
+
24
+ ```sh
25
+ skyffla --version
26
+ ```
27
+
28
+ If you want source code, release notes, or other install paths, see the
29
+ [Skyffla repository on GitHub](https://github.com/skyffla/skyffla).
30
+
31
+ ## 2. Install the Node.js wrapper
32
+
33
+ ```sh
34
+ npm install skyffla
35
+ ```
36
+
37
+ The wrapper expects a `skyffla` binary in `PATH`. Override it with
38
+ `SKYFFLA_BIN` or the `binary` option when starting a room.
39
+
40
+ ## 3. Small example
41
+
42
+ Save this as `example.mjs`. Start `host` in one terminal, then start `join` in
43
+ another.
44
+
45
+ ```js
46
+ import { Room } from "skyffla";
47
+
48
+ async function main(role) {
49
+ const opener = role === "host" ? Room.host : Room.join;
50
+ const room = await opener("warehouse", { name: role });
51
+
52
+ try {
53
+ await room.waitForWelcome();
54
+ if (role === "host") {
55
+ const joined = await room.waitForMemberJoined({ name: "join" });
56
+ await room.sendChat("all", "hello from node");
57
+ console.log(await room.waitForChat({ fromName: "join" }));
58
+ await room.openMachineChannel("node-demo", {
59
+ to: joined.member.member_id,
60
+ name: "node-demo",
61
+ });
62
+ } else {
63
+ await room.waitForMemberSnapshot({ minMembers: 2 });
64
+ console.log(await room.waitForChat({ fromName: "host" }));
65
+ await room.sendChat("all", "hello from join");
66
+ const opened = await room.waitForChannelOpened({ channelId: "node-demo" });
67
+ await room.channel(opened.channel_id).send("hello over machine channel");
68
+ }
69
+ } finally {
70
+ await room.close();
71
+ }
72
+ }
73
+
74
+ await main(process.argv[2]);
75
+ ```
76
+
77
+ ```sh
78
+ node example.mjs host
79
+ node example.mjs join
80
+ ```
81
+
82
+ ## Version checks
83
+
84
+ Versioning is split across two layers:
85
+
86
+ - machine protocol compatibility follows the
87
+ [versioning policy](https://github.com/skyffla/skyffla/blob/main/docs/versioning.md)
88
+ and
89
+ [machine protocol reference](https://github.com/skyffla/skyffla/blob/main/docs/machine-protocol.md):
90
+ the wrapper requires the same machine protocol major version and tolerates
91
+ additive minor changes
92
+ - release pairing is stricter for published packages: by default, the wrapper
93
+ also checks that the spawned `skyffla` binary has the exact same release
94
+ version as the Node.js package
95
+
96
+ Set `SKYFFLA_SKIP_VERSION_CHECK=1` only if you intentionally want to run the
97
+ wrapper against a different `skyffla` release. This skips the package-to-binary
98
+ release match, but machine protocol compatibility is still enforced.
99
+
100
+ ## More examples
101
+
102
+ Runnable example apps live under `examples/node` in the GitHub repository:
103
+
104
+ - [examples/node](https://github.com/skyffla/skyffla/tree/main/examples/node):
105
+ shared Node.js example project
106
+ - [examples/node/simple-chat](https://github.com/skyffla/skyffla/tree/main/examples/node/simple-chat):
107
+ minimal interactive chat client for one room
108
+ - [examples/node/sync-chat-and-channel](https://github.com/skyffla/skyffla/tree/main/examples/node/sync-chat-and-channel):
109
+ chat plus one machine-channel exchange
110
+
111
+ ## Tests
112
+
113
+ From a repository checkout:
114
+
115
+ ```sh
116
+ cd wrappers/node
117
+ npm test
118
+ ```
package/package.json ADDED
@@ -0,0 +1,38 @@
1
+ {
2
+ "name": "skyffla",
3
+ "version": "1.1.3",
4
+ "description": "Node.js wrapper for skyffla peer-to-peer rooms and automation",
5
+ "type": "module",
6
+ "main": "./src/index.js",
7
+ "exports": {
8
+ ".": "./src/index.js"
9
+ },
10
+ "types": "./src/index.d.ts",
11
+ "files": [
12
+ "src",
13
+ "README.md",
14
+ "PUBLISHING.md"
15
+ ],
16
+ "engines": {
17
+ "node": ">=20"
18
+ },
19
+ "scripts": {
20
+ "test": "node --test"
21
+ },
22
+ "keywords": [
23
+ "skyffla",
24
+ "p2p",
25
+ "rooms",
26
+ "automation"
27
+ ],
28
+ "author": "Skyffla",
29
+ "license": "MIT",
30
+ "homepage": "https://github.com/skyffla/skyffla",
31
+ "repository": {
32
+ "type": "git",
33
+ "url": "git+https://github.com/skyffla/skyffla.git"
34
+ },
35
+ "bugs": {
36
+ "url": "https://github.com/skyffla/skyffla/issues"
37
+ }
38
+ }
package/src/errors.js ADDED
@@ -0,0 +1,14 @@
1
+ export class SkyfflaError extends Error {
2
+ constructor(message) {
3
+ super(message);
4
+ this.name = new.target.name;
5
+ }
6
+ }
7
+
8
+ export class SkyfflaProtocolError extends SkyfflaError {}
9
+
10
+ export class SkyfflaMachineProtocolMismatch extends SkyfflaProtocolError {}
11
+
12
+ export class SkyfflaProcessExited extends SkyfflaError {}
13
+
14
+ export class SkyfflaVersionMismatch extends SkyfflaError {}
package/src/index.d.ts ADDED
@@ -0,0 +1,336 @@
1
+ export const __version__: string;
2
+
3
+ export interface ProtocolVersion {
4
+ major: number;
5
+ minor: number;
6
+ }
7
+
8
+ export interface BlobRef {
9
+ hash: string;
10
+ format: BlobFormatValue;
11
+ }
12
+
13
+ export interface Member {
14
+ member_id: string;
15
+ name: string;
16
+ fingerprint?: string;
17
+ }
18
+
19
+ export interface RouteAll {
20
+ type: "all";
21
+ }
22
+
23
+ export interface RouteMember {
24
+ type: "member";
25
+ member_id: string;
26
+ }
27
+
28
+ export type Route = RouteAll | RouteMember;
29
+
30
+ export interface SendChat {
31
+ type: "send_chat";
32
+ to: Route;
33
+ text: string;
34
+ }
35
+
36
+ export interface SendFile {
37
+ type: "send_file";
38
+ channel_id: string;
39
+ to: Route;
40
+ path: string;
41
+ name?: string;
42
+ mime?: string;
43
+ }
44
+
45
+ export interface OpenChannel {
46
+ type: "open_channel";
47
+ channel_id: string;
48
+ kind: ChannelKindValue;
49
+ to: Route;
50
+ name?: string;
51
+ size?: number;
52
+ mime?: string;
53
+ blob?: BlobRef;
54
+ }
55
+
56
+ export interface AcceptChannel {
57
+ type: "accept_channel";
58
+ channel_id: string;
59
+ }
60
+
61
+ export interface RejectChannel {
62
+ type: "reject_channel";
63
+ channel_id: string;
64
+ reason?: string;
65
+ }
66
+
67
+ export interface SendChannelData {
68
+ type: "send_channel_data";
69
+ channel_id: string;
70
+ body: string;
71
+ }
72
+
73
+ export interface CloseChannel {
74
+ type: "close_channel";
75
+ channel_id: string;
76
+ reason?: string;
77
+ }
78
+
79
+ export interface ExportChannelFile {
80
+ type: "export_channel_file";
81
+ channel_id: string;
82
+ path: string;
83
+ }
84
+
85
+ export type MachineCommand =
86
+ | SendChat
87
+ | SendFile
88
+ | OpenChannel
89
+ | AcceptChannel
90
+ | RejectChannel
91
+ | SendChannelData
92
+ | CloseChannel
93
+ | ExportChannelFile;
94
+
95
+ export interface RoomWelcome {
96
+ type: "room_welcome";
97
+ protocol_version: ProtocolVersion;
98
+ room_id: string;
99
+ self_member: string;
100
+ host_member: string;
101
+ }
102
+
103
+ export interface MemberSnapshot {
104
+ type: "member_snapshot";
105
+ members: readonly Member[];
106
+ }
107
+
108
+ export interface MemberJoined {
109
+ type: "member_joined";
110
+ member: Member;
111
+ }
112
+
113
+ export interface MemberLeft {
114
+ type: "member_left";
115
+ member_id: string;
116
+ reason?: string;
117
+ }
118
+
119
+ export interface Chat {
120
+ type: "chat";
121
+ from: string;
122
+ from_name: string;
123
+ to: Route;
124
+ text: string;
125
+ }
126
+
127
+ export interface ChannelOpened {
128
+ type: "channel_opened";
129
+ channel_id: string;
130
+ kind: ChannelKindValue;
131
+ from: string;
132
+ from_name: string;
133
+ to: Route;
134
+ name?: string;
135
+ size?: number;
136
+ mime?: string;
137
+ blob?: BlobRef;
138
+ }
139
+
140
+ export interface ChannelAccepted {
141
+ type: "channel_accepted";
142
+ channel_id: string;
143
+ member_id: string;
144
+ member_name: string;
145
+ }
146
+
147
+ export interface ChannelRejected {
148
+ type: "channel_rejected";
149
+ channel_id: string;
150
+ member_id: string;
151
+ member_name: string;
152
+ reason?: string;
153
+ }
154
+
155
+ export interface ChannelData {
156
+ type: "channel_data";
157
+ channel_id: string;
158
+ from: string;
159
+ from_name: string;
160
+ body: string;
161
+ }
162
+
163
+ export interface ChannelClosed {
164
+ type: "channel_closed";
165
+ channel_id: string;
166
+ member_id: string;
167
+ member_name: string;
168
+ reason?: string;
169
+ }
170
+
171
+ export interface ChannelFileReady {
172
+ type: "channel_file_ready";
173
+ channel_id: string;
174
+ blob: BlobRef;
175
+ }
176
+
177
+ export interface ChannelFileExported {
178
+ type: "channel_file_exported";
179
+ channel_id: string;
180
+ path: string;
181
+ size: number;
182
+ }
183
+
184
+ export interface TransferProgress {
185
+ type: "transfer_progress";
186
+ channel_id: string;
187
+ item_kind: TransferItemKindValue;
188
+ name: string;
189
+ phase: TransferPhaseValue;
190
+ bytes_complete: number;
191
+ bytes_total?: number;
192
+ }
193
+
194
+ export interface ErrorEvent {
195
+ type: "error";
196
+ code: string;
197
+ message: string;
198
+ channel_id?: string;
199
+ }
200
+
201
+ export type MachineEvent =
202
+ | RoomWelcome
203
+ | MemberSnapshot
204
+ | MemberJoined
205
+ | MemberLeft
206
+ | Chat
207
+ | ChannelOpened
208
+ | ChannelAccepted
209
+ | ChannelRejected
210
+ | ChannelData
211
+ | ChannelClosed
212
+ | ChannelFileReady
213
+ | ChannelFileExported
214
+ | TransferProgress
215
+ | ErrorEvent;
216
+
217
+ export type ChannelKindValue = "machine" | "file" | "clipboard";
218
+ export type BlobFormatValue = "blob" | "collection";
219
+ export type TransferItemKindValue = "file" | "folder";
220
+ export type TransferPhaseValue = "preparing" | "downloading" | "exporting";
221
+
222
+ export const ChannelKind: Readonly<{
223
+ MACHINE: "machine";
224
+ FILE: "file";
225
+ CLIPBOARD: "clipboard";
226
+ }>;
227
+
228
+ export const BlobFormat: Readonly<{
229
+ BLOB: "blob";
230
+ COLLECTION: "collection";
231
+ }>;
232
+
233
+ export const TransferItemKind: Readonly<{
234
+ FILE: "file";
235
+ FOLDER: "folder";
236
+ }>;
237
+
238
+ export const TransferPhase: Readonly<{
239
+ PREPARING: "preparing";
240
+ DOWNLOADING: "downloading";
241
+ EXPORTING: "exporting";
242
+ }>;
243
+
244
+ export const MACHINE_PROTOCOL_VERSION: Readonly<ProtocolVersion>;
245
+
246
+ export class SkyfflaError extends Error {}
247
+ export class SkyfflaProtocolError extends SkyfflaError {}
248
+ export class SkyfflaMachineProtocolMismatch extends SkyfflaProtocolError {}
249
+ export class SkyfflaProcessExited extends SkyfflaError {}
250
+ export class SkyfflaVersionMismatch extends SkyfflaError {}
251
+
252
+ export interface SpawnOptions {
253
+ binary?: string;
254
+ server?: string;
255
+ name?: string;
256
+ downloadDir?: string;
257
+ local?: boolean;
258
+ }
259
+
260
+ export interface OpenChannelOptions {
261
+ kind: ChannelKindValue;
262
+ to: string | Route;
263
+ name?: string;
264
+ size?: number;
265
+ mime?: string;
266
+ blob?: BlobRef;
267
+ }
268
+
269
+ export interface OpenMachineChannelOptions {
270
+ to: string | Route;
271
+ name?: string;
272
+ }
273
+
274
+ export class MachineChannel {
275
+ readonly room: Room;
276
+ readonly channelId: string;
277
+ send(body: string): Promise<void>;
278
+ accept(): Promise<void>;
279
+ reject(reason?: string): Promise<void>;
280
+ close(reason?: string): Promise<void>;
281
+ exportFile(path: string): Promise<void>;
282
+ }
283
+
284
+ export class Room implements AsyncIterable<MachineEvent> {
285
+ readonly role: string;
286
+ readonly roomId: string;
287
+ readonly argv: readonly string[];
288
+ static host(roomId: string, options?: SpawnOptions): Promise<Room>;
289
+ static join(roomId: string, options?: SpawnOptions): Promise<Room>;
290
+ channel(channelId: string): MachineChannel;
291
+ send(command: MachineCommand): Promise<void>;
292
+ sendChat(to: string | Route, text: string): Promise<void>;
293
+ sendFile(
294
+ channelId: string,
295
+ to: string | Route,
296
+ path: string,
297
+ options?: { name?: string; mime?: string },
298
+ ): Promise<void>;
299
+ openChannel(channelId: string, options: OpenChannelOptions): Promise<MachineChannel>;
300
+ openMachineChannel(channelId: string, options: OpenMachineChannelOptions): Promise<MachineChannel>;
301
+ acceptChannel(channelId: string): Promise<void>;
302
+ rejectChannel(channelId: string, options?: { reason?: string }): Promise<void>;
303
+ sendChannelData(channelId: string, body: string): Promise<void>;
304
+ closeChannel(channelId: string, options?: { reason?: string }): Promise<void>;
305
+ exportChannelFile(channelId: string, path: string): Promise<void>;
306
+ recv(): Promise<MachineEvent>;
307
+ recvUntil(
308
+ predicate: (event: MachineEvent) => boolean,
309
+ options?: { timeout?: number },
310
+ ): Promise<MachineEvent>;
311
+ waitForWelcome(options?: { timeout?: number }): Promise<RoomWelcome>;
312
+ waitForMemberSnapshot(options?: { minMembers?: number; timeout?: number }): Promise<MemberSnapshot>;
313
+ waitForMemberJoined(options?: { name?: string; timeout?: number }): Promise<MemberJoined>;
314
+ waitForChat(options?: { text?: string; fromName?: string; timeout?: number }): Promise<Chat>;
315
+ waitForChannelOpened(options?: { channelId?: string; timeout?: number }): Promise<ChannelOpened>;
316
+ waitForChannelData(options?: { channelId?: string; timeout?: number }): Promise<ChannelData>;
317
+ stderrLines(): readonly string[];
318
+ wait(): Promise<number>;
319
+ close(): Promise<void>;
320
+ [Symbol.asyncIterator](): AsyncIterator<MachineEvent>;
321
+ }
322
+
323
+ export function routeAll(): RouteAll;
324
+ export function routeMember(memberId: string): RouteMember;
325
+ export function normalizeRoute(route: string | Route): Route;
326
+ export function parseRoute(value: Route): Route;
327
+ export function parseMachineCommand(value: string | MachineCommand): MachineCommand;
328
+ export function parseMachineEvent(value: string | MachineEvent): MachineEvent;
329
+ export function dumpMessage(message: MachineCommand | MachineEvent): Record<string, unknown>;
330
+ export function dumpMessageJson(message: MachineCommand | MachineEvent): string;
331
+ export function parseCliVersion(text: string): string;
332
+ export function isProtocolCompatible(version: ProtocolVersion, other: ProtocolVersion): boolean;
333
+ export function ensureMachineProtocolVersion(version: ProtocolVersion): void;
334
+ export function ensureReleaseVersion(binaryVersion: string): void;
335
+ export function probeBinaryVersion(binary?: string): Promise<string>;
336
+ export function ensureBinaryVersion(binary?: string): Promise<void>;
package/src/index.js ADDED
@@ -0,0 +1,33 @@
1
+ export { __version__ } from "./version.js";
2
+ export {
3
+ BlobFormat,
4
+ ChannelKind,
5
+ MACHINE_PROTOCOL_VERSION,
6
+ TransferItemKind,
7
+ TransferPhase,
8
+ dumpMessage,
9
+ dumpMessageJson,
10
+ ensureMachineProtocolVersion,
11
+ ensureReleaseVersion,
12
+ isProtocolCompatible,
13
+ normalizeRoute,
14
+ parseCliVersion,
15
+ parseMachineCommand,
16
+ parseMachineEvent,
17
+ parseRoute,
18
+ routeAll,
19
+ routeMember,
20
+ } from "./protocol.js";
21
+ export {
22
+ SkyfflaError,
23
+ SkyfflaMachineProtocolMismatch,
24
+ SkyfflaProcessExited,
25
+ SkyfflaProtocolError,
26
+ SkyfflaVersionMismatch,
27
+ } from "./errors.js";
28
+ export {
29
+ MachineChannel,
30
+ Room,
31
+ ensureBinaryVersion,
32
+ probeBinaryVersion,
33
+ } from "./room.js";