@moostjs/event-ws 0.6.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.
@@ -0,0 +1,225 @@
1
+ import * as moost from 'moost';
2
+ import { TMoostAdapter, Moost, TConsoleBase, TMoostAdapterOptions } from 'moost';
3
+ export { Controller, Description, Intercept, Param, TInterceptorPriority, defineAfterInterceptor, defineBeforeInterceptor, defineInterceptor } from 'moost';
4
+ import { WooksWs, TWooksWsOptions } from '@wooksjs/event-ws';
5
+ export { TTestWsConnectionContext, TTestWsMessageContext, TWooksWsOptions, WooksWs, WsBroadcastOptions, WsBroadcastTransport, WsClientMessage, WsConnection, WsError, WsPushMessage, WsReplyMessage, WsRoomManager, WsServerAdapter, WsSocket, currentConnection, prepareTestWsConnectionContext, prepareTestWsMessageContext, useWsConnection, useWsMessage, useWsRooms, useWsServer, wsConnectionKind, wsMessageKind } from '@wooksjs/event-ws';
6
+
7
+ /**
8
+ * ## Define WebSocket Message Handler
9
+ * ### @MethodDecorator
10
+ *
11
+ * Registers a handler for routed WebSocket messages.
12
+ * The event and path correspond to the WsClientMessage protocol fields.
13
+ *
14
+ * @param event - message event type (e.g. "message", "rpc", "subscribe")
15
+ * @param path - route path with optional params (e.g. "/chat/rooms/:roomId")
16
+ */
17
+ declare function Message(event: string, path?: string): MethodDecorator;
18
+ /**
19
+ * ## Define WebSocket Connection Handler
20
+ * ### @MethodDecorator
21
+ *
22
+ * Registers a handler that runs when a new WebSocket connection is established.
23
+ * Runs inside the connection context. Throwing or rejecting closes the connection.
24
+ */
25
+ declare function Connect(): MethodDecorator;
26
+ /**
27
+ * ## Define WebSocket Disconnection Handler
28
+ * ### @MethodDecorator
29
+ *
30
+ * Registers a handler that runs when a WebSocket connection closes.
31
+ */
32
+ declare function Disconnect(): MethodDecorator;
33
+
34
+ /**
35
+ * Get the parsed WebSocket message payload.
36
+ * Only available in message handlers (@Message).
37
+ * @decorator
38
+ * @paramType T
39
+ */
40
+ declare function MessageData(): ParameterDecorator & PropertyDecorator;
41
+ /**
42
+ * Get the raw WebSocket message (Buffer or string before parsing).
43
+ * Only available in message handlers (@Message).
44
+ * @decorator
45
+ * @paramType Buffer | string
46
+ */
47
+ declare function RawMessage(): ParameterDecorator & PropertyDecorator;
48
+ /**
49
+ * Get the WebSocket message correlation ID.
50
+ * Only available in message handlers (@Message).
51
+ * @decorator
52
+ * @paramType string | number | undefined
53
+ */
54
+ declare function MessageId(): ParameterDecorator & PropertyDecorator;
55
+ /**
56
+ * Get the WebSocket message event type.
57
+ * Only available in message handlers (@Message).
58
+ * @decorator
59
+ * @paramType string
60
+ */
61
+ declare function MessageType(): ParameterDecorator & PropertyDecorator;
62
+ /**
63
+ * Get the WebSocket message path.
64
+ * Only available in message handlers (@Message).
65
+ * @decorator
66
+ * @paramType string
67
+ */
68
+ declare function MessagePath(): ParameterDecorator & PropertyDecorator;
69
+ /**
70
+ * Get the WebSocket connection ID (UUID).
71
+ * Available in all WS handlers (@Message, @Connect, @Disconnect).
72
+ * @decorator
73
+ * @paramType string
74
+ */
75
+ declare function ConnectionId(): ParameterDecorator & PropertyDecorator;
76
+
77
+ /** Handler metadata for routed WebSocket message events. */
78
+ interface TWsMessageHandlerMeta {
79
+ event: string;
80
+ path: string;
81
+ }
82
+ /** Handler metadata for WebSocket connection events. */
83
+ interface TWsConnectHandlerMeta {
84
+ }
85
+ /** Union of all WebSocket handler metadata types. */
86
+ type TWsHandlerMeta = TWsMessageHandlerMeta | TWsConnectHandlerMeta;
87
+ /** Configuration options for the MoostWs adapter. */
88
+ interface TMoostWsOpts {
89
+ /**
90
+ * WooksWs options or instance
91
+ */
92
+ wooksWs?: WooksWs | TWooksWsOptions;
93
+ }
94
+ /**
95
+ * ## Moost WebSocket Adapter
96
+ *
97
+ * Moost Adapter for WebSocket events, wrapping @wooksjs/event-ws.
98
+ * Supports standalone and HTTP-integrated modes.
99
+ *
100
+ * ### HTTP-integrated mode (recommended)
101
+ * ```ts
102
+ * | import { MoostHttp, Upgrade } from '@moostjs/event-http'
103
+ * | import { MoostWs, Message, MessageData } from '@moostjs/event-ws'
104
+ * | import { Moost, Param, Controller, Injectable } from 'moost'
105
+ * |
106
+ * | @Controller()
107
+ * | class AppController {
108
+ * | constructor(private ws: WooksWs) {}
109
+ * |
110
+ * | @Upgrade('/ws')
111
+ * | handleUpgrade() { return this.ws.upgrade() }
112
+ * | }
113
+ * |
114
+ * | @Controller('chat')
115
+ * | class ChatController {
116
+ * | @Message('message', 'rooms/:roomId')
117
+ * | onMessage(@Param('roomId') roomId: string, @MessageData() data: unknown) {
118
+ * | return { received: true, roomId }
119
+ * | }
120
+ * | }
121
+ * |
122
+ * | const app = new Moost()
123
+ * | const http = new MoostHttp()
124
+ * | const ws = new MoostWs({ httpApp: http.getHttpApp() })
125
+ * | app.adapter(http)
126
+ * | app.adapter(ws)
127
+ * | app.registerControllers(AppController, ChatController)
128
+ * | http.listen(3000)
129
+ * | app.init()
130
+ * ```
131
+ */
132
+ declare class MoostWs implements TMoostAdapter<TWsHandlerMeta> {
133
+ protected opts?: (TMoostWsOpts & {
134
+ /**
135
+ * WooksHttp instance or an adapter with getHttpApp() for HTTP-integrated mode.
136
+ * When provided, the WS adapter shares the HTTP server.
137
+ * Use @Upgrade() decorator on a handler method to register the upgrade route.
138
+ */
139
+ httpApp?: {
140
+ getHttpApp(): unknown;
141
+ } | object;
142
+ }) | undefined;
143
+ readonly name = "ws";
144
+ protected wsApp: WooksWs;
145
+ constructor(opts?: (TMoostWsOpts & {
146
+ /**
147
+ * WooksHttp instance or an adapter with getHttpApp() for HTTP-integrated mode.
148
+ * When provided, the WS adapter shares the HTTP server.
149
+ * Use @Upgrade() decorator on a handler method to register the upgrade route.
150
+ */
151
+ httpApp?: {
152
+ getHttpApp(): unknown;
153
+ } | object;
154
+ }) | undefined);
155
+ getWsApp(): WooksWs;
156
+ /**
157
+ * Start a standalone WebSocket server (without HTTP integration).
158
+ */
159
+ listen(port: number, hostname?: string): Promise<void>;
160
+ /**
161
+ * Stop the server, close all connections, clean up heartbeat.
162
+ */
163
+ close(): void;
164
+ protected moost?: Moost;
165
+ onInit(moost: Moost): void;
166
+ getProvideRegistry(): moost.TProvideRegistry;
167
+ getLogger(): TConsoleBase;
168
+ bindHandler<T extends object = object>(opts: TMoostAdapterOptions<TWsHandlerMeta, T>): void;
169
+ protected bindMessageHandler<T extends object>(opts: TMoostAdapterOptions<TWsHandlerMeta, T>, handler: TWsMessageHandlerMeta & {
170
+ type: string;
171
+ }): void;
172
+ protected bindConnectHandler<T extends object>(opts: TMoostAdapterOptions<TWsHandlerMeta, T>): void;
173
+ protected bindDisconnectHandler<T extends object>(opts: TMoostAdapterOptions<TWsHandlerMeta, T>): void;
174
+ }
175
+
176
+ interface TWsAppOptions {
177
+ /**
178
+ * WooksWs configuration options
179
+ */
180
+ ws?: TWooksWsOptions;
181
+ }
182
+ /**
183
+ * Quick WS App factory class.
184
+ *
185
+ * Use this class to quickly build a standalone WebSocket application
186
+ * with controllers. It extends the Moost class and wraps MoostWs initialization.
187
+ *
188
+ * @example
189
+ * ```typescript
190
+ * import { WsApp } from '@moostjs/event-ws'
191
+ * import { ChatController } from './chat.controller.ts'
192
+ *
193
+ * new WsApp()
194
+ * .controllers(ChatController)
195
+ * .start(3000)
196
+ * ```
197
+ */
198
+ declare class WsApp extends Moost {
199
+ protected _wsOpts?: TWsAppOptions;
200
+ protected _wsAdapter?: MoostWs;
201
+ /**
202
+ * Registers one or more WS controllers.
203
+ *
204
+ * (Shortcut for `registerControllers` method.)
205
+ */
206
+ controllers(...controllers: (object | Function | [string, object | Function])[]): this;
207
+ /**
208
+ * Configures the WS options.
209
+ */
210
+ useWsOptions(wsOpts: TWsAppOptions): this;
211
+ /**
212
+ * Returns the underlying MoostWs adapter instance.
213
+ */
214
+ getWsAdapter(): MoostWs | undefined;
215
+ /**
216
+ * Starts the standalone WebSocket application.
217
+ *
218
+ * @param port - Port to listen on
219
+ * @param hostname - Optional hostname
220
+ */
221
+ start(port: number, hostname?: string): Promise<void>;
222
+ }
223
+
224
+ export { Connect, ConnectionId, Disconnect, Message, MessageData, MessageId, MessagePath, MessageType, MoostWs, RawMessage, WsApp };
225
+ export type { TMoostWsOpts, TWsConnectHandlerMeta, TWsHandlerMeta, TWsMessageHandlerMeta };
package/dist/index.mjs ADDED
@@ -0,0 +1,298 @@
1
+ import { Controller, Description, Intercept, Moost, Param, Resolve, defineAfterInterceptor, defineBeforeInterceptor, defineInterceptor, defineMoostEventHandler, getMoostMate } from "moost";
2
+ import { WooksWs, WooksWs as WooksWs$1, WsError, WsRoomManager, createWsApp, currentConnection, prepareTestWsConnectionContext, prepareTestWsMessageContext, useWsConnection, useWsConnection as useWsConnection$1, useWsMessage, useWsMessage as useWsMessage$1, useWsRooms, useWsServer, wsConnectionKind, wsMessageKind } from "@wooksjs/event-ws";
3
+ import { createProvideRegistry } from "@prostojs/infact";
4
+
5
+ //#region packages/event-ws/src/decorators/ws-method.decorator.ts
6
+ /**
7
+ * ## Define WebSocket Message Handler
8
+ * ### @MethodDecorator
9
+ *
10
+ * Registers a handler for routed WebSocket messages.
11
+ * The event and path correspond to the WsClientMessage protocol fields.
12
+ *
13
+ * @param event - message event type (e.g. "message", "rpc", "subscribe")
14
+ * @param path - route path with optional params (e.g. "/chat/rooms/:roomId")
15
+ */ function Message(event, path) {
16
+ return getMoostMate().decorate("handlers", {
17
+ event,
18
+ path,
19
+ type: "WS_MESSAGE"
20
+ }, true);
21
+ }
22
+ /**
23
+ * ## Define WebSocket Connection Handler
24
+ * ### @MethodDecorator
25
+ *
26
+ * Registers a handler that runs when a new WebSocket connection is established.
27
+ * Runs inside the connection context. Throwing or rejecting closes the connection.
28
+ */ function Connect() {
29
+ return getMoostMate().decorate("handlers", { type: "WS_CONNECT" }, true);
30
+ }
31
+ /**
32
+ * ## Define WebSocket Disconnection Handler
33
+ * ### @MethodDecorator
34
+ *
35
+ * Registers a handler that runs when a WebSocket connection closes.
36
+ */ function Disconnect() {
37
+ return getMoostMate().decorate("handlers", { type: "WS_DISCONNECT" }, true);
38
+ }
39
+
40
+ //#endregion
41
+ //#region packages/event-ws/src/decorators/resolve.decorator.ts
42
+ /**
43
+ * Get the parsed WebSocket message payload.
44
+ * Only available in message handlers (@Message).
45
+ * @decorator
46
+ * @paramType T
47
+ */ function MessageData() {
48
+ return Resolve(() => useWsMessage$1().data, "ws_data");
49
+ }
50
+ /**
51
+ * Get the raw WebSocket message (Buffer or string before parsing).
52
+ * Only available in message handlers (@Message).
53
+ * @decorator
54
+ * @paramType Buffer | string
55
+ */ function RawMessage() {
56
+ return Resolve(() => useWsMessage$1().raw, "ws_raw");
57
+ }
58
+ /**
59
+ * Get the WebSocket message correlation ID.
60
+ * Only available in message handlers (@Message).
61
+ * @decorator
62
+ * @paramType string | number | undefined
63
+ */ function MessageId() {
64
+ return Resolve(() => useWsMessage$1().id, "ws_message_id");
65
+ }
66
+ /**
67
+ * Get the WebSocket message event type.
68
+ * Only available in message handlers (@Message).
69
+ * @decorator
70
+ * @paramType string
71
+ */ function MessageType() {
72
+ return Resolve(() => useWsMessage$1().event, "ws_event");
73
+ }
74
+ /**
75
+ * Get the WebSocket message path.
76
+ * Only available in message handlers (@Message).
77
+ * @decorator
78
+ * @paramType string
79
+ */ function MessagePath() {
80
+ return Resolve(() => useWsMessage$1().path, "ws_path");
81
+ }
82
+ /**
83
+ * Get the WebSocket connection ID (UUID).
84
+ * Available in all WS handlers (@Message, @Connect, @Disconnect).
85
+ * @decorator
86
+ * @paramType string
87
+ */ function ConnectionId() {
88
+ return Resolve(() => useWsConnection$1().id, "ws_connection_id");
89
+ }
90
+
91
+ //#endregion
92
+ //#region packages/event-ws/src/event-ws.ts
93
+ function _define_property$1(obj, key, value) {
94
+ if (key in obj) Object.defineProperty(obj, key, {
95
+ value,
96
+ enumerable: true,
97
+ configurable: true,
98
+ writable: true
99
+ });
100
+ else obj[key] = value;
101
+ return obj;
102
+ }
103
+ const LOGGER_TITLE = "moost-ws";
104
+ /**
105
+ * ## Moost WebSocket Adapter
106
+ *
107
+ * Moost Adapter for WebSocket events, wrapping @wooksjs/event-ws.
108
+ * Supports standalone and HTTP-integrated modes.
109
+ *
110
+ * ### HTTP-integrated mode (recommended)
111
+ * ```ts
112
+ * | import { MoostHttp, Upgrade } from '@moostjs/event-http'
113
+ * | import { MoostWs, Message, MessageData } from '@moostjs/event-ws'
114
+ * | import { Moost, Param, Controller, Injectable } from 'moost'
115
+ * |
116
+ * | @Controller()
117
+ * | class AppController {
118
+ * | constructor(private ws: WooksWs) {}
119
+ * |
120
+ * | @Upgrade('/ws')
121
+ * | handleUpgrade() { return this.ws.upgrade() }
122
+ * | }
123
+ * |
124
+ * | @Controller('chat')
125
+ * | class ChatController {
126
+ * | @Message('message', 'rooms/:roomId')
127
+ * | onMessage(@Param('roomId') roomId: string, @MessageData() data: unknown) {
128
+ * | return { received: true, roomId }
129
+ * | }
130
+ * | }
131
+ * |
132
+ * | const app = new Moost()
133
+ * | const http = new MoostHttp()
134
+ * | const ws = new MoostWs({ httpApp: http.getHttpApp() })
135
+ * | app.adapter(http)
136
+ * | app.adapter(ws)
137
+ * | app.registerControllers(AppController, ChatController)
138
+ * | http.listen(3000)
139
+ * | app.init()
140
+ * ```
141
+ */ var MoostWs = class MoostWs {
142
+ getWsApp() {
143
+ return this.wsApp;
144
+ }
145
+ /**
146
+ * Start a standalone WebSocket server (without HTTP integration).
147
+ */ listen(port, hostname) {
148
+ return this.wsApp.listen(port, hostname);
149
+ }
150
+ /**
151
+ * Stop the server, close all connections, clean up heartbeat.
152
+ */ close() {
153
+ return this.wsApp.close();
154
+ }
155
+ onInit(moost) {
156
+ this.moost = moost;
157
+ }
158
+ getProvideRegistry() {
159
+ return createProvideRegistry([MoostWs, () => this], ["MoostWs", () => this], [WooksWs$1, () => this.getWsApp()], ["WooksWs", () => this.getWsApp()]);
160
+ }
161
+ getLogger() {
162
+ return this.wsApp.getLogger("[moost-ws]");
163
+ }
164
+ bindHandler(opts) {
165
+ for (const handler of opts.handlers) if (handler.type === "WS_MESSAGE") this.bindMessageHandler(opts, handler);
166
+ else if (handler.type === "WS_CONNECT") this.bindConnectHandler(opts);
167
+ else if (handler.type === "WS_DISCONNECT") this.bindDisconnectHandler(opts);
168
+ }
169
+ bindMessageHandler(opts, handler) {
170
+ const event = handler.event;
171
+ const path = typeof handler.path === "string" ? handler.path : typeof opts.method === "string" ? opts.method : "";
172
+ const targetPath = `${`${opts.prefix || ""}/${path}`.replaceAll(/\/\/+/g, "/")}`;
173
+ const fn = defineMoostEventHandler({
174
+ contextType: "WS_MESSAGE",
175
+ loggerTitle: LOGGER_TITLE,
176
+ getIterceptorHandler: opts.getIterceptorHandler,
177
+ getControllerInstance: opts.getInstance,
178
+ controllerMethod: opts.method,
179
+ controllerName: opts.controllerName,
180
+ resolveArgs: opts.resolveArgs,
181
+ targetPath,
182
+ handlerType: handler.type
183
+ });
184
+ this.wsApp.onMessage(event, targetPath, fn);
185
+ opts.logHandler(`(ws:${event})${targetPath}`);
186
+ opts.register(handler, targetPath, []);
187
+ }
188
+ bindConnectHandler(opts) {
189
+ const fn = defineMoostEventHandler({
190
+ contextType: "WS_CONNECT",
191
+ loggerTitle: LOGGER_TITLE,
192
+ getIterceptorHandler: opts.getIterceptorHandler,
193
+ getControllerInstance: opts.getInstance,
194
+ controllerMethod: opts.method,
195
+ resolveArgs: opts.resolveArgs,
196
+ targetPath: "__ws_connect__",
197
+ handlerType: "WS_CONNECT"
198
+ });
199
+ this.wsApp.onConnect(fn);
200
+ opts.logHandler(`(ws:connect)`);
201
+ opts.register({ type: "WS_CONNECT" }, "__ws_connect__", []);
202
+ }
203
+ bindDisconnectHandler(opts) {
204
+ const fn = defineMoostEventHandler({
205
+ contextType: "WS_DISCONNECT",
206
+ loggerTitle: LOGGER_TITLE,
207
+ getIterceptorHandler: opts.getIterceptorHandler,
208
+ getControllerInstance: opts.getInstance,
209
+ controllerMethod: opts.method,
210
+ resolveArgs: opts.resolveArgs,
211
+ targetPath: "__ws_disconnect__",
212
+ handlerType: "WS_DISCONNECT"
213
+ });
214
+ this.wsApp.onDisconnect(fn);
215
+ opts.logHandler(`(ws:disconnect)`);
216
+ opts.register({ type: "WS_DISCONNECT" }, "__ws_disconnect__", []);
217
+ }
218
+ constructor(opts) {
219
+ _define_property$1(this, "opts", void 0);
220
+ _define_property$1(this, "name", void 0);
221
+ _define_property$1(this, "wsApp", void 0);
222
+ _define_property$1(this, "moost", void 0);
223
+ this.opts = opts;
224
+ this.name = "ws";
225
+ const wsOpts = opts?.wooksWs;
226
+ if (wsOpts && wsOpts instanceof WooksWs$1) this.wsApp = wsOpts;
227
+ else {
228
+ const httpApp = opts?.httpApp && "getHttpApp" in opts.httpApp ? opts.httpApp.getHttpApp() : opts?.httpApp;
229
+ if (httpApp) this.wsApp = createWsApp(httpApp, wsOpts || {});
230
+ else this.wsApp = createWsApp(wsOpts || {});
231
+ }
232
+ }
233
+ };
234
+
235
+ //#endregion
236
+ //#region packages/event-ws/src/quick-ws.ts
237
+ function _define_property(obj, key, value) {
238
+ if (key in obj) Object.defineProperty(obj, key, {
239
+ value,
240
+ enumerable: true,
241
+ configurable: true,
242
+ writable: true
243
+ });
244
+ else obj[key] = value;
245
+ return obj;
246
+ }
247
+ /**
248
+ * Quick WS App factory class.
249
+ *
250
+ * Use this class to quickly build a standalone WebSocket application
251
+ * with controllers. It extends the Moost class and wraps MoostWs initialization.
252
+ *
253
+ * @example
254
+ * ```typescript
255
+ * import { WsApp } from '@moostjs/event-ws'
256
+ * import { ChatController } from './chat.controller.ts'
257
+ *
258
+ * new WsApp()
259
+ * .controllers(ChatController)
260
+ * .start(3000)
261
+ * ```
262
+ */ var WsApp = class extends Moost {
263
+ /**
264
+ * Registers one or more WS controllers.
265
+ *
266
+ * (Shortcut for `registerControllers` method.)
267
+ */ controllers(...controllers) {
268
+ return this.registerControllers(...controllers);
269
+ }
270
+ /**
271
+ * Configures the WS options.
272
+ */ useWsOptions(wsOpts) {
273
+ this._wsOpts = wsOpts;
274
+ return this;
275
+ }
276
+ /**
277
+ * Returns the underlying MoostWs adapter instance.
278
+ */ getWsAdapter() {
279
+ return this._wsAdapter;
280
+ }
281
+ /**
282
+ * Starts the standalone WebSocket application.
283
+ *
284
+ * @param port - Port to listen on
285
+ * @param hostname - Optional hostname
286
+ */ async start(port, hostname) {
287
+ this._wsAdapter = new MoostWs({ wooksWs: this._wsOpts?.ws });
288
+ this.adapter(this._wsAdapter);
289
+ await this.init();
290
+ return this._wsAdapter.listen(port, hostname);
291
+ }
292
+ constructor(...args) {
293
+ super(...args), _define_property(this, "_wsOpts", void 0), _define_property(this, "_wsAdapter", void 0);
294
+ }
295
+ };
296
+
297
+ //#endregion
298
+ export { Connect, ConnectionId, Controller, Description, Disconnect, Intercept, Message, MessageData, MessageId, MessagePath, MessageType, MoostWs, Param, RawMessage, WooksWs, WsApp, WsError, WsRoomManager, currentConnection, defineAfterInterceptor, defineBeforeInterceptor, defineInterceptor, prepareTestWsConnectionContext, prepareTestWsMessageContext, useWsConnection, useWsMessage, useWsRooms, useWsServer, wsConnectionKind, wsMessageKind };
package/package.json ADDED
@@ -0,0 +1,62 @@
1
+ {
2
+ "name": "@moostjs/event-ws",
3
+ "version": "0.6.0",
4
+ "description": "@moostjs/event-ws",
5
+ "keywords": [
6
+ "composables",
7
+ "framework",
8
+ "moost",
9
+ "moostjs",
10
+ "prostojs",
11
+ "websocket",
12
+ "wooksjs"
13
+ ],
14
+ "homepage": "https://github.com/moostjs/moostjs/tree/main/packages/event-ws#readme",
15
+ "bugs": {
16
+ "url": "https://github.com/moostjs/moostjs/issues"
17
+ },
18
+ "license": "MIT",
19
+ "author": "Artem Maltsev",
20
+ "repository": {
21
+ "type": "git",
22
+ "url": "git+https://github.com/moostjs/moostjs.git",
23
+ "directory": "packages/event-ws"
24
+ },
25
+ "bin": {
26
+ "moostjs-event-ws-skill": "./scripts/setup-skills.js"
27
+ },
28
+ "files": [
29
+ "dist",
30
+ "skills",
31
+ "scripts/setup-skills.js"
32
+ ],
33
+ "type": "module",
34
+ "sideEffects": false,
35
+ "main": "dist/index.cjs",
36
+ "module": "dist/index.mjs",
37
+ "types": "dist/index.d.ts",
38
+ "exports": {
39
+ "./package.json": "./package.json",
40
+ ".": {
41
+ "types": "./dist/index.d.ts",
42
+ "import": "./dist/index.mjs",
43
+ "require": "./dist/index.cjs"
44
+ }
45
+ },
46
+ "dependencies": {
47
+ "@wooksjs/event-ws": "^0.7.3"
48
+ },
49
+ "devDependencies": {
50
+ "vitest": "3.2.4"
51
+ },
52
+ "peerDependencies": {
53
+ "@prostojs/infact": "^0.4.1",
54
+ "@wooksjs/event-core": "^0.7.3",
55
+ "moost": "^0.6.0"
56
+ },
57
+ "scripts": {
58
+ "pub": "pnpm publish --access public",
59
+ "test": "vitest",
60
+ "setup-skills": "node ./scripts/setup-skills.js"
61
+ }
62
+ }
@@ -0,0 +1,78 @@
1
+ #!/usr/bin/env node
2
+ /* prettier-ignore */
3
+ import fs from 'fs'
4
+ import path from 'path'
5
+ import os from 'os'
6
+ import { fileURLToPath } from 'url'
7
+
8
+ const __dirname = path.dirname(fileURLToPath(import.meta.url))
9
+
10
+ const SKILL_NAME = 'moostjs-event-ws'
11
+ const SKILL_SRC = path.join(__dirname, '..', 'skills', SKILL_NAME)
12
+
13
+ if (!fs.existsSync(SKILL_SRC)) {
14
+ console.error(`No skills found at ${SKILL_SRC}`)
15
+ console.error('Add your SKILL.md files to the skills/' + SKILL_NAME + '/ directory first.')
16
+ process.exit(1)
17
+ }
18
+
19
+ const AGENTS = {
20
+ 'Claude Code': { dir: '.claude/skills', global: path.join(os.homedir(), '.claude', 'skills') },
21
+ 'Cursor': { dir: '.cursor/skills', global: path.join(os.homedir(), '.cursor', 'skills') },
22
+ 'Windsurf': { dir: '.windsurf/skills', global: path.join(os.homedir(), '.windsurf', 'skills') },
23
+ 'Codex': { dir: '.codex/skills', global: path.join(os.homedir(), '.codex', 'skills') },
24
+ 'OpenCode': { dir: '.opencode/skills', global: path.join(os.homedir(), '.opencode', 'skills') },
25
+ }
26
+
27
+ const args = process.argv.slice(2)
28
+ const isGlobal = args.includes('--global') || args.includes('-g')
29
+ const isPostinstall = args.includes('--postinstall')
30
+ let installed = 0, skipped = 0
31
+ const installedDirs = []
32
+
33
+ for (const [agentName, cfg] of Object.entries(AGENTS)) {
34
+ const targetBase = isGlobal ? cfg.global : path.join(process.cwd(), cfg.dir)
35
+ const agentRootDir = path.dirname(cfg.global) // Check if the agent has ever been installed globally
36
+
37
+ // In postinstall mode: silently skip agents that aren't set up globally
38
+ if (isPostinstall || isGlobal) {
39
+ if (!fs.existsSync(agentRootDir)) { skipped++; continue }
40
+ }
41
+
42
+ const dest = path.join(targetBase, SKILL_NAME)
43
+ try {
44
+ fs.mkdirSync(dest, { recursive: true })
45
+ fs.cpSync(SKILL_SRC, dest, { recursive: true })
46
+ console.log(`āœ… ${agentName}: installed to ${dest}`)
47
+ installed++
48
+ if (!isGlobal) installedDirs.push(cfg.dir + '/' + SKILL_NAME)
49
+ } catch (err) {
50
+ console.warn(`āš ļø ${agentName}: failed — ${err.message}`)
51
+ }
52
+ }
53
+
54
+ // Add locally-installed skill dirs to .gitignore
55
+ if (!isGlobal && installedDirs.length > 0) {
56
+ const gitignorePath = path.join(process.cwd(), '.gitignore')
57
+ let gitignoreContent = ''
58
+ try { gitignoreContent = fs.readFileSync(gitignorePath, 'utf8') } catch {}
59
+ const linesToAdd = installedDirs.filter(d => !gitignoreContent.includes(d))
60
+ if (linesToAdd.length > 0) {
61
+ const hasHeader = gitignoreContent.includes('# AI agent skills')
62
+ const block = (gitignoreContent && !gitignoreContent.endsWith('\n') ? '\n' : '')
63
+ + (hasHeader ? '' : '\n# AI agent skills (auto-generated by setup-skills)\n')
64
+ + linesToAdd.join('\n') + '\n'
65
+ fs.appendFileSync(gitignorePath, block)
66
+ console.log(`šŸ“ Added ${linesToAdd.length} entries to .gitignore`)
67
+ }
68
+ }
69
+
70
+ if (installed === 0 && isPostinstall) {
71
+ // Silence is fine — no agents present, nothing to do
72
+ } else if (installed === 0 && skipped === Object.keys(AGENTS).length) {
73
+ console.log('No agent directories detected. Try --global or run without it for project-local install.')
74
+ } else if (installed === 0) {
75
+ console.log('Nothing installed. Run without --global to install project-locally.')
76
+ } else {
77
+ console.log(`\n✨ Done! Restart your AI agent to pick up the "${SKILL_NAME}" skill.`)
78
+ }