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.
- package/README.md +2 -2
- package/dist/{_shared-Bxa2_kq7.mjs → _shared-S-Ujqz_L.mjs} +1 -1
- package/dist/adapters/build.d.mts +1 -1
- package/dist/adapters/build.mjs +9 -5
- package/dist/adapters/cli.d.mts +2 -2
- package/dist/adapters/cli.mjs +78 -292
- package/dist/adapters/dev.d.mts +83 -0
- package/dist/adapters/dev.mjs +2 -0
- package/dist/adapters/embedded.d.mts +1 -1
- package/dist/adapters/kit.d.mts +1 -1
- package/dist/adapters/mcp.d.mts +1 -1
- package/dist/adapters/mcp.mjs +6 -3
- package/dist/adapters/vite.d.mts +1 -1
- package/dist/adapters/vite.mjs +1 -1
- package/dist/client/index.d.mts +41 -5
- package/dist/client/index.mjs +1673 -2
- package/dist/constants.d.mts +1 -1
- package/dist/{context-xQo1FcxX.mjs → context-BfvbAyGk.mjs} +429 -41
- package/dist/dev-B8i_CBlQ.mjs +325 -0
- package/dist/{devtool-CHT-4_OU.d.mts → devtool-Bm6zZAw5.d.mts} +371 -28
- package/dist/host-h3-BMvrFzIJ.mjs +24 -0
- package/dist/{index-Cei8vVcd.d.mts → index-BCo03GEF.d.mts} +1 -1
- package/dist/index.d.mts +4 -4
- package/dist/index.mjs +11 -2
- package/dist/node/index.d.mts +41 -53
- package/dist/node/index.mjs +5 -4
- package/dist/recipes/open-helpers.d.mts +1 -1
- package/dist/recipes/open-helpers.mjs +4 -2
- package/dist/rpc/index.d.mts +2 -2
- package/dist/rpc/index.mjs +3 -3
- package/dist/rpc/transports/ws-client.d.mts +1 -1
- package/dist/rpc/transports/ws-client.mjs +1 -1
- package/dist/rpc/transports/ws-server.d.mts +1 -1
- package/dist/rpc/transports/ws-server.mjs +1 -1
- package/dist/{rpc-hbRk8qtt.mjs → rpc-INbWfHoX.mjs} +1 -1
- package/dist/{serialization-CWcWE7x7.mjs → serialization-DwKi05Pn.mjs} +1 -1
- package/dist/server-DksyT-um.d.mts +54 -0
- package/dist/{server-DkJ2-s0Y.mjs → server-qRRM8t3v.mjs} +27 -2
- package/dist/{static-dump-DwFfj4U_.mjs → static-dump-C1aVSwxY.mjs} +2 -19
- package/dist/types/index.d.mts +4 -4
- package/dist/{types-BkyzI9r4.d.mts → types-ag029cAe.d.mts} +1 -1
- package/dist/utils/events.d.mts +1 -1
- package/dist/utils/shared-state.d.mts +2 -2
- package/dist/utils/shared-state.mjs +1 -1
- package/dist/utils/state.d.mts +2 -1
- package/dist/utils/state.mjs +1 -1
- package/dist/utils/streaming-channel.d.mts +2 -0
- package/dist/utils/streaming-channel.mjs +255 -0
- package/dist/{ws-client-CDGmViwt.d.mts → ws-client-DQySVfF_.d.mts} +1 -1
- package/dist/{ws-server-Cu8tmZcF.d.mts → ws-server-VQqESKAs.d.mts} +13 -1
- package/package.json +10 -17
- package/skills/devframe/README.md +13 -0
- package/skills/devframe/SKILL.md +452 -0
- package/skills/devframe/templates/counter-devtool.ts +29 -0
- package/skills/devframe/templates/spa-devtool.ts +33 -0
- package/skills/devframe/templates/vite-client.ts +11 -0
- package/dist/client-CsR1_h3o.mjs +0 -1569
- package/dist/helpers/nuxt/index.d.mts +0 -46
- package/dist/helpers/nuxt/index.mjs +0 -58
- package/dist/helpers/nuxt/runtime/plugin.client.d.mts +0 -8
- package/dist/helpers/nuxt/runtime/plugin.client.mjs +0 -12
- package/dist/immer-DEqg5kOd.mjs +0 -894
- package/dist/src-DIKqQIjp.mjs +0 -85
- /package/dist/{define-CW9gLnyG.mjs → define-Bb4zh-Dc.mjs} +0 -0
- /package/dist/{main-BJD9t8dk.mjs → main-DpINGndA.mjs} +0 -0
- /package/dist/{open-BMO2_-wC.mjs → open-BtOOEldu.mjs} +0 -0
- /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-
|
|
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.
|
|
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.
|
|
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.
|
|
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
|
+
```
|