vibe-remote-protocol 1.0.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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 jun-young1993
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,87 @@
1
+ # vibe-remote-protocol
2
+
3
+ VibeRemote binary WebSocket protocol v3 — opcode constants, encoder/decoder helpers, and TypeScript types for `APP_JSON` messages.
4
+
5
+ The single source of truth that lets `vibe-remote-relay`, the mobile client (Flutter / Dart), and the desktop agent (Electron / TypeScript) communicate over the same wire format.
6
+
7
+ [한국어 README](./README_KR.md)
8
+
9
+ ---
10
+
11
+ ## Install
12
+
13
+ ```bash
14
+ npm install vibe-remote-protocol
15
+ ```
16
+
17
+ ## Quick start
18
+
19
+ ```ts
20
+ import {
21
+ OPCODE,
22
+ encodeAppJson,
23
+ encodeFrameSnapshotPush,
24
+ readOpcode,
25
+ isAppJsonMessage,
26
+ type AppJsonMessage,
27
+ } from 'vibe-remote-protocol';
28
+
29
+ // 1) Send a hello message
30
+ const hello = encodeAppJson({ type: 'hello', protocolVersion: 3 });
31
+ ws.send(hello);
32
+
33
+ // 2) Inspect the opcode of an incoming frame
34
+ const op = readOpcode(buffer);
35
+ if (op === OPCODE.APP_JSON) {
36
+ const json: AppJsonMessage = JSON.parse(buffer.toString('utf-8', 1));
37
+ if (isAppJsonMessage(json, 'machine_list')) {
38
+ console.log(json.machines);
39
+ }
40
+ }
41
+ ```
42
+
43
+ ## Opcodes
44
+
45
+ | Opcode | Name | Direction | Payload |
46
+ |---|---|---|---|
47
+ | `0x00` | `PTY_DATA` | desktop → mobile | raw PTY UTF-8 ANSI stream |
48
+ | `0x01` | `APP_JSON` | bidirectional | UTF-8 JSON (`AppJsonMessage`) |
49
+ | `0x02` | `RESIZE` | mobile → desktop | `[cols u32 BE][rows u32 BE]`, fire-and-forget |
50
+ | `0x03` | `FRAME_SNAPSHOT` | relay → mobile | `[id len u32][id][ansi len u32][ansi]` |
51
+ | `0x04` | `GRID_SNAPSHOT` | desktop → mobile | A3 server-truth full snapshot |
52
+ | `0x05` | `GRID_DIFF` | desktop → mobile | A3 incremental cell ops |
53
+ | `0xFF` | `PROTOCOL_ERROR` | bidirectional | `[reason u8]` |
54
+
55
+ `0x06`–`0xFE` are reserved.
56
+
57
+ ## Message types
58
+
59
+ The `AppJsonMessage` discriminated union includes:
60
+
61
+ - `hello`, `hello_ack` — version negotiation
62
+ - `cap.req`, `cap.ack` — A3 capability negotiation
63
+ - `snapshot.req` — request a fresh full grid snapshot
64
+ - `machine_list`, `session_attach`, `connection_status`
65
+ - `terminal_data`, `claude_state`, `pty_status`, `git_result`
66
+ - `session_replay`, `grid_resize_capped`, `protocol_mismatch`
67
+ - `ping`, `pong`, `error`, `input`
68
+
69
+ See `dist/messages.d.ts` (or [`src/messages.ts`](./src/messages.ts)) for full field definitions.
70
+
71
+ ## Fixtures
72
+
73
+ `fixtures/binary-protocol-v3.fixtures.json` is the cross-validation fixture set used by all three repos (mobile / relay / desktop). Each entry contains the wire hex plus the expected parse result for one opcode. The mobile (Dart) and desktop implementations round-trip the same file.
74
+
75
+ ## Versioning policy
76
+
77
+ - **Major** — opcode added/removed or wire format change
78
+ - **Minor** — new `AppJsonMessage` type (backward compatible)
79
+ - **Patch** — docs / typing fixes only
80
+
81
+ Three-repo simultaneous-PR policy — see `binary-protocol-v3.md` §10.
82
+
83
+ For maintainers cutting a new npm release, see [PUBLISHING.md](./PUBLISHING.md).
84
+
85
+ ## License
86
+
87
+ MIT — see [`LICENSE`](./LICENSE).
package/README_KR.md ADDED
@@ -0,0 +1,87 @@
1
+ # vibe-remote-protocol
2
+
3
+ VibeRemote binary WebSocket protocol v3 — opcode 상수, encoder/decoder helper, 그리고 `APP_JSON` 메시지의 TypeScript 타입.
4
+
5
+ `vibe-remote-relay`, 모바일 클라이언트(Flutter / Dart), 데스크탑 에이전트(Electron / TypeScript) 가 동일한 wire format 으로 통신하기 위한 단일 source of truth.
6
+
7
+ [English README](./README.md)
8
+
9
+ ---
10
+
11
+ ## 설치
12
+
13
+ ```bash
14
+ npm install vibe-remote-protocol
15
+ ```
16
+
17
+ ## Quick start
18
+
19
+ ```ts
20
+ import {
21
+ OPCODE,
22
+ encodeAppJson,
23
+ encodeFrameSnapshotPush,
24
+ readOpcode,
25
+ isAppJsonMessage,
26
+ type AppJsonMessage,
27
+ } from 'vibe-remote-protocol';
28
+
29
+ // 1) hello 메시지 송신
30
+ const hello = encodeAppJson({ type: 'hello', protocolVersion: 3 });
31
+ ws.send(hello);
32
+
33
+ // 2) 수신 frame 의 opcode 확인
34
+ const op = readOpcode(buffer);
35
+ if (op === OPCODE.APP_JSON) {
36
+ const json: AppJsonMessage = JSON.parse(buffer.toString('utf-8', 1));
37
+ if (isAppJsonMessage(json, 'machine_list')) {
38
+ console.log(json.machines);
39
+ }
40
+ }
41
+ ```
42
+
43
+ ## Opcode
44
+
45
+ | Opcode | Name | 방향 | Payload |
46
+ |---|---|---|---|
47
+ | `0x00` | `PTY_DATA` | desktop → mobile | raw PTY UTF-8 ANSI stream |
48
+ | `0x01` | `APP_JSON` | 양방향 | UTF-8 JSON (`AppJsonMessage`) |
49
+ | `0x02` | `RESIZE` | mobile → desktop | `[cols u32 BE][rows u32 BE]`, fire-and-forget |
50
+ | `0x03` | `FRAME_SNAPSHOT` | relay → mobile | `[id len u32][id][ansi len u32][ansi]` |
51
+ | `0x04` | `GRID_SNAPSHOT` | desktop → mobile | A3 server-truth full snapshot |
52
+ | `0x05` | `GRID_DIFF` | desktop → mobile | A3 incremental cell ops |
53
+ | `0xFF` | `PROTOCOL_ERROR` | 양방향 | `[reason u8]` |
54
+
55
+ `0x06` ~ `0xFE` reserved.
56
+
57
+ ## 메시지 타입
58
+
59
+ `AppJsonMessage` discriminated union 에는 다음이 포함됩니다:
60
+
61
+ - `hello`, `hello_ack` — version negotiation
62
+ - `cap.req`, `cap.ack` — A3 capability negotiation
63
+ - `snapshot.req` — full grid snapshot 재요청
64
+ - `machine_list`, `session_attach`, `connection_status`
65
+ - `terminal_data`, `claude_state`, `pty_status`, `git_result`
66
+ - `session_replay`, `grid_resize_capped`, `protocol_mismatch`
67
+ - `ping`, `pong`, `error`, `input`
68
+
69
+ 자세한 필드는 `dist/messages.d.ts` 또는 [`src/messages.ts`](./src/messages.ts) 참고.
70
+
71
+ ## Fixture
72
+
73
+ `fixtures/binary-protocol-v3.fixtures.json` — 3 repo (mobile / relay / desktop) cross-validation 용. 각 opcode 별 wire hex + expected parse 결과. mobile (Dart) 과 desktop 의 구현도 동일 fixture 로 round-trip 검증.
74
+
75
+ ## 버전 정책
76
+
77
+ - **Major** — opcode 추가/제거 또는 wire format 변경
78
+ - **Minor** — `AppJsonMessage` 타입 추가 (호환)
79
+ - **Patch** — 문서/타입 보정
80
+
81
+ 3 repo 동시 PR 정책 — `binary-protocol-v3.md` §10 참고.
82
+
83
+ 새 npm 릴리즈를 cut 하는 maintainer 는 [PUBLISHING_KR.md](./PUBLISHING_KR.md) 참고.
84
+
85
+ ## License
86
+
87
+ MIT — [`LICENSE`](./LICENSE) 파일 참조.
@@ -0,0 +1,78 @@
1
+ /**
2
+ * Binary protocol v3 — opcode 상수 + helpers.
3
+ *
4
+ * 단일 source of truth: spec 문서 `binary-protocol-v3.md` (모바일 리포에 보관).
5
+ * opcode 상수 / encoding 규칙이 spec 문서와 *반드시* 일치해야 한다 — fixture-driven
6
+ * contract test (`fixtures/binary-protocol-v3.fixtures.json`) 가 wire 호환을 영구 검증.
7
+ *
8
+ * 본 파일은:
9
+ * - opcode 식별 helpers (host PTY_DATA / mobile session_attach 분기)
10
+ * - FRAME_SNAPSHOT push encoding (Approach B 의 last-frame replay)
11
+ * - PROTOCOL_ERROR 송신 helpers
12
+ * - APP_JSON encoder
13
+ */
14
+ export declare const OPCODE: {
15
+ readonly PTY_DATA: 0;
16
+ readonly APP_JSON: 1;
17
+ readonly RESIZE: 2;
18
+ readonly FRAME_SNAPSHOT: 3;
19
+ readonly GRID_SNAPSHOT: 4;
20
+ readonly GRID_DIFF: 5;
21
+ readonly PROTOCOL_ERROR: 255;
22
+ };
23
+ export declare const PROTOCOL_ERROR_REASON: {
24
+ readonly UNKNOWN_OPCODE: 1;
25
+ readonly MALFORMED_PAYLOAD: 2;
26
+ readonly VERSION_MISMATCH: 3;
27
+ readonly UNAUTHORIZED: 4;
28
+ };
29
+ /**
30
+ * 본 프로토콜 패키지가 구현하는 binary protocol version.
31
+ * 양 client (mobile / desktop) 의 `kRelayProtocolVersion` / `PROTOCOL_VERSION`
32
+ * 과 *동시* 변경 필수.
33
+ */
34
+ export declare const PROTOCOL_VERSION_V3 = 3;
35
+ /**
36
+ * frame 의 opcode 만 빠르게 식별. binary frame 검증 + 라우팅 분기에 사용.
37
+ * empty buffer 면 -1 리턴 (호출자가 close 결정).
38
+ */
39
+ export declare function readOpcode(buffer: Buffer): number;
40
+ /**
41
+ * APP_JSON frame 의 payload 가 `"session_attach"` 를 포함하는지 빠르게 검사.
42
+ *
43
+ * substring fast path — JSON parse 회피로 high-frequency 메시지 (예: terminal_input)
44
+ * 의 처리 비용을 상수로. true 일 때만 호출자가 [parseAppJsonSessionAttach] 로 정확히
45
+ * 검증.
46
+ *
47
+ * False positive 가능 (예: terminal_input 의 data field 가 "session_attach" string
48
+ * 포함) — 호출자가 JSON parse 후 type 필드로 최종 결정.
49
+ */
50
+ export declare function isAppJsonSessionAttachLike(buffer: Buffer): boolean;
51
+ /**
52
+ * APP_JSON session_attach payload 를 JSON parse. malformed / 잘못된 type 이면 null.
53
+ */
54
+ export declare function parseAppJsonSessionAttach(buffer: Buffer): {
55
+ machineId: string;
56
+ } | null;
57
+ /**
58
+ * FRAME_SNAPSHOT (0x03) frame encode.
59
+ *
60
+ * Wire format:
61
+ * `[0x03] [machineId len: BE u32] [machineId UTF-8] [ansi len: BE u32] [ansi UTF-8]`
62
+ */
63
+ export declare function encodeFrameSnapshotPush(machineId: string, ansi: string): Buffer;
64
+ /**
65
+ * PROTOCOL_ERROR (0xFF) frame encode.
66
+ */
67
+ export declare function encodeProtocolError(reason: number): Buffer;
68
+ /**
69
+ * APP_JSON (0x01) frame encode — server-initiated message 송신용.
70
+ */
71
+ export declare function encodeAppJson(data: object): Buffer;
72
+ /**
73
+ * RESIZE (0x02) frame encode — mobile → host. fire-and-forget, ack 없음.
74
+ *
75
+ * Wire format: `[0x02] [cols: BE u32] [rows: BE u32]`
76
+ */
77
+ export declare function encodeResize(cols: number, rows: number): Buffer;
78
+ //# sourceMappingURL=binary-protocol.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"binary-protocol.d.ts","sourceRoot":"","sources":["../src/binary-protocol.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,eAAO,MAAM,MAAM;;;;;;;;CAQT,CAAC;AAEX,eAAO,MAAM,qBAAqB;;;;;CAKxB,CAAC;AAEX;;;;GAIG;AACH,eAAO,MAAM,mBAAmB,IAAI,CAAC;AAErC;;;GAGG;AACH,wBAAgB,UAAU,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAGjD;AAED;;;;;;;;;GASG;AACH,wBAAgB,0BAA0B,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAKlE;AAED;;GAEG;AACH,wBAAgB,yBAAyB,CACvC,MAAM,EAAE,MAAM,GACb;IAAE,SAAS,EAAE,MAAM,CAAA;CAAE,GAAG,IAAI,CAoB9B;AAED;;;;;GAKG;AACH,wBAAgB,uBAAuB,CACrC,SAAS,EAAE,MAAM,EACjB,IAAI,EAAE,MAAM,GACX,MAAM,CAeR;AAED;;GAEG;AACH,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAK1D;AAED;;GAEG;AACH,wBAAgB,aAAa,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAMlD;AAED;;;;GAIG;AACH,wBAAgB,YAAY,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,CAM/D"}
@@ -0,0 +1,149 @@
1
+ "use strict";
2
+ /**
3
+ * Binary protocol v3 — opcode 상수 + helpers.
4
+ *
5
+ * 단일 source of truth: spec 문서 `binary-protocol-v3.md` (모바일 리포에 보관).
6
+ * opcode 상수 / encoding 규칙이 spec 문서와 *반드시* 일치해야 한다 — fixture-driven
7
+ * contract test (`fixtures/binary-protocol-v3.fixtures.json`) 가 wire 호환을 영구 검증.
8
+ *
9
+ * 본 파일은:
10
+ * - opcode 식별 helpers (host PTY_DATA / mobile session_attach 분기)
11
+ * - FRAME_SNAPSHOT push encoding (Approach B 의 last-frame replay)
12
+ * - PROTOCOL_ERROR 송신 helpers
13
+ * - APP_JSON encoder
14
+ */
15
+ Object.defineProperty(exports, "__esModule", { value: true });
16
+ exports.PROTOCOL_VERSION_V3 = exports.PROTOCOL_ERROR_REASON = exports.OPCODE = void 0;
17
+ exports.readOpcode = readOpcode;
18
+ exports.isAppJsonSessionAttachLike = isAppJsonSessionAttachLike;
19
+ exports.parseAppJsonSessionAttach = parseAppJsonSessionAttach;
20
+ exports.encodeFrameSnapshotPush = encodeFrameSnapshotPush;
21
+ exports.encodeProtocolError = encodeProtocolError;
22
+ exports.encodeAppJson = encodeAppJson;
23
+ exports.encodeResize = encodeResize;
24
+ exports.OPCODE = {
25
+ PTY_DATA: 0x00,
26
+ APP_JSON: 0x01,
27
+ RESIZE: 0x02,
28
+ FRAME_SNAPSHOT: 0x03,
29
+ GRID_SNAPSHOT: 0x04,
30
+ GRID_DIFF: 0x05,
31
+ PROTOCOL_ERROR: 0xff,
32
+ };
33
+ exports.PROTOCOL_ERROR_REASON = {
34
+ UNKNOWN_OPCODE: 0x01,
35
+ MALFORMED_PAYLOAD: 0x02,
36
+ VERSION_MISMATCH: 0x03,
37
+ UNAUTHORIZED: 0x04,
38
+ };
39
+ /**
40
+ * 본 프로토콜 패키지가 구현하는 binary protocol version.
41
+ * 양 client (mobile / desktop) 의 `kRelayProtocolVersion` / `PROTOCOL_VERSION`
42
+ * 과 *동시* 변경 필수.
43
+ */
44
+ exports.PROTOCOL_VERSION_V3 = 3;
45
+ /**
46
+ * frame 의 opcode 만 빠르게 식별. binary frame 검증 + 라우팅 분기에 사용.
47
+ * empty buffer 면 -1 리턴 (호출자가 close 결정).
48
+ */
49
+ function readOpcode(buffer) {
50
+ if (buffer.length === 0)
51
+ return -1;
52
+ return buffer.readUInt8(0);
53
+ }
54
+ /**
55
+ * APP_JSON frame 의 payload 가 `"session_attach"` 를 포함하는지 빠르게 검사.
56
+ *
57
+ * substring fast path — JSON parse 회피로 high-frequency 메시지 (예: terminal_input)
58
+ * 의 처리 비용을 상수로. true 일 때만 호출자가 [parseAppJsonSessionAttach] 로 정확히
59
+ * 검증.
60
+ *
61
+ * False positive 가능 (예: terminal_input 의 data field 가 "session_attach" string
62
+ * 포함) — 호출자가 JSON parse 후 type 필드로 최종 결정.
63
+ */
64
+ function isAppJsonSessionAttachLike(buffer) {
65
+ if (buffer.length < 2)
66
+ return false;
67
+ if (buffer.readUInt8(0) !== exports.OPCODE.APP_JSON)
68
+ return false;
69
+ const text = buffer.toString('utf-8', 1);
70
+ return text.includes('"session_attach"');
71
+ }
72
+ /**
73
+ * APP_JSON session_attach payload 를 JSON parse. malformed / 잘못된 type 이면 null.
74
+ */
75
+ function parseAppJsonSessionAttach(buffer) {
76
+ if (buffer.readUInt8(0) !== exports.OPCODE.APP_JSON)
77
+ return null;
78
+ const text = buffer.toString('utf-8', 1);
79
+ let parsed;
80
+ try {
81
+ parsed = JSON.parse(text);
82
+ }
83
+ catch {
84
+ return null;
85
+ }
86
+ if (!parsed ||
87
+ typeof parsed !== 'object' ||
88
+ parsed.type !== 'session_attach') {
89
+ return null;
90
+ }
91
+ const rawMachineId = parsed.machineId;
92
+ return {
93
+ machineId: typeof rawMachineId === 'string' ? rawMachineId : '',
94
+ };
95
+ }
96
+ /**
97
+ * FRAME_SNAPSHOT (0x03) frame encode.
98
+ *
99
+ * Wire format:
100
+ * `[0x03] [machineId len: BE u32] [machineId UTF-8] [ansi len: BE u32] [ansi UTF-8]`
101
+ */
102
+ function encodeFrameSnapshotPush(machineId, ansi) {
103
+ const idBytes = Buffer.from(machineId, 'utf-8');
104
+ const ansiBytes = Buffer.from(ansi, 'utf-8');
105
+ const frame = Buffer.alloc(1 + 4 + idBytes.length + 4 + ansiBytes.length);
106
+ let offset = 0;
107
+ frame.writeUInt8(exports.OPCODE.FRAME_SNAPSHOT, offset);
108
+ offset += 1;
109
+ frame.writeUInt32BE(idBytes.length, offset);
110
+ offset += 4;
111
+ idBytes.copy(frame, offset);
112
+ offset += idBytes.length;
113
+ frame.writeUInt32BE(ansiBytes.length, offset);
114
+ offset += 4;
115
+ ansiBytes.copy(frame, offset);
116
+ return frame;
117
+ }
118
+ /**
119
+ * PROTOCOL_ERROR (0xFF) frame encode.
120
+ */
121
+ function encodeProtocolError(reason) {
122
+ const frame = Buffer.alloc(2);
123
+ frame.writeUInt8(exports.OPCODE.PROTOCOL_ERROR, 0);
124
+ frame.writeUInt8(reason & 0xff, 1);
125
+ return frame;
126
+ }
127
+ /**
128
+ * APP_JSON (0x01) frame encode — server-initiated message 송신용.
129
+ */
130
+ function encodeAppJson(data) {
131
+ const body = Buffer.from(JSON.stringify(data), 'utf-8');
132
+ const frame = Buffer.alloc(1 + body.length);
133
+ frame.writeUInt8(exports.OPCODE.APP_JSON, 0);
134
+ body.copy(frame, 1);
135
+ return frame;
136
+ }
137
+ /**
138
+ * RESIZE (0x02) frame encode — mobile → host. fire-and-forget, ack 없음.
139
+ *
140
+ * Wire format: `[0x02] [cols: BE u32] [rows: BE u32]`
141
+ */
142
+ function encodeResize(cols, rows) {
143
+ const frame = Buffer.alloc(9);
144
+ frame.writeUInt8(exports.OPCODE.RESIZE, 0);
145
+ frame.writeUInt32BE(cols, 1);
146
+ frame.writeUInt32BE(rows, 5);
147
+ return frame;
148
+ }
149
+ //# sourceMappingURL=binary-protocol.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"binary-protocol.js","sourceRoot":"","sources":["../src/binary-protocol.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,13 @@
1
+ /**
2
+ * @vibe-remote/protocol — VibeRemote binary WebSocket protocol v3.
3
+ *
4
+ * Public API:
5
+ * - opcode 상수: OPCODE, PROTOCOL_ERROR_REASON, PROTOCOL_VERSION_V3
6
+ * - encoders: encodeAppJson, encodeFrameSnapshotPush, encodeProtocolError, encodeResize
7
+ * - inspectors: readOpcode, isAppJsonSessionAttachLike, parseAppJsonSessionAttach
8
+ * - APP_JSON 메시지 타입: AppJsonMessage 및 모든 Msg* 인터페이스
9
+ */
10
+ export { OPCODE, PROTOCOL_ERROR_REASON, PROTOCOL_VERSION_V3, readOpcode, isAppJsonSessionAttachLike, parseAppJsonSessionAttach, encodeFrameSnapshotPush, encodeProtocolError, encodeAppJson, encodeResize, } from './binary-protocol';
11
+ export type { AppJsonMessage, Machine, PtyStatus, ClaudeState, ClientCapability, SnapshotReqReason, PtyAuthorityMode, MsgHello, MsgHelloAck, MsgCapReq, MsgCapAck, MsgSnapshotReq, MsgMachineList, MsgSessionAttach, MsgConnectionStatus, MsgTerminalData, MsgClaudeState, MsgPtyStatus, MsgGitResult, MsgSessionReplay, MsgGridResizeCapped, MsgProtocolMismatch, MsgPing, MsgPong, MsgError, MsgInput, } from './messages';
12
+ export { isAppJsonMessage } from './messages';
13
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,EACL,MAAM,EACN,qBAAqB,EACrB,mBAAmB,EACnB,UAAU,EACV,0BAA0B,EAC1B,yBAAyB,EACzB,uBAAuB,EACvB,mBAAmB,EACnB,aAAa,EACb,YAAY,GACb,MAAM,mBAAmB,CAAC;AAE3B,YAAY,EACV,cAAc,EACd,OAAO,EACP,SAAS,EACT,WAAW,EACX,gBAAgB,EAChB,iBAAiB,EACjB,gBAAgB,EAChB,QAAQ,EACR,WAAW,EACX,SAAS,EACT,SAAS,EACT,cAAc,EACd,cAAc,EACd,gBAAgB,EAChB,mBAAmB,EACnB,eAAe,EACf,cAAc,EACd,YAAY,EACZ,YAAY,EACZ,gBAAgB,EAChB,mBAAmB,EACnB,mBAAmB,EACnB,OAAO,EACP,OAAO,EACP,QAAQ,EACR,QAAQ,GACT,MAAM,YAAY,CAAC;AAEpB,OAAO,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,26 @@
1
+ "use strict";
2
+ /**
3
+ * @vibe-remote/protocol — VibeRemote binary WebSocket protocol v3.
4
+ *
5
+ * Public API:
6
+ * - opcode 상수: OPCODE, PROTOCOL_ERROR_REASON, PROTOCOL_VERSION_V3
7
+ * - encoders: encodeAppJson, encodeFrameSnapshotPush, encodeProtocolError, encodeResize
8
+ * - inspectors: readOpcode, isAppJsonSessionAttachLike, parseAppJsonSessionAttach
9
+ * - APP_JSON 메시지 타입: AppJsonMessage 및 모든 Msg* 인터페이스
10
+ */
11
+ Object.defineProperty(exports, "__esModule", { value: true });
12
+ exports.isAppJsonMessage = exports.encodeResize = exports.encodeAppJson = exports.encodeProtocolError = exports.encodeFrameSnapshotPush = exports.parseAppJsonSessionAttach = exports.isAppJsonSessionAttachLike = exports.readOpcode = exports.PROTOCOL_VERSION_V3 = exports.PROTOCOL_ERROR_REASON = exports.OPCODE = void 0;
13
+ var binary_protocol_1 = require("./binary-protocol");
14
+ Object.defineProperty(exports, "OPCODE", { enumerable: true, get: function () { return binary_protocol_1.OPCODE; } });
15
+ Object.defineProperty(exports, "PROTOCOL_ERROR_REASON", { enumerable: true, get: function () { return binary_protocol_1.PROTOCOL_ERROR_REASON; } });
16
+ Object.defineProperty(exports, "PROTOCOL_VERSION_V3", { enumerable: true, get: function () { return binary_protocol_1.PROTOCOL_VERSION_V3; } });
17
+ Object.defineProperty(exports, "readOpcode", { enumerable: true, get: function () { return binary_protocol_1.readOpcode; } });
18
+ Object.defineProperty(exports, "isAppJsonSessionAttachLike", { enumerable: true, get: function () { return binary_protocol_1.isAppJsonSessionAttachLike; } });
19
+ Object.defineProperty(exports, "parseAppJsonSessionAttach", { enumerable: true, get: function () { return binary_protocol_1.parseAppJsonSessionAttach; } });
20
+ Object.defineProperty(exports, "encodeFrameSnapshotPush", { enumerable: true, get: function () { return binary_protocol_1.encodeFrameSnapshotPush; } });
21
+ Object.defineProperty(exports, "encodeProtocolError", { enumerable: true, get: function () { return binary_protocol_1.encodeProtocolError; } });
22
+ Object.defineProperty(exports, "encodeAppJson", { enumerable: true, get: function () { return binary_protocol_1.encodeAppJson; } });
23
+ Object.defineProperty(exports, "encodeResize", { enumerable: true, get: function () { return binary_protocol_1.encodeResize; } });
24
+ var messages_1 = require("./messages");
25
+ Object.defineProperty(exports, "isAppJsonMessage", { enumerable: true, get: function () { return messages_1.isAppJsonMessage; } });
26
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AAAA;;;;;;;;GAQG;;;AAEH,qDAW2B;AAVzB,yGAAA,MAAM,OAAA;AACN,wHAAA,qBAAqB,OAAA;AACrB,sHAAA,mBAAmB,OAAA;AACnB,6GAAA,UAAU,OAAA;AACV,6HAAA,0BAA0B,OAAA;AAC1B,4HAAA,yBAAyB,OAAA;AACzB,0HAAA,uBAAuB,OAAA;AACvB,sHAAA,mBAAmB,OAAA;AACnB,gHAAA,aAAa,OAAA;AACb,+GAAA,YAAY,OAAA;AAgCd,uCAA8C;AAArC,4GAAA,gBAAgB,OAAA"}
@@ -0,0 +1,158 @@
1
+ /**
2
+ * APP_JSON (opcode 0x01) 의 payload 타입 정의.
3
+ *
4
+ * 본 파일은 wire spec 의 `type` discriminator 기반 sum type 을 TypeScript 로 표현한다.
5
+ * 비-TS 클라이언트(Dart/Rust/Python)는 spec 문서와 fixture 만 보고 동일 메시지를 구현하면 된다.
6
+ *
7
+ * 변경 시 spec 문서 `binary-protocol-v3.md` 와 3 repo (mobile / relay / desktop) 의 구현을
8
+ * 동시에 갱신해야 한다. fixture (`fixtures/binary-protocol-v3.fixtures.json`) 의 round-trip
9
+ * 테스트가 호환을 영구 검증.
10
+ */
11
+ /** PTY 운영 상태 — `pty_status` 메시지의 `status` 필드. */
12
+ export type PtyStatus = 'idle' | 'running' | 'dead' | 'oom';
13
+ /** Claude Code 실행 상태 — host 의 PTY 패턴 매칭 결과. */
14
+ export type ClaudeState = 'idle' | 'working' | 'awaiting-approval';
15
+ /** A3 capability negotiation 의 client 후보. */
16
+ export type ClientCapability = 'grid.diff' | 'pty.data';
17
+ /** Snapshot 요청 사유 — `snapshot.req` 메시지의 `reason` 필드. */
18
+ export type SnapshotReqReason = 'recover' | 'reattach' | 'manual';
19
+ /** PTY 사이즈 권위 모드 — host 가 mobile 의 RESIZE 를 어떻게 다룰지 결정. */
20
+ export type PtyAuthorityMode = 'desktop-priority' | 'mobile-priority';
21
+ /** Machine (SSH host) — host 가 관리하는 원격 노드 메타데이터. */
22
+ export interface Machine {
23
+ id: string;
24
+ name: string;
25
+ /** 'connected' | 'disconnected' | 'connecting' | ... — host 가 자유 형식. */
26
+ status: string;
27
+ host?: string;
28
+ username?: string;
29
+ port?: number;
30
+ errorMessage?: string;
31
+ ptyAuthority?: PtyAuthorityMode;
32
+ }
33
+ /** 첫 frame — client 가 server (relay/host) 에 protocol version 통보. */
34
+ export interface MsgHello {
35
+ type: 'hello';
36
+ protocolVersion: number;
37
+ clientId?: string;
38
+ }
39
+ /** hello 응답 — server 의 version 회신. */
40
+ export interface MsgHelloAck {
41
+ type: 'hello_ack';
42
+ serverVersion: number;
43
+ }
44
+ /** Capability 협상 요청 — client → host. */
45
+ export interface MsgCapReq {
46
+ type: 'cap.req';
47
+ client_caps: ClientCapability[];
48
+ client_ver: number;
49
+ }
50
+ /** Capability 협상 응답 — host → client. */
51
+ export interface MsgCapAck {
52
+ type: 'cap.ack';
53
+ selected: ClientCapability | string;
54
+ server_ver: number;
55
+ max_cols: number;
56
+ max_rows: number;
57
+ }
58
+ /** Snapshot 재요청 — client (grid.diff path) → host. */
59
+ export interface MsgSnapshotReq {
60
+ type: 'snapshot.req';
61
+ reason: SnapshotReqReason;
62
+ last_seq: number;
63
+ }
64
+ /** host 의 machine 목록 push. */
65
+ export interface MsgMachineList {
66
+ type: 'machine_list';
67
+ machines: Machine[];
68
+ }
69
+ /** mobile 이 특정 machine 의 세션을 attach 요청 / host 가 ack. */
70
+ export interface MsgSessionAttach {
71
+ type: 'session_attach';
72
+ machineId: string;
73
+ }
74
+ /** machine 의 연결 상태 변화 push. */
75
+ export interface MsgConnectionStatus {
76
+ type: 'connection_status';
77
+ machineId: string;
78
+ status: string;
79
+ }
80
+ /** legacy(v2) 호환 path — PTY raw text 를 JSON 으로 wrap. v3 에서는 PTY_DATA opcode 권장. */
81
+ export interface MsgTerminalData {
82
+ type: 'terminal_data';
83
+ machineId: string;
84
+ data: string;
85
+ }
86
+ /** Claude 실행 상태 변화. */
87
+ export interface MsgClaudeState {
88
+ type: 'claude_state';
89
+ machineId: string;
90
+ state: ClaudeState;
91
+ }
92
+ /** PTY 운영 상태 변화. */
93
+ export interface MsgPtyStatus {
94
+ type: 'pty_status';
95
+ machineId: string;
96
+ status: PtyStatus;
97
+ pid?: number;
98
+ }
99
+ /** Git 명령 결과 push. */
100
+ export interface MsgGitResult {
101
+ type: 'git_result';
102
+ machineId: string;
103
+ action: string;
104
+ success: boolean;
105
+ data?: unknown;
106
+ error?: string;
107
+ }
108
+ /**
109
+ * Session replay — host 가 ring buffer 의 청크를 모바일에 송신.
110
+ * `totalChunks` 회 만큼 `chunkIndex` 가 증가하며 도착.
111
+ */
112
+ export interface MsgSessionReplay {
113
+ type: 'session_replay';
114
+ machineId: string;
115
+ chunkIndex: number;
116
+ totalChunks: number;
117
+ data: string;
118
+ }
119
+ /** server-side RESIZE cap (500×200) 적용 통보. */
120
+ export interface MsgGridResizeCapped {
121
+ type: 'grid_resize_capped';
122
+ actual_cols: number;
123
+ actual_rows: number;
124
+ }
125
+ /** Protocol version mismatch — server 가 client 에게 통보 (binary 0xFF 와 별도 호환 경로). */
126
+ export interface MsgProtocolMismatch {
127
+ type: 'protocol_mismatch';
128
+ clientVersion: number;
129
+ serverVersion: number;
130
+ minimumRequired: number;
131
+ }
132
+ /** Heartbeat — server initiated. */
133
+ export interface MsgPing {
134
+ type: 'ping';
135
+ }
136
+ /** Heartbeat 응답 — client → server. */
137
+ export interface MsgPong {
138
+ type: 'pong';
139
+ timestamp: number;
140
+ }
141
+ /** generic application error. */
142
+ export interface MsgError {
143
+ type: 'error';
144
+ message: string;
145
+ }
146
+ /** mobile → host: keyboard / paste / Y-N 등 raw 텍스트 입력. */
147
+ export interface MsgInput {
148
+ type: 'input';
149
+ machineId?: string;
150
+ data: string;
151
+ }
152
+ /** discriminated union — APP_JSON payload 의 모든 정식 타입. */
153
+ export type AppJsonMessage = MsgHello | MsgHelloAck | MsgCapReq | MsgCapAck | MsgSnapshotReq | MsgMachineList | MsgSessionAttach | MsgConnectionStatus | MsgTerminalData | MsgClaudeState | MsgPtyStatus | MsgGitResult | MsgSessionReplay | MsgGridResizeCapped | MsgProtocolMismatch | MsgPing | MsgPong | MsgError | MsgInput;
154
+ /** type discriminator 로 좁히는 helper. */
155
+ export declare function isAppJsonMessage<T extends AppJsonMessage['type']>(msg: unknown, type: T): msg is Extract<AppJsonMessage, {
156
+ type: T;
157
+ }>;
158
+ //# sourceMappingURL=messages.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"messages.d.ts","sourceRoot":"","sources":["../src/messages.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,20 @@
1
+ "use strict";
2
+ /**
3
+ * APP_JSON (opcode 0x01) 의 payload 타입 정의.
4
+ *
5
+ * 본 파일은 wire spec 의 `type` discriminator 기반 sum type 을 TypeScript 로 표현한다.
6
+ * 비-TS 클라이언트(Dart/Rust/Python)는 spec 문서와 fixture 만 보고 동일 메시지를 구현하면 된다.
7
+ *
8
+ * 변경 시 spec 문서 `binary-protocol-v3.md` 와 3 repo (mobile / relay / desktop) 의 구현을
9
+ * 동시에 갱신해야 한다. fixture (`fixtures/binary-protocol-v3.fixtures.json`) 의 round-trip
10
+ * 테스트가 호환을 영구 검증.
11
+ */
12
+ Object.defineProperty(exports, "__esModule", { value: true });
13
+ exports.isAppJsonMessage = isAppJsonMessage;
14
+ /** type discriminator 로 좁히는 helper. */
15
+ function isAppJsonMessage(msg, type) {
16
+ return (typeof msg === 'object' &&
17
+ msg !== null &&
18
+ msg.type === type);
19
+ }
20
+ //# sourceMappingURL=messages.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"messages.js","sourceRoot":"","sources":["../src/messages.ts"],"names":[],"mappings":";AAAA;;;;;;;;;GASG;;AAiMH,4CASC;AAVD,uCAAuC;AACvC,SAAgB,gBAAgB,CAC9B,GAAY,EACZ,IAAO;IAEP,OAAO,CACL,OAAO,GAAG,KAAK,QAAQ;QACvB,GAAG,KAAK,IAAI;QACX,GAA0B,CAAC,IAAI,KAAK,IAAI,CAC1C,CAAC;AACJ,CAAC"}
@@ -0,0 +1,229 @@
1
+ {
2
+ "$schema": "binary-protocol-v3 contract test fixtures",
3
+ "version": 3,
4
+ "description": "3 repo cross-validation fixture set. mobile (Dart) / relay (TypeScript) / desktop (TypeScript) 의 spec 가 본 fixture 를 parse 한 결과가 일치해야 함. wireHex 의 공백/대소문자는 무시 — parse 시 hex pair 로만 해석.",
5
+ "fixtures": [
6
+ {
7
+ "name": "PTY_DATA — hello newline",
8
+ "opcode": 0,
9
+ "wireHex": "00 68 65 6C 6C 6F 0A",
10
+ "expected": {
11
+ "type": "pty_data",
12
+ "textUtf8": "hello\n"
13
+ }
14
+ },
15
+ {
16
+ "name": "PTY_DATA — empty payload (PTY 가 0 byte forward — edge)",
17
+ "opcode": 0,
18
+ "wireHex": "00",
19
+ "expected": {
20
+ "type": "pty_data",
21
+ "textUtf8": ""
22
+ }
23
+ },
24
+ {
25
+ "name": "PTY_DATA — 한국어 (UTF-8 multi-byte)",
26
+ "opcode": 0,
27
+ "wireHex": "00 EC 95 88 EB 85 95",
28
+ "expected": {
29
+ "type": "pty_data",
30
+ "textUtf8": "안녕"
31
+ }
32
+ },
33
+ {
34
+ "name": "APP_JSON — hello (mobile → relay 첫 frame)",
35
+ "opcode": 1,
36
+ "wireHex": "01 7B 22 74 79 70 65 22 3A 22 68 65 6C 6C 6F 22 2C 22 70 72 6F 74 6F 63 6F 6C 56 65 72 73 69 6F 6E 22 3A 33 7D",
37
+ "expected": {
38
+ "type": "app_json",
39
+ "json": { "type": "hello", "protocolVersion": 3 }
40
+ }
41
+ },
42
+ {
43
+ "name": "APP_JSON — hello_ack (server → mobile 응답)",
44
+ "opcode": 1,
45
+ "wireHex": "01 7B 22 74 79 70 65 22 3A 22 68 65 6C 6C 6F 5F 61 63 6B 22 2C 22 73 65 72 76 65 72 56 65 72 73 69 6F 6E 22 3A 33 7D",
46
+ "expected": {
47
+ "type": "app_json",
48
+ "json": { "type": "hello_ack", "serverVersion": 3 }
49
+ }
50
+ },
51
+ {
52
+ "name": "RESIZE — 80x24 (RFC 4254 default)",
53
+ "opcode": 2,
54
+ "wireHex": "02 00 00 00 50 00 00 00 18",
55
+ "expected": {
56
+ "type": "resize",
57
+ "cols": 80,
58
+ "rows": 24
59
+ }
60
+ },
61
+ {
62
+ "name": "RESIZE — 200x60 (큰 viewport)",
63
+ "opcode": 2,
64
+ "wireHex": "02 00 00 00 C8 00 00 00 3C",
65
+ "expected": {
66
+ "type": "resize",
67
+ "cols": 200,
68
+ "rows": 60
69
+ }
70
+ },
71
+ {
72
+ "name": "FRAME_SNAPSHOT — machineId=m1, ANSI=ESC[H",
73
+ "opcode": 3,
74
+ "wireHex": "03 00 00 00 02 6D 31 00 00 00 03 1B 5B 48",
75
+ "expected": {
76
+ "type": "frame_snapshot",
77
+ "machineId": "m1",
78
+ "ansiUtf8": "[H"
79
+ }
80
+ },
81
+ {
82
+ "name": "FRAME_SNAPSHOT — empty ANSI (alt-screen exit)",
83
+ "opcode": 3,
84
+ "wireHex": "03 00 00 00 02 6D 31 00 00 00 00",
85
+ "expected": {
86
+ "type": "frame_snapshot",
87
+ "machineId": "m1",
88
+ "ansiUtf8": ""
89
+ }
90
+ },
91
+ {
92
+ "name": "PROTOCOL_ERROR — version_mismatch (0x03)",
93
+ "opcode": 255,
94
+ "wireHex": "FF 03",
95
+ "expected": {
96
+ "type": "protocol_error",
97
+ "reason": 3,
98
+ "reasonName": "version_mismatch"
99
+ }
100
+ },
101
+ {
102
+ "name": "PROTOCOL_ERROR — unknown_opcode (0x01)",
103
+ "opcode": 255,
104
+ "wireHex": "FF 01",
105
+ "expected": {
106
+ "type": "protocol_error",
107
+ "reason": 1,
108
+ "reasonName": "unknown_opcode"
109
+ }
110
+ },
111
+ {
112
+ "name": "APP_JSON — cap.req (A3 capability negotiation, client → agent)",
113
+ "opcode": 1,
114
+ "wireHex": "01 7b 22 74 79 70 65 22 3a 22 63 61 70 2e 72 65 71 22 2c 22 63 6c 69 65 6e 74 5f 63 61 70 73 22 3a 5b 22 67 72 69 64 2e 64 69 66 66 22 2c 22 70 74 79 2e 64 61 74 61 22 5d 2c 22 63 6c 69 65 6e 74 5f 76 65 72 22 3a 33 7d",
115
+ "expected": {
116
+ "type": "app_json",
117
+ "json": { "type": "cap.req", "client_caps": ["grid.diff", "pty.data"], "client_ver": 3 }
118
+ }
119
+ },
120
+ {
121
+ "name": "APP_JSON — cap.ack (A3 capability negotiation, agent → client, selected=grid.diff)",
122
+ "opcode": 1,
123
+ "wireHex": "01 7b 22 74 79 70 65 22 3a 22 63 61 70 2e 61 63 6b 22 2c 22 73 65 6c 65 63 74 65 64 22 3a 22 67 72 69 64 2e 64 69 66 66 22 2c 22 73 65 72 76 65 72 5f 76 65 72 22 3a 33 2c 22 6d 61 78 5f 63 6f 6c 73 22 3a 35 30 30 2c 22 6d 61 78 5f 72 6f 77 73 22 3a 32 30 30 7d",
124
+ "expected": {
125
+ "type": "app_json",
126
+ "json": { "type": "cap.ack", "selected": "grid.diff", "server_ver": 3, "max_cols": 500, "max_rows": 200 }
127
+ }
128
+ },
129
+ {
130
+ "name": "APP_JSON — snapshot.req (manual reason, last_seq=42)",
131
+ "opcode": 1,
132
+ "wireHex": "01 7b 22 74 79 70 65 22 3a 22 73 6e 61 70 73 68 6f 74 2e 72 65 71 22 2c 22 72 65 61 73 6f 6e 22 3a 22 6d 61 6e 75 61 6c 22 2c 22 6c 61 73 74 5f 73 65 71 22 3a 34 32 7d",
133
+ "expected": {
134
+ "type": "app_json",
135
+ "json": { "type": "snapshot.req", "reason": "manual", "last_seq": 42 }
136
+ }
137
+ },
138
+ {
139
+ "name": "GRID_SNAPSHOT — minimal 1x2 grid, cells='ab' palette mode, cursor (0,2)",
140
+ "opcode": 4,
141
+ "wireHex": "04 00 00 00 01 00 02 00 01 61 07 00 00 62 07 00 00 00 00 00 02",
142
+ "expected": {
143
+ "type": "grid_snapshot",
144
+ "seq": 1,
145
+ "cols": 2,
146
+ "rows": 1,
147
+ "cellsBytes": 8,
148
+ "cursorR": 0,
149
+ "cursorC": 2,
150
+ "decodedCells": [
151
+ { "ch": "a", "fg": 7, "bg": 0, "attrs": 0 },
152
+ { "ch": "b", "fg": 7, "bg": 0, "attrs": 0 }
153
+ ]
154
+ }
155
+ },
156
+ {
157
+ "name": "GRID_DIFF — 1 op (r=0,c=0,ch=X,fg=1 red,bg=0,attrs=bold)",
158
+ "opcode": 5,
159
+ "wireHex": "05 00 00 00 02 00 00 00 01 00 01 00 00 00 00 58 01 00 01",
160
+ "expected": {
161
+ "type": "grid_diff",
162
+ "seq": 2,
163
+ "baseSeq": 1,
164
+ "opCount": 1,
165
+ "ops": [
166
+ { "r": 0, "c": 0, "ch": "X", "fg": 1, "bg": 0, "attrs": 1 }
167
+ ]
168
+ }
169
+ }
170
+ ],
171
+ "malformed": [
172
+ {
173
+ "name": "RESIZE — payload 길이 8 미만 (malformed)",
174
+ "wireHex": "02 00 00 00 50",
175
+ "expected": {
176
+ "type": "parse_error",
177
+ "shouldEmit": "PROTOCOL_ERROR(0x02 malformed_payload)"
178
+ }
179
+ },
180
+ {
181
+ "name": "FRAME_SNAPSHOT — machineId len 헤더 부족",
182
+ "wireHex": "03 00 00",
183
+ "expected": {
184
+ "type": "parse_error",
185
+ "shouldEmit": "PROTOCOL_ERROR(0x02 malformed_payload)"
186
+ }
187
+ },
188
+ {
189
+ "name": "FRAME_SNAPSHOT — declared len 이 buffer 초과",
190
+ "wireHex": "03 00 00 00 FF 6D 31",
191
+ "expected": {
192
+ "type": "parse_error",
193
+ "shouldEmit": "PROTOCOL_ERROR(0x02 malformed_payload)"
194
+ }
195
+ },
196
+ {
197
+ "name": "Unknown opcode (0x10 — reserved 영역)",
198
+ "wireHex": "10 DE AD BE EF",
199
+ "expected": {
200
+ "type": "parse_error",
201
+ "shouldEmit": "PROTOCOL_ERROR(0x01 unknown_opcode)"
202
+ }
203
+ },
204
+ {
205
+ "name": "Empty frame (0 byte)",
206
+ "wireHex": "",
207
+ "expected": {
208
+ "type": "parse_error",
209
+ "shouldEmit": "PROTOCOL_ERROR(0x02 malformed_payload)"
210
+ }
211
+ },
212
+ {
213
+ "name": "GRID_SNAPSHOT — header < 11 bytes (cursor 영역 없음)",
214
+ "wireHex": "04 00 00 00 01 00 02 00 01",
215
+ "expected": {
216
+ "type": "parse_error",
217
+ "shouldEmit": "PROTOCOL_ERROR(0x02 malformed_payload)"
218
+ }
219
+ },
220
+ {
221
+ "name": "GRID_DIFF — header < 11 bytes (opCount/ops 영역 없음)",
222
+ "wireHex": "05 00 00 00 02 00 00",
223
+ "expected": {
224
+ "type": "parse_error",
225
+ "shouldEmit": "PROTOCOL_ERROR(0x02 malformed_payload)"
226
+ }
227
+ }
228
+ ]
229
+ }
package/package.json ADDED
@@ -0,0 +1,51 @@
1
+ {
2
+ "name": "vibe-remote-protocol",
3
+ "version": "1.0.0",
4
+ "description": "VibeRemote binary WebSocket protocol v3 — opcode constants, encoders/decoders, and TypeScript message types shared by relay, desktop agent, and mobile clients.",
5
+ "license": "MIT",
6
+ "author": "jun-young1993",
7
+ "homepage": "https://github.com/june-glre/vibe-remote-protocol",
8
+ "repository": {
9
+ "type": "git",
10
+ "url": "git+https://github.com/june-glre/vibe-remote-protocol.git"
11
+ },
12
+ "keywords": [
13
+ "vibe-remote",
14
+ "websocket",
15
+ "protocol",
16
+ "binary",
17
+ "terminal",
18
+ "pty",
19
+ "relay"
20
+ ],
21
+ "main": "./dist/index.js",
22
+ "types": "./dist/index.d.ts",
23
+ "files": [
24
+ "dist",
25
+ "fixtures",
26
+ "README.md",
27
+ "README_KR.md",
28
+ "LICENSE"
29
+ ],
30
+ "scripts": {
31
+ "build": "tsc -p tsconfig.json",
32
+ "test": "jest",
33
+ "lint": "tsc --noEmit",
34
+ "clean": "rimraf dist"
35
+ },
36
+ "devDependencies": {
37
+ "@types/jest": "^29.5.2",
38
+ "@types/node": "^20.3.1",
39
+ "jest": "^29.5.0",
40
+ "rimraf": "^5.0.5",
41
+ "ts-jest": "^29.1.0",
42
+ "typescript": "^5.1.3"
43
+ },
44
+ "engines": {
45
+ "node": ">=18"
46
+ },
47
+ "publishConfig": {
48
+ "access": "public"
49
+ }
50
+
51
+ }