@zhushanwen/extension-protocol 0.11.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/README.md +43 -0
- package/dist/background-task-Cu0p02O-.d.ts +106 -0
- package/dist/background-task-entry.d.ts +159 -0
- package/dist/background-task-entry.js +217 -0
- package/dist/chunk-NJ3E4ISR.js +28 -0
- package/dist/index.d.ts +798 -0
- package/dist/index.js +389 -0
- package/package.json +35 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,389 @@
|
|
|
1
|
+
import {
|
|
2
|
+
BACKGROUND_TASK_ID_PREFIX,
|
|
3
|
+
BACKGROUND_TASK_REGISTRY_FILENAME,
|
|
4
|
+
BACKGROUND_TASK_REGISTRY_VERSION,
|
|
5
|
+
BASE_TOOL_ENHANCE_DIRNAME,
|
|
6
|
+
MAX_TERMINAL_REGISTRY_ENTRIES,
|
|
7
|
+
isActiveBackgroundTaskState,
|
|
8
|
+
isBackgroundTaskRegistryEntry,
|
|
9
|
+
isTerminalBackgroundTaskState
|
|
10
|
+
} from "./chunk-NJ3E4ISR.js";
|
|
11
|
+
|
|
12
|
+
// src/core/types.ts
|
|
13
|
+
var PROTOCOL_VERSION = 1;
|
|
14
|
+
|
|
15
|
+
// src/core/markers.ts
|
|
16
|
+
var GUI_WIDGET_MARKER = "\0TAIJI_GUI_WIDGET:";
|
|
17
|
+
|
|
18
|
+
// src/core/helpers.ts
|
|
19
|
+
function isGuiCapable(ctx) {
|
|
20
|
+
return ctx.mode === "rpc";
|
|
21
|
+
}
|
|
22
|
+
function guiResult(component, meta) {
|
|
23
|
+
const result = {
|
|
24
|
+
v: PROTOCOL_VERSION,
|
|
25
|
+
component: stripUndefined(component)
|
|
26
|
+
};
|
|
27
|
+
if (meta !== void 0) result.meta = stripUndefined(meta);
|
|
28
|
+
return result;
|
|
29
|
+
}
|
|
30
|
+
function guiComponent(type, props) {
|
|
31
|
+
return { type, props };
|
|
32
|
+
}
|
|
33
|
+
function guiSetWidget(ctx, key, result) {
|
|
34
|
+
if (!ctx.ui?.setWidget) return;
|
|
35
|
+
if (result) {
|
|
36
|
+
const encoded = [GUI_WIDGET_MARKER + JSON.stringify(stripUndefined(result))];
|
|
37
|
+
ctx.ui.setWidget(key, encoded);
|
|
38
|
+
} else {
|
|
39
|
+
ctx.ui.setWidget(key, void 0);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
function setWidgetDual(ctx, key, content) {
|
|
43
|
+
if (!ctx.ui?.setWidget) return;
|
|
44
|
+
if (content === void 0) {
|
|
45
|
+
ctx.ui.setWidget(key, void 0);
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
if (isGuiCapable(ctx)) {
|
|
49
|
+
guiSetWidget(ctx, key, content.gui);
|
|
50
|
+
} else {
|
|
51
|
+
ctx.ui.setWidget(key, content.text);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
function extractGui(details) {
|
|
55
|
+
const g = details?.__gui__;
|
|
56
|
+
if (g && typeof g === "object" && "v" in g && "component" in g && g.v === PROTOCOL_VERSION) {
|
|
57
|
+
return g;
|
|
58
|
+
}
|
|
59
|
+
return void 0;
|
|
60
|
+
}
|
|
61
|
+
function isGuiComponent(value) {
|
|
62
|
+
if (value === null || typeof value !== "object") return false;
|
|
63
|
+
const obj = value;
|
|
64
|
+
return typeof obj.type === "string" && obj.props !== null && obj.props !== void 0 && typeof obj.props === "object";
|
|
65
|
+
}
|
|
66
|
+
function isGuiRenderResult(value) {
|
|
67
|
+
if (value === null || typeof value !== "object") return false;
|
|
68
|
+
const obj = value;
|
|
69
|
+
return obj.v === PROTOCOL_VERSION && isGuiComponent(obj.component);
|
|
70
|
+
}
|
|
71
|
+
function firstContentText(result) {
|
|
72
|
+
const first = result.content[0];
|
|
73
|
+
return first?.type === "text" ? first.text ?? "" : "";
|
|
74
|
+
}
|
|
75
|
+
function stripUndefined(obj) {
|
|
76
|
+
if (obj === null || obj === void 0) return obj;
|
|
77
|
+
if (typeof obj !== "object") return obj;
|
|
78
|
+
if (Array.isArray(obj)) return obj.map(stripUndefined);
|
|
79
|
+
const result = {};
|
|
80
|
+
for (const [key, value] of Object.entries(obj)) {
|
|
81
|
+
if (value !== void 0) {
|
|
82
|
+
result[key] = typeof value === "object" ? stripUndefined(value) : value;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
return result;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// src/core/select-rpc.ts
|
|
89
|
+
var RESPONSE_PREVIEW_LENGTH = 200;
|
|
90
|
+
function isChannelErrorResult(v) {
|
|
91
|
+
return typeof v === "object" && v !== null && !Array.isArray(v) && typeof v.error === "string";
|
|
92
|
+
}
|
|
93
|
+
function formatChannelErrorText(err) {
|
|
94
|
+
return err.hint ? `${err.error}
|
|
95
|
+
hint: ${err.hint}` : err.error;
|
|
96
|
+
}
|
|
97
|
+
function emitRpcLog(opts, msg, detail) {
|
|
98
|
+
opts?.log?.(msg, detail);
|
|
99
|
+
}
|
|
100
|
+
async function callMarkerRpc(ctx, marker, payload, opts) {
|
|
101
|
+
const select = ctx.ui?.select;
|
|
102
|
+
if (!select) {
|
|
103
|
+
throw new Error(
|
|
104
|
+
`callMarkerRpc() requires ctx.ui.select (marker ${JSON.stringify(marker)}). Non-GUI ctx must be gated by the caller first \u2014 see askUserInteract precedent (isGuiCapable(ctx) && ctx.ui?.select, or a mode check).`
|
|
105
|
+
);
|
|
106
|
+
}
|
|
107
|
+
let value;
|
|
108
|
+
try {
|
|
109
|
+
value = await select(marker, [payload], { signal: opts?.signal, timeout: opts?.timeout });
|
|
110
|
+
} catch (err) {
|
|
111
|
+
emitRpcLog(opts, `select channel threw (marker ${JSON.stringify(marker)})`, {
|
|
112
|
+
reason: err instanceof Error ? err.message : String(err)
|
|
113
|
+
});
|
|
114
|
+
return { ok: false, reason: "channel-error" };
|
|
115
|
+
}
|
|
116
|
+
if (value === void 0 || value === null) {
|
|
117
|
+
return { ok: false, reason: opts?.signal?.aborted ? "cancelled" : "timeout" };
|
|
118
|
+
}
|
|
119
|
+
if (typeof value !== "string") {
|
|
120
|
+
emitRpcLog(opts, `select resolved non-string value (marker ${JSON.stringify(marker)})`, {
|
|
121
|
+
valueType: typeof value
|
|
122
|
+
});
|
|
123
|
+
return { ok: false, reason: "channel-error" };
|
|
124
|
+
}
|
|
125
|
+
try {
|
|
126
|
+
JSON.parse(value);
|
|
127
|
+
} catch {
|
|
128
|
+
emitRpcLog(opts, `non-JSON response (marker ${JSON.stringify(marker)})`, {
|
|
129
|
+
responseHead: value.slice(0, RESPONSE_PREVIEW_LENGTH)
|
|
130
|
+
});
|
|
131
|
+
return { ok: false, reason: "non-json" };
|
|
132
|
+
}
|
|
133
|
+
return { ok: true, value };
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// src/extensions/ask-user/marker.ts
|
|
137
|
+
var ASK_USER_MARKER = "\0TAIJI_ASK_USER";
|
|
138
|
+
|
|
139
|
+
// src/extensions/ask-user/helpers.ts
|
|
140
|
+
async function askUserInteract(ctx, questions, options) {
|
|
141
|
+
if (questions.length === 0) return {};
|
|
142
|
+
if (isGuiCapable(ctx) && ctx.ui?.select) {
|
|
143
|
+
const payload = JSON.stringify(stripUndefined({
|
|
144
|
+
questions,
|
|
145
|
+
allowCancel: options?.allowCancel ?? true
|
|
146
|
+
}));
|
|
147
|
+
const value = await ctx.ui.select(
|
|
148
|
+
ASK_USER_MARKER,
|
|
149
|
+
// title = marker,runtime/前端据此识别
|
|
150
|
+
[payload],
|
|
151
|
+
// options[0] = JSON payload(runtime 解析)
|
|
152
|
+
{ signal: options?.signal }
|
|
153
|
+
);
|
|
154
|
+
if (value === void 0) return null;
|
|
155
|
+
try {
|
|
156
|
+
return JSON.parse(value);
|
|
157
|
+
} catch {
|
|
158
|
+
return null;
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
throw new Error(
|
|
162
|
+
"askUserInteract() is only available in RPC mode. In TUI mode, use ctx.ui.custom() with your own Component directly."
|
|
163
|
+
);
|
|
164
|
+
}
|
|
165
|
+
function askUserKey(question) {
|
|
166
|
+
return question.header ?? question.question;
|
|
167
|
+
}
|
|
168
|
+
function getAskUserAnswer(answers, question) {
|
|
169
|
+
const key = askUserKey(question);
|
|
170
|
+
const raw = answers[key];
|
|
171
|
+
if (raw === void 0) return void 0;
|
|
172
|
+
if (question.multiSelect) {
|
|
173
|
+
try {
|
|
174
|
+
const parsed = JSON.parse(raw);
|
|
175
|
+
return Array.isArray(parsed) ? parsed : [raw];
|
|
176
|
+
} catch {
|
|
177
|
+
return [raw];
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
return raw;
|
|
181
|
+
}
|
|
182
|
+
function getAskUserOther(answers, question) {
|
|
183
|
+
return answers[`${askUserKey(question)}__other`];
|
|
184
|
+
}
|
|
185
|
+
function isAskUserQuestion(value) {
|
|
186
|
+
if (typeof value !== "object" || value === null) return false;
|
|
187
|
+
const q = value;
|
|
188
|
+
return typeof q.question === "string" && (q.header === void 0 || typeof q.header === "string") && (q.options === void 0 || Array.isArray(q.options)) && (q.multiSelect === void 0 || typeof q.multiSelect === "boolean") && (q.allowOther === void 0 || typeof q.allowOther === "boolean");
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
// src/extensions/session-manager/types.ts
|
|
192
|
+
function isRecord(v) {
|
|
193
|
+
return typeof v === "object" && v !== null && !Array.isArray(v);
|
|
194
|
+
}
|
|
195
|
+
function isOptionalString(v) {
|
|
196
|
+
return v === void 0 || typeof v === "string";
|
|
197
|
+
}
|
|
198
|
+
function isSessionIdParams(v) {
|
|
199
|
+
return isRecord(v) && typeof v.sessionId === "string";
|
|
200
|
+
}
|
|
201
|
+
function isSessionManagerCreateParams(v) {
|
|
202
|
+
return isRecord(v) && isOptionalString(v.cwd) && isOptionalString(v.label) && isOptionalString(v.prompt);
|
|
203
|
+
}
|
|
204
|
+
function isSessionManagerSendParams(v) {
|
|
205
|
+
return isRecord(v) && typeof v.sessionId === "string" && typeof v.prompt === "string";
|
|
206
|
+
}
|
|
207
|
+
function isSessionManagerHistoryParams(v) {
|
|
208
|
+
return isRecord(v) && typeof v.sessionId === "string" && (v.tailTurns === void 0 || typeof v.tailTurns === "number");
|
|
209
|
+
}
|
|
210
|
+
function isSessionManagerStatusParams(v) {
|
|
211
|
+
return isSessionIdParams(v);
|
|
212
|
+
}
|
|
213
|
+
function isSessionManagerAbortParams(v) {
|
|
214
|
+
return isSessionIdParams(v);
|
|
215
|
+
}
|
|
216
|
+
function isSessionManagerListParams(v) {
|
|
217
|
+
return isRecord(v);
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
// src/extensions/session-manager/marker.ts
|
|
221
|
+
var SESSION_MANAGER_MARKER = "\0TAIJI_SESSION_MANAGER";
|
|
222
|
+
var SESSION_MANAGER_ACTIONS = ["create", "send", "history", "status", "list", "abort"];
|
|
223
|
+
|
|
224
|
+
// src/extensions/subagent-inflight/marker.ts
|
|
225
|
+
var SUBAGENT_INFLIGHT_MARKER = "\0TAIJI_SUBAGENT_INFLIGHT";
|
|
226
|
+
|
|
227
|
+
// src/extensions/subagent-inflight/types.ts
|
|
228
|
+
var INFLIGHT_REPORT_ACK = '{"ack":true}';
|
|
229
|
+
function isRecord2(v) {
|
|
230
|
+
return typeof v === "object" && v !== null && !Array.isArray(v);
|
|
231
|
+
}
|
|
232
|
+
function isSubagentInFlightReport(v) {
|
|
233
|
+
if (!isRecord2(v)) return false;
|
|
234
|
+
if (typeof v.inFlight !== "number" || !Number.isInteger(v.inFlight) || v.inFlight < 0) return false;
|
|
235
|
+
if (typeof v.emittedAt !== "number") return false;
|
|
236
|
+
if (v.sessionId !== void 0 && typeof v.sessionId !== "string") return false;
|
|
237
|
+
return true;
|
|
238
|
+
}
|
|
239
|
+
function isInFlightReportAck(v) {
|
|
240
|
+
return v === INFLIGHT_REPORT_ACK;
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
// src/extensions/plugin-bridge/marker.ts
|
|
244
|
+
var BRIDGE_MARKER = "\0TAIJI_BRIDGE";
|
|
245
|
+
var BRIDGE_METHODS = ["bridge:sync", "bridge:tool_execute", "bridge:event", "bridge:intercept"];
|
|
246
|
+
|
|
247
|
+
// src/extensions/plugin-bridge/guards.ts
|
|
248
|
+
function isRecord3(v) {
|
|
249
|
+
return typeof v === "object" && v !== null && !Array.isArray(v);
|
|
250
|
+
}
|
|
251
|
+
function isBridgeErrorResponse(v) {
|
|
252
|
+
return isChannelErrorResult(v);
|
|
253
|
+
}
|
|
254
|
+
function isBridgeToolExecuteResponse(v) {
|
|
255
|
+
return isRecord3(v) && typeof v.content === "string" && (v.isError === void 0 || typeof v.isError === "boolean");
|
|
256
|
+
}
|
|
257
|
+
function isBridgeSyncPayload(v) {
|
|
258
|
+
return isRecord3(v) && v.success === true && Array.isArray(v.tools);
|
|
259
|
+
}
|
|
260
|
+
function isBridgeInterceptResponse(v) {
|
|
261
|
+
return isRecord3(v) && Array.isArray(v.injectedMessages);
|
|
262
|
+
}
|
|
263
|
+
function isSyncedTool(v) {
|
|
264
|
+
return isRecord3(v) && typeof v.name === "string" && typeof v.description === "string" && isRecord3(v.parameters) && v.parameters.type === "object";
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
// src/extensions/subagent-engine/contract.ts
|
|
268
|
+
var SUBAGENTS_ENGINES_FILENAME = "engines.json";
|
|
269
|
+
|
|
270
|
+
// src/pending-entries.ts
|
|
271
|
+
var PENDING_REGISTER_ENTRY_TYPE = "pending:register";
|
|
272
|
+
var PENDING_UNREGISTER_ENTRY_TYPE = "pending:unregister";
|
|
273
|
+
function scanPendingEntries(entries) {
|
|
274
|
+
const scan = { registerEntries: [], unregisteredIds: /* @__PURE__ */ new Set() };
|
|
275
|
+
for (const raw of entries) {
|
|
276
|
+
if (!raw || typeof raw !== "object") continue;
|
|
277
|
+
const entry = raw;
|
|
278
|
+
if (entry.customType === PENDING_REGISTER_ENTRY_TYPE) {
|
|
279
|
+
scan.registerEntries.push({ data: entry.data ?? {} });
|
|
280
|
+
} else if (entry.customType === PENDING_UNREGISTER_ENTRY_TYPE) {
|
|
281
|
+
const data = entry.data ?? {};
|
|
282
|
+
if (typeof data.id === "string") scan.unregisteredIds.add(data.id);
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
return scan;
|
|
286
|
+
}
|
|
287
|
+
function applyPendingDiff(scan) {
|
|
288
|
+
const active = [];
|
|
289
|
+
const seen = /* @__PURE__ */ new Set();
|
|
290
|
+
for (const { data } of scan.registerEntries) {
|
|
291
|
+
if (typeof data.id !== "string" || scan.unregisteredIds.has(data.id) || seen.has(data.id)) continue;
|
|
292
|
+
seen.add(data.id);
|
|
293
|
+
active.push({ data });
|
|
294
|
+
}
|
|
295
|
+
return active;
|
|
296
|
+
}
|
|
297
|
+
function collectActivePendingIds(entries, opts) {
|
|
298
|
+
const active = applyPendingDiff(scanPendingEntries(entries));
|
|
299
|
+
const prefix = opts?.idPrefix;
|
|
300
|
+
const ids = /* @__PURE__ */ new Set();
|
|
301
|
+
for (const { data } of active) {
|
|
302
|
+
const id = data.id;
|
|
303
|
+
if (typeof id !== "string") continue;
|
|
304
|
+
if (prefix !== void 0 && !id.startsWith(prefix)) continue;
|
|
305
|
+
ids.add(id);
|
|
306
|
+
}
|
|
307
|
+
return ids;
|
|
308
|
+
}
|
|
309
|
+
function mapReasonToStatus(reason) {
|
|
310
|
+
switch (reason) {
|
|
311
|
+
case "completed":
|
|
312
|
+
return "completed";
|
|
313
|
+
case "failed":
|
|
314
|
+
return "failed";
|
|
315
|
+
case "cancelled":
|
|
316
|
+
return "cancelled";
|
|
317
|
+
case "expired":
|
|
318
|
+
return "expired";
|
|
319
|
+
case "time_limited":
|
|
320
|
+
return "time_limited";
|
|
321
|
+
case "budget_limited":
|
|
322
|
+
return "failed";
|
|
323
|
+
case "aborted":
|
|
324
|
+
return "aborted";
|
|
325
|
+
case "interrupted":
|
|
326
|
+
case "interrupted-by-restart":
|
|
327
|
+
case "interrupted-by-parent":
|
|
328
|
+
return "aborted";
|
|
329
|
+
case "reopened":
|
|
330
|
+
return "completed";
|
|
331
|
+
default:
|
|
332
|
+
return "completed";
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
export {
|
|
336
|
+
ASK_USER_MARKER,
|
|
337
|
+
BACKGROUND_TASK_ID_PREFIX,
|
|
338
|
+
BACKGROUND_TASK_REGISTRY_FILENAME,
|
|
339
|
+
BACKGROUND_TASK_REGISTRY_VERSION,
|
|
340
|
+
BASE_TOOL_ENHANCE_DIRNAME,
|
|
341
|
+
BRIDGE_MARKER,
|
|
342
|
+
BRIDGE_METHODS,
|
|
343
|
+
GUI_WIDGET_MARKER,
|
|
344
|
+
INFLIGHT_REPORT_ACK,
|
|
345
|
+
MAX_TERMINAL_REGISTRY_ENTRIES,
|
|
346
|
+
PENDING_REGISTER_ENTRY_TYPE,
|
|
347
|
+
PENDING_UNREGISTER_ENTRY_TYPE,
|
|
348
|
+
PROTOCOL_VERSION,
|
|
349
|
+
SESSION_MANAGER_ACTIONS,
|
|
350
|
+
SESSION_MANAGER_MARKER,
|
|
351
|
+
SUBAGENTS_ENGINES_FILENAME,
|
|
352
|
+
SUBAGENT_INFLIGHT_MARKER,
|
|
353
|
+
applyPendingDiff,
|
|
354
|
+
askUserInteract,
|
|
355
|
+
callMarkerRpc,
|
|
356
|
+
collectActivePendingIds,
|
|
357
|
+
extractGui,
|
|
358
|
+
firstContentText,
|
|
359
|
+
formatChannelErrorText,
|
|
360
|
+
getAskUserAnswer,
|
|
361
|
+
getAskUserOther,
|
|
362
|
+
guiComponent,
|
|
363
|
+
guiResult,
|
|
364
|
+
guiSetWidget,
|
|
365
|
+
isActiveBackgroundTaskState,
|
|
366
|
+
isAskUserQuestion,
|
|
367
|
+
isBackgroundTaskRegistryEntry,
|
|
368
|
+
isBridgeErrorResponse,
|
|
369
|
+
isBridgeInterceptResponse,
|
|
370
|
+
isBridgeSyncPayload,
|
|
371
|
+
isBridgeToolExecuteResponse,
|
|
372
|
+
isChannelErrorResult,
|
|
373
|
+
isGuiCapable,
|
|
374
|
+
isGuiComponent,
|
|
375
|
+
isGuiRenderResult,
|
|
376
|
+
isInFlightReportAck,
|
|
377
|
+
isSessionManagerAbortParams,
|
|
378
|
+
isSessionManagerCreateParams,
|
|
379
|
+
isSessionManagerHistoryParams,
|
|
380
|
+
isSessionManagerListParams,
|
|
381
|
+
isSessionManagerSendParams,
|
|
382
|
+
isSessionManagerStatusParams,
|
|
383
|
+
isSubagentInFlightReport,
|
|
384
|
+
isSyncedTool,
|
|
385
|
+
isTerminalBackgroundTaskState,
|
|
386
|
+
mapReasonToStatus,
|
|
387
|
+
scanPendingEntries,
|
|
388
|
+
setWidgetDual
|
|
389
|
+
};
|
package/package.json
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@zhushanwen/extension-protocol",
|
|
3
|
+
"version": "0.11.0",
|
|
4
|
+
"description": "Cross-layer pi extension contract package: dual-mode (TUI/GUI) rendering helpers, session/background-task/pending-entries protocols, and shared behavior primitives",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "dist/index.js",
|
|
7
|
+
"module": "dist/index.js",
|
|
8
|
+
"types": "dist/index.d.ts",
|
|
9
|
+
"exports": {
|
|
10
|
+
".": {
|
|
11
|
+
"types": "./dist/index.d.ts",
|
|
12
|
+
"import": "./dist/index.js"
|
|
13
|
+
},
|
|
14
|
+
"./background-task": {
|
|
15
|
+
"types": "./dist/background-task-entry.d.ts",
|
|
16
|
+
"import": "./dist/background-task-entry.js"
|
|
17
|
+
}
|
|
18
|
+
},
|
|
19
|
+
"files": [
|
|
20
|
+
"dist",
|
|
21
|
+
"README.md"
|
|
22
|
+
],
|
|
23
|
+
"license": "MIT",
|
|
24
|
+
"devDependencies": {
|
|
25
|
+
"@vitest/coverage-v8": "^4.1.9",
|
|
26
|
+
"tsup": "^8.5.1",
|
|
27
|
+
"typescript": "^5.7.0",
|
|
28
|
+
"vitest": "^4.1.6"
|
|
29
|
+
},
|
|
30
|
+
"scripts": {
|
|
31
|
+
"build": "tsup",
|
|
32
|
+
"typecheck": "tsc --noEmit",
|
|
33
|
+
"test": "vitest run"
|
|
34
|
+
}
|
|
35
|
+
}
|