@llblab/pi-codex-usage 0.11.0 → 0.12.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/AGENTS.md +7 -4
- package/BACKLOG.md +2 -1
- package/CHANGELOG.md +7 -0
- package/README.md +48 -3
- package/index.ts +8 -1807
- package/lib/extension.ts +25 -0
- package/lib/fast.ts +23 -0
- package/lib/query.ts +368 -0
- package/lib/status-format.ts +347 -0
- package/lib/status.ts +435 -0
- package/lib/telegram.ts +45 -0
- package/lib/usage-store.ts +229 -0
- package/lib/usage.ts +425 -0
- package/package.json +11 -6
package/lib/usage.ts
ADDED
|
@@ -0,0 +1,425 @@
|
|
|
1
|
+
/** Domain: quota report. Owns: normalization, parsing and selection. Excludes: provider I/O and display. */
|
|
2
|
+
import type { ExtensionContext } from "@earendil-works/pi-coding-agent";
|
|
3
|
+
const SECOND_MS = 1000;
|
|
4
|
+
const CODEX_USAGE_LIMIT_ID = "codex";
|
|
5
|
+
type PiModel = NonNullable<ExtensionContext["model"]>;
|
|
6
|
+
export type CodexUsageModel = Pick<PiModel, "id" | "name" | "provider">;
|
|
7
|
+
export function isOpenAICodexModel(model: Pick<PiModel, "provider"> | undefined): boolean {
|
|
8
|
+
return model?.provider === "openai-codex";
|
|
9
|
+
}
|
|
10
|
+
export type UsageSource = "pi-auth" | "codex-app-server";
|
|
11
|
+
export type UsageQueryError = { source: UsageSource; message: string; cause?: unknown };
|
|
12
|
+
export type CodexUsageReport = {
|
|
13
|
+
snapshots: NormalizedRateLimitSnapshot[];
|
|
14
|
+
credits?: NormalizedCreditUsage;
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
export type NormalizedCreditUsage = {
|
|
18
|
+
remainingPercent: number;
|
|
19
|
+
resetAt?: number;
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
export type NormalizedRateLimitSnapshot = {
|
|
23
|
+
limitId: string;
|
|
24
|
+
primary?: NormalizedRateLimitWindow;
|
|
25
|
+
secondary?: NormalizedRateLimitWindow;
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
export type NormalizedRateLimitWindow = {
|
|
29
|
+
usedPercent: number;
|
|
30
|
+
resetAt?: number;
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
export type RateLimitStatusPayload = {
|
|
34
|
+
rate_limit?: unknown;
|
|
35
|
+
additional_rate_limits?: unknown;
|
|
36
|
+
credits?: unknown;
|
|
37
|
+
spend_control?: unknown;
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
type BackendRateLimitDetails = {
|
|
41
|
+
primary_window?: unknown;
|
|
42
|
+
secondary_window?: unknown;
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
type BackendWindowSnapshot = {
|
|
46
|
+
used_percent?: unknown;
|
|
47
|
+
reset_at?: unknown;
|
|
48
|
+
resets_at?: unknown;
|
|
49
|
+
reset_time?: unknown;
|
|
50
|
+
end_time?: unknown;
|
|
51
|
+
ends_at?: unknown;
|
|
52
|
+
expires_at?: unknown;
|
|
53
|
+
reset_after_seconds?: unknown;
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
export type AppServerRateLimitResponse = {
|
|
57
|
+
rateLimits?: unknown;
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
type AppServerRateLimitSnapshot = {
|
|
61
|
+
limitId?: unknown;
|
|
62
|
+
primary?: unknown;
|
|
63
|
+
secondary?: unknown;
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
type AppServerWindowSnapshot = {
|
|
67
|
+
usedPercent?: unknown;
|
|
68
|
+
resetAt?: unknown;
|
|
69
|
+
resetsAt?: unknown;
|
|
70
|
+
resetTime?: unknown;
|
|
71
|
+
endTime?: unknown;
|
|
72
|
+
endsAt?: unknown;
|
|
73
|
+
expiresAt?: unknown;
|
|
74
|
+
resetAfterSeconds?: unknown;
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
export function normalizeBackendPayload(
|
|
79
|
+
payload: RateLimitStatusPayload,
|
|
80
|
+
_capturedAt: number,
|
|
81
|
+
_source: UsageSource,
|
|
82
|
+
): CodexUsageReport {
|
|
83
|
+
const snapshots: NormalizedRateLimitSnapshot[] = [];
|
|
84
|
+
const primarySnapshot = normalizeBackendSnapshot(
|
|
85
|
+
CODEX_USAGE_LIMIT_ID,
|
|
86
|
+
payload.rate_limit,
|
|
87
|
+
_capturedAt,
|
|
88
|
+
);
|
|
89
|
+
if (primarySnapshot) snapshots.push(primarySnapshot);
|
|
90
|
+
|
|
91
|
+
const credits = normalizeBackendCredits(payload, _capturedAt);
|
|
92
|
+
if (snapshots.length === 0 && !credits) {
|
|
93
|
+
throw new Error(
|
|
94
|
+
"Codex usage endpoint returned no displayable rate-limit windows or credits.",
|
|
95
|
+
);
|
|
96
|
+
}
|
|
97
|
+
return credits ? { snapshots, credits } : { snapshots };
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
function normalizeBackendCredits(
|
|
101
|
+
payload: RateLimitStatusPayload,
|
|
102
|
+
capturedAt: number,
|
|
103
|
+
): NormalizedCreditUsage | undefined {
|
|
104
|
+
const spendControl = payload.spend_control as
|
|
105
|
+
| Record<string, unknown>
|
|
106
|
+
| undefined;
|
|
107
|
+
const individualLimit = spendControl?.individual_limit as
|
|
108
|
+
| Record<string, unknown>
|
|
109
|
+
| undefined;
|
|
110
|
+
const credits = payload.credits as Record<string, unknown> | undefined;
|
|
111
|
+
const limit = asNumber(individualLimit?.limit);
|
|
112
|
+
const used = asNumber(individualLimit?.used);
|
|
113
|
+
const remaining = asNumber(individualLimit?.remaining);
|
|
114
|
+
const resetAt = asResetTime(
|
|
115
|
+
[
|
|
116
|
+
individualLimit?.reset_at,
|
|
117
|
+
individualLimit?.resets_at,
|
|
118
|
+
individualLimit?.reset_time,
|
|
119
|
+
spendControl?.reset_at,
|
|
120
|
+
spendControl?.resets_at,
|
|
121
|
+
credits?.reset_at,
|
|
122
|
+
credits?.resets_at,
|
|
123
|
+
],
|
|
124
|
+
individualLimit?.reset_after_seconds ?? spendControl?.reset_after_seconds,
|
|
125
|
+
capturedAt,
|
|
126
|
+
);
|
|
127
|
+
if (
|
|
128
|
+
limit === undefined ||
|
|
129
|
+
used === undefined ||
|
|
130
|
+
remaining === undefined ||
|
|
131
|
+
limit <= 0
|
|
132
|
+
)
|
|
133
|
+
return undefined;
|
|
134
|
+
const remainingPercent = Math.min(
|
|
135
|
+
100,
|
|
136
|
+
Math.max(0, (remaining / limit) * 100),
|
|
137
|
+
);
|
|
138
|
+
return resetAt === undefined
|
|
139
|
+
? { remainingPercent }
|
|
140
|
+
: { remainingPercent, resetAt };
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
function normalizeBackendSnapshot(
|
|
144
|
+
limitId: string,
|
|
145
|
+
rateLimit: unknown,
|
|
146
|
+
capturedAt: number,
|
|
147
|
+
): NormalizedRateLimitSnapshot | undefined {
|
|
148
|
+
if (rateLimit === null || rateLimit === undefined) return undefined;
|
|
149
|
+
const details = assertObject(
|
|
150
|
+
rateLimit,
|
|
151
|
+
"rate limit",
|
|
152
|
+
) as BackendRateLimitDetails;
|
|
153
|
+
const primary = normalizeBackendWindow(details.primary_window, capturedAt);
|
|
154
|
+
const secondary = normalizeBackendWindow(
|
|
155
|
+
details.secondary_window,
|
|
156
|
+
capturedAt,
|
|
157
|
+
);
|
|
158
|
+
if (!primary && !secondary) return undefined;
|
|
159
|
+
return { limitId, primary, secondary };
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
function normalizeBackendWindow(
|
|
163
|
+
value: unknown,
|
|
164
|
+
capturedAt: number,
|
|
165
|
+
): NormalizedRateLimitWindow | undefined {
|
|
166
|
+
if (value === null || value === undefined) return undefined;
|
|
167
|
+
const window = assertObject(
|
|
168
|
+
value,
|
|
169
|
+
"rate-limit window",
|
|
170
|
+
) as BackendWindowSnapshot;
|
|
171
|
+
const usedPercent = asNumber(window.used_percent);
|
|
172
|
+
if (usedPercent === undefined) return undefined;
|
|
173
|
+
const resetAt = asResetTime(
|
|
174
|
+
[
|
|
175
|
+
window.reset_at,
|
|
176
|
+
window.resets_at,
|
|
177
|
+
window.reset_time,
|
|
178
|
+
window.end_time,
|
|
179
|
+
window.ends_at,
|
|
180
|
+
window.expires_at,
|
|
181
|
+
],
|
|
182
|
+
window.reset_after_seconds,
|
|
183
|
+
capturedAt,
|
|
184
|
+
);
|
|
185
|
+
return resetAt === undefined ? { usedPercent } : { usedPercent, resetAt };
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
export function normalizeAppServerResponse(
|
|
189
|
+
response: AppServerRateLimitResponse,
|
|
190
|
+
_capturedAt: number,
|
|
191
|
+
): CodexUsageReport {
|
|
192
|
+
const snapshots: NormalizedRateLimitSnapshot[] = [];
|
|
193
|
+
const addSnapshot = (raw: unknown, fallbackId: string) => {
|
|
194
|
+
const snapshot = normalizeAppServerSnapshot(raw, fallbackId, _capturedAt);
|
|
195
|
+
if (!snapshot) return;
|
|
196
|
+
const existingIndex = snapshots.findIndex(
|
|
197
|
+
(item) => item.limitId === snapshot.limitId,
|
|
198
|
+
);
|
|
199
|
+
if (existingIndex >= 0)
|
|
200
|
+
snapshots[existingIndex] = mergeSnapshot(
|
|
201
|
+
snapshots[existingIndex],
|
|
202
|
+
snapshot,
|
|
203
|
+
);
|
|
204
|
+
else snapshots.push(snapshot);
|
|
205
|
+
};
|
|
206
|
+
|
|
207
|
+
if (Array.isArray(response.rateLimits)) {
|
|
208
|
+
for (const item of response.rateLimits) addSnapshot(item, "codex");
|
|
209
|
+
} else {
|
|
210
|
+
addSnapshot(response.rateLimits, "codex");
|
|
211
|
+
}
|
|
212
|
+
if (snapshots.length === 0) {
|
|
213
|
+
throw new Error(
|
|
214
|
+
"codex app-server returned no displayable rate-limit windows.",
|
|
215
|
+
);
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
return { snapshots };
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
function normalizeAppServerSnapshot(
|
|
222
|
+
raw: unknown,
|
|
223
|
+
fallbackId: string,
|
|
224
|
+
capturedAt: number,
|
|
225
|
+
): NormalizedRateLimitSnapshot | undefined {
|
|
226
|
+
if (raw === null || raw === undefined) return undefined;
|
|
227
|
+
const snapshot = assertObject(
|
|
228
|
+
raw,
|
|
229
|
+
"app-server rate-limit snapshot",
|
|
230
|
+
) as AppServerRateLimitSnapshot;
|
|
231
|
+
const limitId = asString(snapshot.limitId) ?? fallbackId;
|
|
232
|
+
if (normalizedUsageKey(limitId) !== CODEX_USAGE_LIMIT_ID) return undefined;
|
|
233
|
+
const primary = normalizeAppServerWindow(snapshot.primary, capturedAt);
|
|
234
|
+
const secondary = normalizeAppServerWindow(snapshot.secondary, capturedAt);
|
|
235
|
+
if (!primary && !secondary) return undefined;
|
|
236
|
+
return { limitId, primary, secondary };
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
function normalizeAppServerWindow(
|
|
240
|
+
value: unknown,
|
|
241
|
+
capturedAt: number,
|
|
242
|
+
): NormalizedRateLimitWindow | undefined {
|
|
243
|
+
if (value === null || value === undefined) return undefined;
|
|
244
|
+
const window = assertObject(
|
|
245
|
+
value,
|
|
246
|
+
"app-server rate-limit window",
|
|
247
|
+
) as AppServerWindowSnapshot;
|
|
248
|
+
const usedPercent = asNumber(window.usedPercent);
|
|
249
|
+
if (usedPercent === undefined) return undefined;
|
|
250
|
+
const resetAt = asResetTime(
|
|
251
|
+
[
|
|
252
|
+
window.resetAt,
|
|
253
|
+
window.resetsAt,
|
|
254
|
+
window.resetTime,
|
|
255
|
+
window.endTime,
|
|
256
|
+
window.endsAt,
|
|
257
|
+
window.expiresAt,
|
|
258
|
+
],
|
|
259
|
+
window.resetAfterSeconds,
|
|
260
|
+
capturedAt,
|
|
261
|
+
);
|
|
262
|
+
return resetAt === undefined ? { usedPercent } : { usedPercent, resetAt };
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
function mergeSnapshot(
|
|
266
|
+
left: NormalizedRateLimitSnapshot,
|
|
267
|
+
right: NormalizedRateLimitSnapshot,
|
|
268
|
+
): NormalizedRateLimitSnapshot {
|
|
269
|
+
return {
|
|
270
|
+
limitId: right.limitId || left.limitId,
|
|
271
|
+
primary: right.primary ?? left.primary,
|
|
272
|
+
secondary: right.secondary ?? left.secondary,
|
|
273
|
+
};
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
|
|
277
|
+
export function isUsageUnavailable(errors: UsageQueryError[]): boolean {
|
|
278
|
+
return errors.length > 0 && errors.every(isUnavailableError);
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
function isUnavailableError(error: UsageQueryError): boolean {
|
|
282
|
+
const message = error.message.toLowerCase();
|
|
283
|
+
return (
|
|
284
|
+
message.includes("no pi openai codex subscription auth") ||
|
|
285
|
+
message.includes("no displayable rate-limit windows") ||
|
|
286
|
+
message.includes("returned no displayable rate-limit windows") ||
|
|
287
|
+
message.includes("returned 401") ||
|
|
288
|
+
message.includes("returned 403") ||
|
|
289
|
+
message.includes("unauthorized") ||
|
|
290
|
+
message.includes("forbidden") ||
|
|
291
|
+
message.includes("subscription") ||
|
|
292
|
+
message.includes("no active plan") ||
|
|
293
|
+
message.includes("plan unavailable") ||
|
|
294
|
+
message.includes("quota unavailable") ||
|
|
295
|
+
message.includes("rate limits unavailable")
|
|
296
|
+
);
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
function normalizedUsageKey(value: string | undefined): string | undefined {
|
|
300
|
+
const key = value
|
|
301
|
+
?.toLowerCase()
|
|
302
|
+
.replace(/[^a-z0-9]+/g, "-")
|
|
303
|
+
.replace(/^-+|-+$/g, "");
|
|
304
|
+
return key || undefined;
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
|
|
308
|
+
export function canReuseCachedReport(
|
|
309
|
+
report: CodexUsageReport,
|
|
310
|
+
model: CodexUsageModel | undefined,
|
|
311
|
+
): boolean {
|
|
312
|
+
return (
|
|
313
|
+
selectActiveUsageSnapshot(report, model) !== undefined ||
|
|
314
|
+
report.credits !== undefined
|
|
315
|
+
);
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
export function isFullyAvailableReport(
|
|
319
|
+
report: CodexUsageReport,
|
|
320
|
+
model?: CodexUsageModel,
|
|
321
|
+
): boolean {
|
|
322
|
+
const snapshot = selectActiveUsageSnapshot(report, model);
|
|
323
|
+
const windows = [snapshot?.primary, snapshot?.secondary].filter(
|
|
324
|
+
(window): window is NormalizedRateLimitWindow => window !== undefined,
|
|
325
|
+
);
|
|
326
|
+
return (
|
|
327
|
+
windows.length > 0 &&
|
|
328
|
+
windows.every((window) => clampPercent(window.usedPercent) === 0)
|
|
329
|
+
);
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
|
|
333
|
+
export function selectActiveUsageSnapshot(
|
|
334
|
+
report: CodexUsageReport,
|
|
335
|
+
_model: CodexUsageModel | undefined,
|
|
336
|
+
): NormalizedRateLimitSnapshot | undefined {
|
|
337
|
+
return selectUsageSnapshot(report, CODEX_USAGE_LIMIT_ID);
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
export function selectUsageSnapshot(
|
|
341
|
+
report: CodexUsageReport,
|
|
342
|
+
limitId: string,
|
|
343
|
+
): NormalizedRateLimitSnapshot | undefined {
|
|
344
|
+
const normalizedLimitId = normalizedUsageKey(limitId);
|
|
345
|
+
return report.snapshots.find(
|
|
346
|
+
(snapshot) => normalizedUsageKey(snapshot.limitId) === normalizedLimitId,
|
|
347
|
+
);
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
export function clampPercent(value: number): number {
|
|
351
|
+
if (!Number.isFinite(value)) return 0;
|
|
352
|
+
return Math.min(100, Math.max(0, value));
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
|
|
356
|
+
export function parseJsonObject(
|
|
357
|
+
text: string,
|
|
358
|
+
description: string,
|
|
359
|
+
): Record<string, unknown> {
|
|
360
|
+
let parsed: unknown;
|
|
361
|
+
try {
|
|
362
|
+
parsed = JSON.parse(text) as unknown;
|
|
363
|
+
} catch (error) {
|
|
364
|
+
throw new Error(
|
|
365
|
+
`${description} was not valid JSON: ${errorMessage(error)}`,
|
|
366
|
+
);
|
|
367
|
+
}
|
|
368
|
+
return assertObject(parsed, description);
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
export function assertObject(
|
|
372
|
+
value: unknown,
|
|
373
|
+
description: string,
|
|
374
|
+
): Record<string, unknown> {
|
|
375
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
376
|
+
throw new Error(`${description} was not an object.`);
|
|
377
|
+
}
|
|
378
|
+
return value as Record<string, unknown>;
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
function asString(value: unknown): string | undefined {
|
|
382
|
+
return typeof value === "string" ? value : undefined;
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
function asNumber(value: unknown): number | undefined {
|
|
386
|
+
if (typeof value === "number" && Number.isFinite(value)) return value;
|
|
387
|
+
if (typeof value === "string" && value.trim()) {
|
|
388
|
+
const parsed = Number(value);
|
|
389
|
+
return Number.isFinite(parsed) ? parsed : undefined;
|
|
390
|
+
}
|
|
391
|
+
return undefined;
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
function asResetTime(
|
|
395
|
+
absoluteValues: unknown[],
|
|
396
|
+
relativeSeconds: unknown,
|
|
397
|
+
capturedAt: number,
|
|
398
|
+
): number | undefined {
|
|
399
|
+
for (const value of absoluteValues) {
|
|
400
|
+
const timestamp = asTimestampMs(value);
|
|
401
|
+
if (timestamp !== undefined) return timestamp;
|
|
402
|
+
}
|
|
403
|
+
const seconds = asNumber(relativeSeconds);
|
|
404
|
+
if (seconds === undefined || seconds < 0) return undefined;
|
|
405
|
+
return capturedAt + seconds * SECOND_MS;
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
function asTimestampMs(value: unknown): number | undefined {
|
|
409
|
+
if (typeof value === "number" && Number.isFinite(value)) {
|
|
410
|
+
if (value <= 0) return undefined;
|
|
411
|
+
return value < 10_000_000_000 ? value * SECOND_MS : value;
|
|
412
|
+
}
|
|
413
|
+
if (typeof value === "string" && value.trim()) {
|
|
414
|
+
const numeric = Number(value);
|
|
415
|
+
if (Number.isFinite(numeric)) return asTimestampMs(numeric);
|
|
416
|
+
const parsed = Date.parse(value);
|
|
417
|
+
return Number.isFinite(parsed) ? parsed : undefined;
|
|
418
|
+
}
|
|
419
|
+
return undefined;
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
|
|
423
|
+
export function errorMessage(error: unknown): string {
|
|
424
|
+
return error instanceof Error ? error.message : String(error);
|
|
425
|
+
}
|
package/package.json
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@llblab/pi-codex-usage",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.12.0",
|
|
4
4
|
"private": false,
|
|
5
|
-
"description": "
|
|
5
|
+
"description": "Pi extension for Codex subscription usage and an optional Fast toggle",
|
|
6
6
|
"keywords": [
|
|
7
7
|
"pi-package",
|
|
8
8
|
"pi-extension",
|
|
@@ -26,7 +26,7 @@
|
|
|
26
26
|
},
|
|
27
27
|
"scripts": {
|
|
28
28
|
"check": "node --experimental-strip-types -e \"await import('./index.ts'); console.log('pi-codex-usage: extension import ok')\"",
|
|
29
|
-
"test": "node --experimental-strip-types --test
|
|
29
|
+
"test": "node --experimental-strip-types --test tests/*.test.ts",
|
|
30
30
|
"typecheck": "tsc --noEmit",
|
|
31
31
|
"audit": "npm audit --omit=peer",
|
|
32
32
|
"pack:dry": "npm pack --dry-run",
|
|
@@ -34,6 +34,7 @@
|
|
|
34
34
|
},
|
|
35
35
|
"files": [
|
|
36
36
|
"index.ts",
|
|
37
|
+
"lib/",
|
|
37
38
|
"README.md",
|
|
38
39
|
"AGENTS.md",
|
|
39
40
|
"BACKLOG.md",
|
|
@@ -48,13 +49,17 @@
|
|
|
48
49
|
"image": "https://github.com/llblab/pi-codex-usage/raw/main/banner.jpg"
|
|
49
50
|
},
|
|
50
51
|
"peerDependencies": {
|
|
51
|
-
"@earendil-works/pi-agent-core": "
|
|
52
|
-
"@earendil-works/pi-ai": "
|
|
53
|
-
"@earendil-works/pi-coding-agent": "
|
|
52
|
+
"@earendil-works/pi-agent-core": ">=1.0.0",
|
|
53
|
+
"@earendil-works/pi-ai": ">=1.0.0",
|
|
54
|
+
"@earendil-works/pi-coding-agent": ">=1.0.0",
|
|
54
55
|
"@sinclair/typebox": "*"
|
|
55
56
|
},
|
|
56
57
|
"devDependencies": {
|
|
57
58
|
"@types/node": "latest",
|
|
59
|
+
"jsonc-parser": "^3.3.1",
|
|
58
60
|
"typescript": "latest"
|
|
61
|
+
},
|
|
62
|
+
"dependencies": {
|
|
63
|
+
"@llblab/pi-command-fast": "^0.1.0"
|
|
59
64
|
}
|
|
60
65
|
}
|