devframe 0.1.18 → 0.1.20

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.
Files changed (67) hide show
  1. package/README.md +2 -2
  2. package/dist/{_shared-Bxa2_kq7.mjs → _shared-S-Ujqz_L.mjs} +1 -1
  3. package/dist/adapters/build.d.mts +1 -1
  4. package/dist/adapters/build.mjs +9 -5
  5. package/dist/adapters/cli.d.mts +2 -2
  6. package/dist/adapters/cli.mjs +78 -292
  7. package/dist/adapters/dev.d.mts +83 -0
  8. package/dist/adapters/dev.mjs +2 -0
  9. package/dist/adapters/embedded.d.mts +1 -1
  10. package/dist/adapters/kit.d.mts +1 -1
  11. package/dist/adapters/mcp.d.mts +1 -1
  12. package/dist/adapters/mcp.mjs +6 -3
  13. package/dist/adapters/vite.d.mts +1 -1
  14. package/dist/adapters/vite.mjs +1 -1
  15. package/dist/client/index.d.mts +41 -5
  16. package/dist/client/index.mjs +1673 -2
  17. package/dist/constants.d.mts +1 -1
  18. package/dist/{context-xQo1FcxX.mjs → context-BfvbAyGk.mjs} +429 -41
  19. package/dist/dev-B8i_CBlQ.mjs +325 -0
  20. package/dist/{devtool-CHT-4_OU.d.mts → devtool-Bm6zZAw5.d.mts} +371 -28
  21. package/dist/host-h3-BMvrFzIJ.mjs +24 -0
  22. package/dist/{index-Cei8vVcd.d.mts → index-BCo03GEF.d.mts} +1 -1
  23. package/dist/index.d.mts +4 -4
  24. package/dist/index.mjs +11 -2
  25. package/dist/node/index.d.mts +41 -53
  26. package/dist/node/index.mjs +5 -4
  27. package/dist/recipes/open-helpers.d.mts +1 -1
  28. package/dist/recipes/open-helpers.mjs +4 -2
  29. package/dist/rpc/index.d.mts +2 -2
  30. package/dist/rpc/index.mjs +3 -3
  31. package/dist/rpc/transports/ws-client.d.mts +1 -1
  32. package/dist/rpc/transports/ws-client.mjs +1 -1
  33. package/dist/rpc/transports/ws-server.d.mts +1 -1
  34. package/dist/rpc/transports/ws-server.mjs +1 -1
  35. package/dist/{rpc-hbRk8qtt.mjs → rpc-INbWfHoX.mjs} +1 -1
  36. package/dist/{serialization-CWcWE7x7.mjs → serialization-DwKi05Pn.mjs} +1 -1
  37. package/dist/server-DksyT-um.d.mts +54 -0
  38. package/dist/{server-DkJ2-s0Y.mjs → server-qRRM8t3v.mjs} +27 -2
  39. package/dist/{static-dump-DwFfj4U_.mjs → static-dump-C1aVSwxY.mjs} +2 -19
  40. package/dist/types/index.d.mts +4 -4
  41. package/dist/{types-BkyzI9r4.d.mts → types-ag029cAe.d.mts} +1 -1
  42. package/dist/utils/events.d.mts +1 -1
  43. package/dist/utils/shared-state.d.mts +2 -2
  44. package/dist/utils/shared-state.mjs +1 -1
  45. package/dist/utils/state.d.mts +2 -1
  46. package/dist/utils/state.mjs +1 -1
  47. package/dist/utils/streaming-channel.d.mts +2 -0
  48. package/dist/utils/streaming-channel.mjs +255 -0
  49. package/dist/{ws-client-CDGmViwt.d.mts → ws-client-DQySVfF_.d.mts} +1 -1
  50. package/dist/{ws-server-Cu8tmZcF.d.mts → ws-server-VQqESKAs.d.mts} +13 -1
  51. package/package.json +10 -17
  52. package/skills/devframe/README.md +13 -0
  53. package/skills/devframe/SKILL.md +452 -0
  54. package/skills/devframe/templates/counter-devtool.ts +29 -0
  55. package/skills/devframe/templates/spa-devtool.ts +33 -0
  56. package/skills/devframe/templates/vite-client.ts +11 -0
  57. package/dist/client-CsR1_h3o.mjs +0 -1569
  58. package/dist/helpers/nuxt/index.d.mts +0 -46
  59. package/dist/helpers/nuxt/index.mjs +0 -58
  60. package/dist/helpers/nuxt/runtime/plugin.client.d.mts +0 -8
  61. package/dist/helpers/nuxt/runtime/plugin.client.mjs +0 -12
  62. package/dist/immer-DEqg5kOd.mjs +0 -894
  63. package/dist/src-DIKqQIjp.mjs +0 -85
  64. /package/dist/{define-CW9gLnyG.mjs → define-Bb4zh-Dc.mjs} +0 -0
  65. /package/dist/{main-BJD9t8dk.mjs → main-DpINGndA.mjs} +0 -0
  66. /package/dist/{open-BMO2_-wC.mjs → open-BtOOEldu.mjs} +0 -0
  67. /package/dist/{transports-4bqw6Fqg.mjs → transports-BPUzHhI2.mjs} +0 -0
@@ -0,0 +1,255 @@
1
+ import { createEventEmitter } from "./events.mjs";
2
+ import { nanoid } from "./nanoid.mjs";
3
+ //#region src/utils/streaming-channel.ts
4
+ const DEFAULT_HIGH_WATER_MARK = 256;
5
+ var StreamClosedError = class extends Error {
6
+ name = "StreamClosedError";
7
+ };
8
+ /**
9
+ * Build a server-side stream sink. RPC-agnostic — the RPC host wires
10
+ * `events.on('chunk' | 'end')` to broadcast, and reads `buffer` to replay
11
+ * for late or reconnecting subscribers.
12
+ */
13
+ function createStreamSink(options = {}) {
14
+ const id = options.id ?? nanoid();
15
+ const replayWindow = Math.max(0, options.replayWindow ?? 0);
16
+ const events = createEventEmitter();
17
+ const controller = new AbortController();
18
+ const buffer = [];
19
+ let closed = false;
20
+ let lastSeq = 0;
21
+ function write(chunk) {
22
+ if (closed) throw new StreamClosedError(`Cannot write to a closed stream "${id}"`);
23
+ lastSeq += 1;
24
+ if (replayWindow > 0) {
25
+ buffer.push({
26
+ seq: lastSeq,
27
+ chunk
28
+ });
29
+ if (buffer.length > replayWindow) buffer.splice(0, buffer.length - replayWindow);
30
+ }
31
+ events.emit("chunk", lastSeq, chunk);
32
+ }
33
+ function error(reason) {
34
+ if (closed) return;
35
+ closed = true;
36
+ const payload = toErrorPayload(reason);
37
+ controller.abort(reason);
38
+ events.emit("end", payload);
39
+ }
40
+ function close() {
41
+ if (closed) return;
42
+ closed = true;
43
+ if (!controller.signal.aborted) controller.abort("stream closed");
44
+ events.emit("end", void 0);
45
+ }
46
+ function abort(reason) {
47
+ if (closed) return;
48
+ if (!controller.signal.aborted) controller.abort(reason ?? "aborted");
49
+ }
50
+ const writable = new WritableStream({
51
+ write(chunk) {
52
+ write(chunk);
53
+ },
54
+ close() {
55
+ close();
56
+ },
57
+ abort(reason) {
58
+ error(reason);
59
+ }
60
+ });
61
+ return {
62
+ id,
63
+ signal: controller.signal,
64
+ get closed() {
65
+ return closed;
66
+ },
67
+ get lastSeq() {
68
+ return lastSeq;
69
+ },
70
+ write,
71
+ error,
72
+ close,
73
+ abort,
74
+ writable,
75
+ events,
76
+ buffer
77
+ };
78
+ }
79
+ /**
80
+ * Build a client-side stream reader. RPC-agnostic — the RPC host calls
81
+ * `_push(seq, chunk)` on each incoming chunk and `_end(error?)` on the
82
+ * terminal frame. Consumers iterate with `for await` or pipe `readable`.
83
+ */
84
+ function createStreamReader(options = {}) {
85
+ const id = options.id ?? nanoid();
86
+ const highWaterMark = Math.max(1, options.highWaterMark ?? DEFAULT_HIGH_WATER_MARK);
87
+ const queue = [];
88
+ let lastSeenSeq = 0;
89
+ let done = false;
90
+ let cancelled = false;
91
+ let endError;
92
+ let pending;
93
+ let pullController;
94
+ let readableInstance;
95
+ function drainNext() {
96
+ if (!pending) return;
97
+ if (queue.length > 0) {
98
+ const value = queue.shift();
99
+ const r = pending;
100
+ pending = void 0;
101
+ r.resolve({
102
+ value,
103
+ done: false
104
+ });
105
+ return;
106
+ }
107
+ if (done) {
108
+ const r = pending;
109
+ pending = void 0;
110
+ if (endError) {
111
+ const err = new Error(endError.message);
112
+ err.name = endError.name;
113
+ r.reject(err);
114
+ } else r.resolve({
115
+ value: void 0,
116
+ done: true
117
+ });
118
+ }
119
+ }
120
+ function feedReadable() {
121
+ if (!pullController) return;
122
+ while (queue.length > 0) {
123
+ const v = queue.shift();
124
+ try {
125
+ pullController.enqueue(v);
126
+ } catch {
127
+ break;
128
+ }
129
+ }
130
+ if (done && pullController) {
131
+ try {
132
+ if (endError) {
133
+ const err = new Error(endError.message);
134
+ err.name = endError.name;
135
+ pullController.error(err);
136
+ } else pullController.close();
137
+ } catch {}
138
+ pullController = void 0;
139
+ }
140
+ }
141
+ function push(seq, chunk) {
142
+ if (done || cancelled) return;
143
+ if (seq <= lastSeenSeq) return;
144
+ lastSeenSeq = seq;
145
+ queue.push(chunk);
146
+ if (queue.length > highWaterMark) {
147
+ const overflow = queue.length - highWaterMark;
148
+ queue.splice(0, overflow);
149
+ options.onOverflow?.(overflow);
150
+ }
151
+ drainNext();
152
+ if (readableInstance) feedReadable();
153
+ }
154
+ function end(error) {
155
+ if (done) return;
156
+ done = true;
157
+ endError = error;
158
+ drainNext();
159
+ if (readableInstance) feedReadable();
160
+ }
161
+ function cancel() {
162
+ if (cancelled || done) return;
163
+ cancelled = true;
164
+ options.onCancel?.();
165
+ end(void 0);
166
+ }
167
+ function getReadable() {
168
+ if (readableInstance) return readableInstance;
169
+ readableInstance = new ReadableStream({
170
+ start(controller) {
171
+ pullController = controller;
172
+ feedReadable();
173
+ },
174
+ cancel() {
175
+ cancel();
176
+ }
177
+ });
178
+ return readableInstance;
179
+ }
180
+ return {
181
+ id,
182
+ get cancelled() {
183
+ return cancelled;
184
+ },
185
+ get done() {
186
+ return done;
187
+ },
188
+ get lastSeenSeq() {
189
+ return lastSeenSeq;
190
+ },
191
+ get readable() {
192
+ return getReadable();
193
+ },
194
+ cancel,
195
+ _push: push,
196
+ _end: end,
197
+ [Symbol.asyncIterator]() {
198
+ return {
199
+ next() {
200
+ if (queue.length > 0) return Promise.resolve({
201
+ value: queue.shift(),
202
+ done: false
203
+ });
204
+ if (done) {
205
+ if (endError) {
206
+ const err = new Error(endError.message);
207
+ err.name = endError.name;
208
+ return Promise.reject(err);
209
+ }
210
+ return Promise.resolve({
211
+ value: void 0,
212
+ done: true
213
+ });
214
+ }
215
+ return new Promise((resolve, reject) => {
216
+ pending = {
217
+ resolve,
218
+ reject
219
+ };
220
+ });
221
+ },
222
+ return() {
223
+ cancel();
224
+ return Promise.resolve({
225
+ value: void 0,
226
+ done: true
227
+ });
228
+ }
229
+ };
230
+ }
231
+ };
232
+ }
233
+ function toErrorPayload(reason) {
234
+ if (reason instanceof Error) return {
235
+ name: reason.name || "Error",
236
+ message: reason.message
237
+ };
238
+ if (typeof reason === "string") return {
239
+ name: "Error",
240
+ message: reason
241
+ };
242
+ try {
243
+ return {
244
+ name: "Error",
245
+ message: JSON.stringify(reason)
246
+ };
247
+ } catch {
248
+ return {
249
+ name: "Error",
250
+ message: String(reason)
251
+ };
252
+ }
253
+ }
254
+ //#endregion
255
+ export { createStreamReader, createStreamSink };
@@ -1,4 +1,4 @@
1
- import { g as RpcFunctionDefinitionAny } from "./types-BkyzI9r4.mjs";
1
+ import { g as RpcFunctionDefinitionAny } from "./types-ag029cAe.mjs";
2
2
  import { ChannelOptions } from "birpc";
3
3
 
4
4
  //#region src/rpc/transports/ws-client.d.ts
@@ -1,4 +1,4 @@
1
- import { g as RpcFunctionDefinitionAny } from "./types-BkyzI9r4.mjs";
1
+ import { g as RpcFunctionDefinitionAny } from "./types-ag029cAe.mjs";
2
2
  import { BirpcGroup, ChannelOptions } from "birpc";
3
3
  import { ServerOptions } from "node:https";
4
4
  import { WebSocket, WebSocketServer } from "ws";
@@ -11,6 +11,18 @@ interface DevToolsNodeRpcSessionMeta {
11
11
  clientAuthToken?: string;
12
12
  isTrusted?: boolean;
13
13
  subscribedStates: Set<string>;
14
+ /**
15
+ * Streams this session has subscribed to via
16
+ * `rpc.streaming.subscribe(channel, id)`. Tracked here for O(1) cleanup
17
+ * on disconnect; the wire format is `${channel}\x1F${id}`.
18
+ */
19
+ subscribedStreams?: Set<string>;
20
+ /**
21
+ * Inbound streams this session is currently uploading to (via
22
+ * `rpc.streaming.upload(channel, id)`). Tracked for cleanup on
23
+ * disconnect; same wire format as `subscribedStreams`.
24
+ */
25
+ uploadingStreams?: Set<string>;
14
26
  }
15
27
  interface WsRpcTransportOptions {
16
28
  /** Attach to an existing WebSocketServer. When provided, `port`, `host`, and `https` are ignored. */
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "devframe",
3
3
  "type": "module",
4
- "version": "0.1.18",
4
+ "version": "0.1.20",
5
5
  "description": "Framework for building generic DevTools",
6
6
  "author": "Anthony Fu <anthonyfu117@hotmail.com>",
7
7
  "license": "MIT",
@@ -22,14 +22,13 @@
22
22
  ".": "./dist/index.mjs",
23
23
  "./adapters/build": "./dist/adapters/build.mjs",
24
24
  "./adapters/cli": "./dist/adapters/cli.mjs",
25
+ "./adapters/dev": "./dist/adapters/dev.mjs",
25
26
  "./adapters/embedded": "./dist/adapters/embedded.mjs",
26
27
  "./adapters/kit": "./dist/adapters/kit.mjs",
27
28
  "./adapters/mcp": "./dist/adapters/mcp.mjs",
28
29
  "./adapters/vite": "./dist/adapters/vite.mjs",
29
30
  "./client": "./dist/client/index.mjs",
30
31
  "./constants": "./dist/constants.mjs",
31
- "./helpers/nuxt": "./dist/helpers/nuxt/index.mjs",
32
- "./helpers/nuxt/runtime/plugin.client": "./dist/helpers/nuxt/runtime/plugin.client.mjs",
33
32
  "./node": "./dist/node/index.mjs",
34
33
  "./recipes/open-helpers": "./dist/recipes/open-helpers.mjs",
35
34
  "./rpc": "./dist/rpc/index.mjs",
@@ -44,46 +43,41 @@
44
43
  "./utils/promise": "./dist/utils/promise.mjs",
45
44
  "./utils/shared-state": "./dist/utils/shared-state.mjs",
46
45
  "./utils/state": "./dist/utils/state.mjs",
46
+ "./utils/streaming-channel": "./dist/utils/streaming-channel.mjs",
47
47
  "./utils/when": "./dist/utils/when.mjs",
48
48
  "./package.json": "./package.json"
49
49
  },
50
50
  "types": "./dist/index.d.ts",
51
51
  "files": [
52
- "dist"
52
+ "dist",
53
+ "skills"
53
54
  ],
54
55
  "peerDependencies": {
55
- "@modelcontextprotocol/sdk": "^1.0.0",
56
- "@nuxt/kit": "^3.0.0 || ^4.0.0",
57
- "launch-editor": "^2.0.0"
56
+ "@modelcontextprotocol/sdk": "^1.0.0"
58
57
  },
59
58
  "peerDependenciesMeta": {
60
59
  "@modelcontextprotocol/sdk": {
61
60
  "optional": true
62
- },
63
- "@nuxt/kit": {
64
- "optional": true
65
- },
66
- "launch-editor": {
67
- "optional": true
68
61
  }
69
62
  },
70
63
  "dependencies": {
71
- "@valibot/to-json-schema": "^1.6.0",
64
+ "@valibot/to-json-schema": "^1.7.0",
72
65
  "ansis": "^4.2.0",
73
66
  "birpc": "^4.0.0",
74
67
  "cac": "^7.0.0",
75
68
  "h3": "^1.15.11",
69
+ "immer": "^11.1.7",
70
+ "launch-editor": "^2.13.2",
76
71
  "logs-sdk": "^0.0.6",
77
72
  "ohash": "^2.0.11",
78
73
  "pathe": "^2.0.3",
79
74
  "sirv": "^3.0.2",
80
75
  "structured-clone-es": "^2.0.0",
81
- "valibot": "^1.3.1",
76
+ "valibot": "^1.4.0",
82
77
  "ws": "^8.20.0"
83
78
  },
84
79
  "devDependencies": {
85
80
  "@modelcontextprotocol/sdk": "^1.29.0",
86
- "@nuxt/kit": "^4.4.4",
87
81
  "launch-editor": "^2.13.2",
88
82
  "tsdown": "^0.21.10",
89
83
  "whenexpr": "^0.1.2"
@@ -96,7 +90,6 @@
96
90
  "define-lazy-prop": "3.0.0",
97
91
  "get-port-please": "3.2.0",
98
92
  "human-id": "4.1.3",
99
- "immer": "11.1.4",
100
93
  "is-docker": "3.0.0",
101
94
  "is-in-ssh": "1.0.0",
102
95
  "is-inside-container": "1.0.0",
@@ -0,0 +1,13 @@
1
+ # devframe skill
2
+
3
+ An agent skill that teaches Claude Code how to build a full-featured
4
+ devtool with devframe.
5
+
6
+ ## Opt-in
7
+
8
+ Until devframe publishes an installable skill, link this directory from
9
+ your Claude Code skills path. Example for the CLI:
10
+
11
+ ```sh
12
+ ln -s "$PWD/devframe/skills/devframe" "$HOME/.claude/skills/devframe"
13
+ ```