@jetcrab/pi-desk-usage 0.0.0-stage → 1.0.1-dev.9001
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 +201 -0
- package/README.md +4 -2
- package/dist/browser/chunks/browser-PWPSWHE7.js +12 -0
- package/dist/browser/chunks/browser-session-view-FVGFXMQB.js +4 -0
- package/dist/browser/chunks/chunk-JHHRG2FT.js +1 -0
- package/dist/browser/entry.js +1 -0
- package/dist/l4-usage-aggregate.d.ts +9 -0
- package/dist/l4-usage-aggregate.js +59 -0
- package/dist/l4-usage-calendar.d.ts +31 -0
- package/dist/l4-usage-calendar.js +186 -0
- package/dist/l4-usage-files.d.ts +5 -0
- package/dist/l4-usage-files.js +97 -0
- package/dist/l4-usage-format.d.ts +1 -0
- package/dist/l4-usage-format.js +12 -0
- package/dist/pi-desk.d.ts +2 -0
- package/dist/pi-desk.js +18 -0
- package/dist/protocol.d.ts +189 -0
- package/dist/protocol.js +1 -0
- package/dist/usage-runtime.d.ts +53 -0
- package/dist/usage-runtime.js +816 -0
- package/dist/usage-scan.d.ts +82 -0
- package/dist/usage-scan.js +467 -0
- package/dist/usage-timeline.d.ts +15 -0
- package/dist/usage-timeline.js +342 -0
- package/dist/usage-worker.d.ts +1 -0
- package/dist/usage-worker.js +13 -0
- package/package.json +56 -3
|
@@ -0,0 +1,342 @@
|
|
|
1
|
+
import { usageBucketRanges, usageBucketIndex } from './l4-usage-calendar.js';
|
|
2
|
+
const MAX_DISPLAY_POINTS = 800;
|
|
3
|
+
const MAX_TRACK_ACTIVITIES = 160;
|
|
4
|
+
const MINUTE_MS = 60_000;
|
|
5
|
+
const HOUR_MS = 60 * MINUTE_MS;
|
|
6
|
+
const DAY_MS = 24 * HOUR_MS;
|
|
7
|
+
function emptyTotals() {
|
|
8
|
+
return { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, cost: 0 };
|
|
9
|
+
}
|
|
10
|
+
function addTotals(target, value) {
|
|
11
|
+
target.input += value.input;
|
|
12
|
+
target.output += value.output;
|
|
13
|
+
target.cacheRead += value.cacheRead;
|
|
14
|
+
target.cacheWrite += value.cacheWrite;
|
|
15
|
+
target.cost += value.cost;
|
|
16
|
+
}
|
|
17
|
+
function promptTokens(event) {
|
|
18
|
+
return event.totals.input + event.totals.cacheRead + event.totals.cacheWrite;
|
|
19
|
+
}
|
|
20
|
+
function rate(point) {
|
|
21
|
+
return point.prompt > 0 ? point.cacheRead / point.prompt : 0;
|
|
22
|
+
}
|
|
23
|
+
function pointKey(point) {
|
|
24
|
+
return `${point.at}:${point.prompt}:${point.cacheRead}`;
|
|
25
|
+
}
|
|
26
|
+
function trimToTarget(points, target, mandatoryAt) {
|
|
27
|
+
if (points.length <= target)
|
|
28
|
+
return points;
|
|
29
|
+
const mandatory = points.filter((point, index) => index === 0 || index === points.length - 1 || mandatoryAt.has(point.at));
|
|
30
|
+
const mandatoryKeys = new Set(mandatory.map(pointKey));
|
|
31
|
+
const optional = points.filter((point) => !mandatoryKeys.has(pointKey(point)));
|
|
32
|
+
const remaining = Math.max(0, target - mandatory.length);
|
|
33
|
+
if (remaining === 0)
|
|
34
|
+
return mandatory.sort((left, right) => left.at - right.at);
|
|
35
|
+
const selected = [...mandatory];
|
|
36
|
+
const bucketCount = Math.max(1, Math.floor(remaining / 2));
|
|
37
|
+
const bucketSize = optional.length / bucketCount;
|
|
38
|
+
for (let bucket = 0; bucket < bucketCount; bucket += 1) {
|
|
39
|
+
const start = Math.floor(bucket * bucketSize);
|
|
40
|
+
const end = Math.max(start + 1, Math.floor((bucket + 1) * bucketSize));
|
|
41
|
+
const values = optional.slice(start, end);
|
|
42
|
+
if (values.length === 0)
|
|
43
|
+
continue;
|
|
44
|
+
const promptMin = values.reduce((best, point) => (point.prompt < best.prompt ? point : best));
|
|
45
|
+
const promptMax = values.reduce((best, point) => (point.prompt > best.prompt ? point : best));
|
|
46
|
+
const rateMin = values.reduce((best, point) => (rate(point) < rate(best) ? point : best));
|
|
47
|
+
const rateMax = values.reduce((best, point) => (rate(point) > rate(best) ? point : best));
|
|
48
|
+
for (const point of [promptMin, promptMax, rateMin, rateMax].sort((left, right) => left.at - right.at)) {
|
|
49
|
+
if (selected.length >= target)
|
|
50
|
+
break;
|
|
51
|
+
if (!selected.some((item) => pointKey(item) === pointKey(point)))
|
|
52
|
+
selected.push(point);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
return selected.sort((left, right) => left.at - right.at);
|
|
56
|
+
}
|
|
57
|
+
function chooseBucketMs(duration) {
|
|
58
|
+
if (duration <= 2 * HOUR_MS)
|
|
59
|
+
return MINUTE_MS;
|
|
60
|
+
if (duration <= 10 * HOUR_MS)
|
|
61
|
+
return 5 * MINUTE_MS;
|
|
62
|
+
if (duration <= 30 * HOUR_MS)
|
|
63
|
+
return 15 * MINUTE_MS;
|
|
64
|
+
if (duration <= 60 * HOUR_MS)
|
|
65
|
+
return 30 * MINUTE_MS;
|
|
66
|
+
if (duration <= 5 * DAY_MS)
|
|
67
|
+
return HOUR_MS;
|
|
68
|
+
if (duration <= 15 * DAY_MS)
|
|
69
|
+
return 3 * HOUR_MS;
|
|
70
|
+
if (duration <= 30 * DAY_MS)
|
|
71
|
+
return 6 * HOUR_MS;
|
|
72
|
+
if (duration <= 90 * DAY_MS)
|
|
73
|
+
return 12 * HOUR_MS;
|
|
74
|
+
return DAY_MS;
|
|
75
|
+
}
|
|
76
|
+
function runIndex(runs, timestamp) {
|
|
77
|
+
if (runs.length <= 1)
|
|
78
|
+
return 0;
|
|
79
|
+
let result = 0;
|
|
80
|
+
for (let index = 1; index < runs.length; index += 1) {
|
|
81
|
+
if (timestamp < runs[index].startAt)
|
|
82
|
+
break;
|
|
83
|
+
result = index;
|
|
84
|
+
}
|
|
85
|
+
return result;
|
|
86
|
+
}
|
|
87
|
+
function inRange(timestamp, startAt, endAt) {
|
|
88
|
+
return timestamp >= startAt && timestamp < endAt;
|
|
89
|
+
}
|
|
90
|
+
function intersects(startAt, endAt, rangeStart, rangeEnd) {
|
|
91
|
+
return startAt < rangeEnd && (endAt === null || endAt >= rangeStart);
|
|
92
|
+
}
|
|
93
|
+
function contextStates(inputs) {
|
|
94
|
+
return inputs.map((input) => {
|
|
95
|
+
const calls = input.events
|
|
96
|
+
.filter((event) => event.kind === 'assistant' && promptTokens(event) > 0)
|
|
97
|
+
.map((event) => ({
|
|
98
|
+
timestamp: event.timestamp,
|
|
99
|
+
order: event.order,
|
|
100
|
+
prompt: promptTokens(event),
|
|
101
|
+
cacheRead: event.totals.cacheRead
|
|
102
|
+
}))
|
|
103
|
+
.sort((left, right) => left.timestamp - right.timestamp || left.order - right.order);
|
|
104
|
+
const markers = [...input.markers].sort((left, right) => left.timestamp - right.timestamp || left.order - right.order);
|
|
105
|
+
const segments = Array.from({ length: Math.max(1, input.kind === 'main' ? 1 : input.runs.length) }, () => []);
|
|
106
|
+
const mandatoryAt = new Set();
|
|
107
|
+
for (const call of calls) {
|
|
108
|
+
segments[input.kind === 'main' ? 0 : runIndex(input.runs, call.timestamp)].push({
|
|
109
|
+
at: call.timestamp,
|
|
110
|
+
prompt: call.prompt,
|
|
111
|
+
cacheRead: call.cacheRead
|
|
112
|
+
});
|
|
113
|
+
}
|
|
114
|
+
for (const marker of markers) {
|
|
115
|
+
const previous = [...calls].reverse().find((call) => call.timestamp < marker.timestamp);
|
|
116
|
+
const next = calls.find((call) => call.timestamp > marker.timestamp);
|
|
117
|
+
mandatoryAt.add(marker.timestamp);
|
|
118
|
+
if (previous)
|
|
119
|
+
mandatoryAt.add(previous.timestamp);
|
|
120
|
+
if (next) {
|
|
121
|
+
mandatoryAt.add(next.timestamp);
|
|
122
|
+
const segment = input.kind === 'main' ? 0 : runIndex(input.runs, marker.timestamp);
|
|
123
|
+
segments[segment].push({
|
|
124
|
+
at: marker.timestamp,
|
|
125
|
+
prompt: next.prompt,
|
|
126
|
+
cacheRead: next.cacheRead
|
|
127
|
+
});
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
for (const segment of segments)
|
|
131
|
+
segment.sort((left, right) => left.at - right.at);
|
|
132
|
+
return {
|
|
133
|
+
input,
|
|
134
|
+
calls,
|
|
135
|
+
markers,
|
|
136
|
+
segments: segments.filter((segment) => segment.length > 0),
|
|
137
|
+
mandatoryAt
|
|
138
|
+
};
|
|
139
|
+
});
|
|
140
|
+
}
|
|
141
|
+
function sampledSources(states) {
|
|
142
|
+
const totalPoints = states.reduce((total, state) => total + state.segments.reduce((sum, segment) => sum + segment.length, 0), 0);
|
|
143
|
+
const sampled = totalPoints > MAX_DISPLAY_POINTS;
|
|
144
|
+
const ratio = sampled ? MAX_DISPLAY_POINTS / totalPoints : 1;
|
|
145
|
+
return {
|
|
146
|
+
sampled,
|
|
147
|
+
sources: states.map((state) => {
|
|
148
|
+
const pointCount = state.segments.reduce((sum, segment) => sum + segment.length, 0);
|
|
149
|
+
let remainingTarget = sampled
|
|
150
|
+
? Math.max(state.segments.length * 2, Math.floor(pointCount * ratio))
|
|
151
|
+
: pointCount;
|
|
152
|
+
let remainingPoints = pointCount;
|
|
153
|
+
const segments = state.segments.map((segment, index) => {
|
|
154
|
+
const target = sampled
|
|
155
|
+
? Math.min(segment.length, Math.max(Math.min(segment.length, 2), index === state.segments.length - 1
|
|
156
|
+
? remainingTarget
|
|
157
|
+
: Math.floor((segment.length / Math.max(1, remainingPoints)) * remainingTarget)))
|
|
158
|
+
: segment.length;
|
|
159
|
+
remainingTarget = Math.max(0, remainingTarget - target);
|
|
160
|
+
remainingPoints -= segment.length;
|
|
161
|
+
return trimToTarget(segment, target, state.mandatoryAt);
|
|
162
|
+
});
|
|
163
|
+
return {
|
|
164
|
+
key: state.input.key,
|
|
165
|
+
kind: state.input.kind,
|
|
166
|
+
title: state.input.kind === 'main' ? '主代理' : state.input.title,
|
|
167
|
+
agentType: state.input.agentType,
|
|
168
|
+
calls: state.calls.length,
|
|
169
|
+
segments,
|
|
170
|
+
runs: state.input.runs
|
|
171
|
+
};
|
|
172
|
+
})
|
|
173
|
+
};
|
|
174
|
+
}
|
|
175
|
+
function timelineMarkers(states) {
|
|
176
|
+
return states
|
|
177
|
+
.flatMap((state) => state.markers.map((marker) => {
|
|
178
|
+
const previous = [...state.calls]
|
|
179
|
+
.reverse()
|
|
180
|
+
.find((call) => call.timestamp < marker.timestamp);
|
|
181
|
+
const next = state.calls.find((call) => call.timestamp > marker.timestamp);
|
|
182
|
+
return {
|
|
183
|
+
x: marker.timestamp,
|
|
184
|
+
sourceKey: state.input.key,
|
|
185
|
+
kind: marker.kind,
|
|
186
|
+
timestamp: marker.timestamp,
|
|
187
|
+
mode: marker.mode,
|
|
188
|
+
beforePrompt: previous?.prompt ?? null,
|
|
189
|
+
projectedPrompt: marker.projectedTokens,
|
|
190
|
+
afterPrompt: next?.prompt ?? null,
|
|
191
|
+
tokensBefore: marker.tokensBefore
|
|
192
|
+
};
|
|
193
|
+
}))
|
|
194
|
+
.sort((left, right) => left.timestamp - right.timestamp);
|
|
195
|
+
}
|
|
196
|
+
function createBuckets(inputs, startAt, endAt, bucketMs, timeZone) {
|
|
197
|
+
const buckets = usageBucketRanges(startAt, endAt, bucketMs, timeZone).map((range) => ({
|
|
198
|
+
...range,
|
|
199
|
+
totals: emptyTotals(),
|
|
200
|
+
calls: 0,
|
|
201
|
+
sources: inputs.map((source) => ({ sourceKey: source.key, totals: emptyTotals(), calls: 0 }))
|
|
202
|
+
}));
|
|
203
|
+
const sourceIndexes = new Map(inputs.map((source, index) => [source.key, index]));
|
|
204
|
+
for (const source of inputs) {
|
|
205
|
+
const sourceIndex = sourceIndexes.get(source.key);
|
|
206
|
+
for (const event of source.events) {
|
|
207
|
+
if (!inRange(event.timestamp, startAt, endAt))
|
|
208
|
+
continue;
|
|
209
|
+
const index = usageBucketIndex(buckets, event.timestamp);
|
|
210
|
+
const bucket = buckets[index];
|
|
211
|
+
const sourceBucket = bucket.sources[sourceIndex];
|
|
212
|
+
addTotals(bucket.totals, event.totals);
|
|
213
|
+
addTotals(sourceBucket.totals, event.totals);
|
|
214
|
+
if (event.kind === 'assistant') {
|
|
215
|
+
bucket.calls += 1;
|
|
216
|
+
sourceBucket.calls += 1;
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
return buckets;
|
|
221
|
+
}
|
|
222
|
+
export function createUsageActivities(inputs, startAt, endAt) {
|
|
223
|
+
const activities = [];
|
|
224
|
+
for (const source of inputs) {
|
|
225
|
+
for (const event of source.events) {
|
|
226
|
+
if (!inRange(event.timestamp, startAt, endAt))
|
|
227
|
+
continue;
|
|
228
|
+
activities.push({
|
|
229
|
+
id: event.identity,
|
|
230
|
+
timestamp: event.timestamp,
|
|
231
|
+
endAt: null,
|
|
232
|
+
sourceKey: source.key,
|
|
233
|
+
kind: 'model',
|
|
234
|
+
label: event.label,
|
|
235
|
+
detail: event.kind === 'cache-warm'
|
|
236
|
+
? '缓存保温'
|
|
237
|
+
: event.kind === 'usage'
|
|
238
|
+
? '其他模型调用'
|
|
239
|
+
: event.kind === 'assistant'
|
|
240
|
+
? '模型调用'
|
|
241
|
+
: '工具与摘要用量',
|
|
242
|
+
status: null,
|
|
243
|
+
totals: { ...event.totals }
|
|
244
|
+
});
|
|
245
|
+
}
|
|
246
|
+
for (const tool of source.tools) {
|
|
247
|
+
if (!intersects(tool.timestamp, tool.endAt, startAt, endAt))
|
|
248
|
+
continue;
|
|
249
|
+
activities.push({
|
|
250
|
+
id: tool.identity,
|
|
251
|
+
timestamp: tool.timestamp,
|
|
252
|
+
endAt: tool.endAt,
|
|
253
|
+
sourceKey: source.key,
|
|
254
|
+
kind: 'tool',
|
|
255
|
+
label: tool.toolName,
|
|
256
|
+
detail: tool.reasoning,
|
|
257
|
+
status: tool.status,
|
|
258
|
+
totals: null
|
|
259
|
+
});
|
|
260
|
+
}
|
|
261
|
+
if (source.kind === 'subagent') {
|
|
262
|
+
for (const [index, run] of source.runs.entries()) {
|
|
263
|
+
if (!intersects(run.startAt, run.endAt, startAt, endAt))
|
|
264
|
+
continue;
|
|
265
|
+
activities.push({
|
|
266
|
+
id: `${source.key}:run:${index}`,
|
|
267
|
+
timestamp: run.startAt,
|
|
268
|
+
endAt: run.endAt,
|
|
269
|
+
sourceKey: source.key,
|
|
270
|
+
kind: 'subagent-run',
|
|
271
|
+
label: '子代理运行',
|
|
272
|
+
detail: source.title,
|
|
273
|
+
status: run.endAt === null ? 'running' : 'completed',
|
|
274
|
+
totals: null
|
|
275
|
+
});
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
for (const marker of source.markers) {
|
|
279
|
+
if (!inRange(marker.timestamp, startAt, endAt))
|
|
280
|
+
continue;
|
|
281
|
+
activities.push({
|
|
282
|
+
id: marker.identity,
|
|
283
|
+
timestamp: marker.timestamp,
|
|
284
|
+
endAt: null,
|
|
285
|
+
sourceKey: source.key,
|
|
286
|
+
kind: marker.kind,
|
|
287
|
+
label: marker.kind === 'compaction' ? '上下文压缩' : '上下文忽略',
|
|
288
|
+
detail: null,
|
|
289
|
+
status: null,
|
|
290
|
+
totals: null
|
|
291
|
+
});
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
return activities.sort((left, right) => left.timestamp - right.timestamp || left.id.localeCompare(right.id));
|
|
295
|
+
}
|
|
296
|
+
export function createUsageTimeline(inputs, requestedStartAt, requestedEndAt, timeZone = 'UTC') {
|
|
297
|
+
const observedStarts = inputs.flatMap((source) => [
|
|
298
|
+
...source.events.map((event) => event.timestamp),
|
|
299
|
+
...source.tools.map((tool) => tool.timestamp),
|
|
300
|
+
...source.runs.map((run) => run.startAt)
|
|
301
|
+
]);
|
|
302
|
+
const observedEnds = inputs.flatMap((source) => [
|
|
303
|
+
...source.events.map((event) => event.timestamp),
|
|
304
|
+
...source.tools.flatMap((tool) => (tool.endAt === null ? [] : [tool.endAt])),
|
|
305
|
+
...source.runs.flatMap((run) => (run.endAt === null ? [] : [run.endAt])),
|
|
306
|
+
source.updatedAt
|
|
307
|
+
]);
|
|
308
|
+
const fallback = Date.now();
|
|
309
|
+
const startAt = requestedStartAt ?? Math.min(...observedStarts, fallback);
|
|
310
|
+
const defaultEndAt = observedEnds.length > 0 ? Math.max(...observedEnds) : fallback;
|
|
311
|
+
const endAt = Math.max(startAt + 1, requestedEndAt ?? defaultEndAt);
|
|
312
|
+
const bucketMs = chooseBucketMs(endAt - startAt);
|
|
313
|
+
const states = contextStates(inputs);
|
|
314
|
+
const sampled = sampledSources(states);
|
|
315
|
+
const markers = timelineMarkers(states).filter((marker) => inRange(marker.timestamp, startAt, endAt));
|
|
316
|
+
const allActivities = createUsageActivities(inputs, startAt, endAt);
|
|
317
|
+
const activities = allActivities
|
|
318
|
+
.filter((activity) => {
|
|
319
|
+
if (activity.kind === 'subagent-run')
|
|
320
|
+
return true;
|
|
321
|
+
if (activity.kind !== 'tool')
|
|
322
|
+
return false;
|
|
323
|
+
const end = activity.endAt ?? endAt;
|
|
324
|
+
return end - activity.timestamp >= bucketMs;
|
|
325
|
+
})
|
|
326
|
+
.sort((left, right) => (right.endAt ?? endAt) - right.timestamp - ((left.endAt ?? endAt) - left.timestamp))
|
|
327
|
+
.slice(0, MAX_TRACK_ACTIVITIES)
|
|
328
|
+
.sort((left, right) => left.timestamp - right.timestamp);
|
|
329
|
+
return {
|
|
330
|
+
generatedAt: Date.now(),
|
|
331
|
+
startAt,
|
|
332
|
+
endAt,
|
|
333
|
+
bucketMs,
|
|
334
|
+
totalCalls: inputs.reduce((total, source) => total + source.events.filter((event) => event.kind === 'assistant').length, 0),
|
|
335
|
+
displayedPoints: sampled.sources.reduce((total, source) => total + source.segments.reduce((sum, segment) => sum + segment.length, 0), 0),
|
|
336
|
+
sampled: sampled.sampled,
|
|
337
|
+
buckets: createBuckets(inputs, startAt, endAt, bucketMs, timeZone),
|
|
338
|
+
sources: sampled.sources,
|
|
339
|
+
markers,
|
|
340
|
+
activities
|
|
341
|
+
};
|
|
342
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { parentPort, workerData } from 'node:worker_threads';
|
|
2
|
+
import { scanUsageFiles } from './usage-scan.js';
|
|
3
|
+
const input = workerData;
|
|
4
|
+
if (!parentPort)
|
|
5
|
+
throw new Error('用量 Worker 缺少 parentPort');
|
|
6
|
+
try {
|
|
7
|
+
parentPort.postMessage({ results: await scanUsageFiles(input.files, 2) });
|
|
8
|
+
}
|
|
9
|
+
catch (error) {
|
|
10
|
+
parentPort.postMessage({
|
|
11
|
+
error: error instanceof Error ? error.message : String(error)
|
|
12
|
+
});
|
|
13
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,59 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jetcrab/pi-desk-usage",
|
|
3
|
-
"
|
|
4
|
-
|
|
5
|
-
|
|
3
|
+
"repository": {
|
|
4
|
+
"type": "git",
|
|
5
|
+
"url": "git+https://github.com/JetCrab/pi-desk.git",
|
|
6
|
+
"directory": "plugins/pi-desk-usage"
|
|
7
|
+
},
|
|
8
|
+
"version": "1.0.1-dev.9001",
|
|
9
|
+
"license": "Apache-2.0",
|
|
10
|
+
"publishConfig": {
|
|
11
|
+
"registry": "https://registry.npmjs.org",
|
|
12
|
+
"access": "public"
|
|
13
|
+
},
|
|
14
|
+
"description": "Lazy Pi Desk dashboard for recent model, token, cache, and cost usage",
|
|
15
|
+
"type": "module",
|
|
16
|
+
"exports": {
|
|
17
|
+
".": {
|
|
18
|
+
"types": "./dist/pi-desk.d.ts",
|
|
19
|
+
"import": "./dist/pi-desk.js",
|
|
20
|
+
"default": "./dist/pi-desk.js"
|
|
21
|
+
}
|
|
22
|
+
},
|
|
23
|
+
"types": "./dist/pi-desk.d.ts",
|
|
24
|
+
"files": [
|
|
25
|
+
"dist",
|
|
26
|
+
"README.md"
|
|
27
|
+
],
|
|
28
|
+
"keywords": [
|
|
29
|
+
"pi-package"
|
|
30
|
+
],
|
|
31
|
+
"pi": {
|
|
32
|
+
"extensions": []
|
|
33
|
+
},
|
|
34
|
+
"piDesk": {
|
|
35
|
+
"entry": "./dist/pi-desk.js"
|
|
36
|
+
},
|
|
37
|
+
"dependencies": {
|
|
38
|
+
"zod": "^4.4.3",
|
|
39
|
+
"@jetcrab/pi-desk-sdk": "^1.0.1-dev.9001"
|
|
40
|
+
},
|
|
41
|
+
"peerDependencies": {
|
|
42
|
+
"@earendil-works/pi-coding-agent": "*"
|
|
43
|
+
},
|
|
44
|
+
"devDependencies": {
|
|
45
|
+
"@base-ui/react": "^1.7.0",
|
|
46
|
+
"@types/node": "^20",
|
|
47
|
+
"@types/react": "^19",
|
|
48
|
+
"@types/react-dom": "^19",
|
|
49
|
+
"esbuild": "^0.25.0",
|
|
50
|
+
"react": "19.2.8",
|
|
51
|
+
"react-dom": "19.2.8",
|
|
52
|
+
"tsx": "^4.23.12",
|
|
53
|
+
"typescript": "^5"
|
|
54
|
+
},
|
|
55
|
+
"scripts": {
|
|
56
|
+
"build": "tsc -p tsconfig.json && node -e \"const fs=require('node:fs');fs.rmSync('dist/browser',{recursive:true,force:true});for(const name of fs.readdirSync('dist'))if(name.startsWith('browser')&&['.js','.d.ts','.js.map'].some(ext=>name.endsWith(ext)))fs.rmSync('dist/'+name,{force:true})\" && esbuild src/browser-entry.ts --bundle --splitting --format=esm --platform=browser --target=es2022 --minify --define:process.env.NODE_ENV='production' --external:react --external:react/jsx-runtime --external:react/jsx-dev-runtime --external:react-dom --external:react-dom/client --external:@jetcrab/pi-desk-sdk/react/base --external:@jetcrab/pi-desk-sdk/react/markdown --outdir=dist/browser --entry-names=entry --chunk-names=chunks/[name]-[hash]",
|
|
57
|
+
"test": "node --import tsx --test tests/*.test.ts"
|
|
58
|
+
}
|
|
6
59
|
}
|