pi-meta-oauth 0.6.0 → 0.7.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 +25 -2
- package/extensions/meta.ts +135 -11
- package/package.json +5 -5
package/README.md
CHANGED
|
@@ -10,6 +10,7 @@ Meta Model API OAuth for [pi](https://pi.dev).
|
|
|
10
10
|
|
|
11
11
|
- Use Muse Spark models through Pi's `openai-responses` provider (not `/chat/completions` — Muse prompt cache is ~0% there)
|
|
12
12
|
- Send `prompt_cache_retention: "24h"` on Meta Responses requests unless the payload already set a retention
|
|
13
|
+
- Optional, off by default: send the Muse CLI `User-Agent` on direct `muse-spark-1.3-contributor` requests so it accepts reasoning effort `max` (see [Contributor `max`](#contributor-max-opt-in))
|
|
13
14
|
- Device authorization against `https://auth.meta.com`
|
|
14
15
|
- Model API-key minting through `POST https://api.meta.ai/muse-code/key`
|
|
15
16
|
- Dynamic Muse model catalog from `GET https://api.meta.ai/v1/models`
|
|
@@ -40,9 +41,12 @@ Pi displays a device code, opens the Meta authorization flow, and mints a Model
|
|
|
40
41
|
|
|
41
42
|
The access key is re-minted daily.
|
|
42
43
|
|
|
44
|
+
Prefer a static key instead? Set `META_API_KEY` (or `MODEL_API_KEY`) and skip
|
|
45
|
+
`/login meta` entirely — requests use it directly.
|
|
46
|
+
|
|
43
47
|
## Models
|
|
44
48
|
|
|
45
|
-
Fallback models use a 1,048,576-token context window, up to 256K output tokens, image input, and reasoning levels `minimal`, `low`, `medium`, `high`, and `xhigh
|
|
49
|
+
Fallback models use a 1,048,576-token context window, up to 256K output tokens, image input, and reasoning levels `minimal`, `low`, `medium`, `high`, and `xhigh` (`muse-spark-1.3` additionally supports `max`; `muse-spark-1.3-contributor` does too when [opted in](#contributor-max-opt-in)).
|
|
46
50
|
|
|
47
51
|
| id | pricing (input/output/cached) $/M |
|
|
48
52
|
| --- | --- |
|
|
@@ -54,6 +58,25 @@ Fallback models use a 1,048,576-token context window, up to 256K output tokens,
|
|
|
54
58
|
|
|
55
59
|
> **Contributor-model privacy:** discounted contributor models allow Meta to use prompts and completions for product improvement, including training future Meta models. Use a standard model such as `muse-spark-1.3` if you do not want the contributor terms. See [Meta's model documentation](https://dev.meta.ai/docs/models).
|
|
56
60
|
|
|
61
|
+
### Contributor `max` (opt-in)
|
|
62
|
+
|
|
63
|
+
Meta documents reasoning effort `max` for standard-tier `muse-spark-1.3` only. `muse-spark-1.3-contributor` rejects it (HTTP 400) unless the request carries the Muse CLI's `User-Agent` (observed 2026-09-25, same for API-key and `/login meta` credentials). To use it anyway, set `META_MUSE_USER_AGENT=1` (`true` and `yes` also work) in the environment Pi starts from:
|
|
64
|
+
|
|
65
|
+
| OS / shell | Enable permanently |
|
|
66
|
+
| --- | --- |
|
|
67
|
+
| macOS (zsh, the default) | `echo 'export META_MUSE_USER_AGENT=1' >> ~/.zshrc` |
|
|
68
|
+
| Linux (bash) | `echo 'export META_MUSE_USER_AGENT=1' >> ~/.bashrc` |
|
|
69
|
+
| fish (any OS) | `set -Ux META_MUSE_USER_AGENT 1` |
|
|
70
|
+
| Windows (PowerShell) | `[Environment]::SetEnvironmentVariable("META_MUSE_USER_AGENT", "1", "User")` |
|
|
71
|
+
|
|
72
|
+
Then open a **new** terminal and restart Pi. Shells that were already open don't see the change, and long-running terminal apps or multiplexers (e.g. tmux) may need a full restart. On macOS/Linux you can instead run `source ~/.zshrc` (or `~/.bashrc`) in the current shell. For a single run, use `META_MUSE_USER_AGENT=1 pi`. To disable it, remove the line (fish: `set -Ue META_MUSE_USER_AGENT`; Windows: pass `$null` instead of `"1"`) and restart Pi.
|
|
73
|
+
|
|
74
|
+
The header is the same on every OS. The captured string names `linux-x86_64`, but the platform segment doesn't appear to be checked: it was accepted from Windows (2026-09-26), and oh-my-pi sends the same fixed string on every platform.
|
|
75
|
+
|
|
76
|
+
With the flag set, the extension exposes `max` on `muse-spark-1.3-contributor` and sends the captured Muse `User-Agent` on that model's requests to `https://api.meta.ai/v1` only. Other models, proxies and custom `baseUrl`s, and any `User-Agent` you set yourself are left untouched. Without the flag, Contributor `max` is hidden and Pi's own `User-Agent` is sent.
|
|
77
|
+
|
|
78
|
+
> **Warning:** this makes Pi identify as Meta's first-party Muse client. It relies on undocumented server behavior, is not supported by Meta, may stop working without notice, and may conflict with Meta's terms. Enable it only if you accept that risk for your account.
|
|
79
|
+
|
|
57
80
|
To scope Pi's model picker to Meta models:
|
|
58
81
|
|
|
59
82
|
```json
|
|
@@ -88,7 +111,7 @@ bun run typecheck
|
|
|
88
111
|
bun test
|
|
89
112
|
```
|
|
90
113
|
|
|
91
|
-
`bun test` is hermetic unless a Meta credential is already available. The live cache-hit probe makes real billable API calls when a credential resolves: two identical `/v1/responses` calls (asserting `cached_tokens` on the second), plus one 2s retry if that second call misses cache. The credential is resolved, in order, from `PI_META_LIVE_API_KEY`, `META_API_KEY`, `MODEL_API_KEY`, or the minted key from `~/.pi/agent/auth.json` after `/login meta` (skipped if expired). OAuth is enough — you do not need a separate key. Skipped when no valid credential exists (CI):
|
|
114
|
+
`bun test` is hermetic unless a Meta credential is already available. The live cache-hit probe makes real billable API calls when a credential resolves: two identical `/v1/responses` calls (asserting `cached_tokens` on the second), plus one 2s retry if that second call misses cache. The live fingerprint probe makes two tiny Contributor-`max` calls: one without the fingerprint (status logged only) and one with it (asserting HTTP 200). It sends the header directly and does not depend on `META_MUSE_USER_AGENT`. The credential is resolved, in order, from `PI_META_LIVE_API_KEY`, `META_API_KEY`, `MODEL_API_KEY`, or the minted key from `~/.pi/agent/auth.json` after `/login meta` (skipped if expired). OAuth is enough — you do not need a separate key. Skipped when no valid credential exists (CI):
|
|
92
115
|
|
|
93
116
|
```bash
|
|
94
117
|
bun test tests/meta-cache.test.ts
|
package/extensions/meta.ts
CHANGED
|
@@ -16,6 +16,29 @@ export const META_API_BASE_URL = "https://api.meta.ai/v1";
|
|
|
16
16
|
export const META_MODEL_CATALOG_URL = "https://api.meta.ai/v1/models";
|
|
17
17
|
export const META_AUTH_BASE_URL = "https://auth.meta.com";
|
|
18
18
|
export const META_CLIENT_ID = "1031625952748946";
|
|
19
|
+
/**
|
|
20
|
+
* User-Agent captured from the Muse CLI inference entrypoint, kept
|
|
21
|
+
* byte-for-byte (including the linux-x86_64 platform) because it is a
|
|
22
|
+
* captured fingerprint: variants for other platforms are unverified.
|
|
23
|
+
*
|
|
24
|
+
* Sending it on direct Meta Model API requests enables `reasoning.effort:
|
|
25
|
+
* "max"` on muse-spark-1.3-contributor (live-verified 2026-09-25 on
|
|
26
|
+
* login-minted credentials: identical payloads 400 without it and 200
|
|
27
|
+
* with it; API-key parity reported in oh-my-pi#12199). Observed wire
|
|
28
|
+
* behavior, not a permission claim: Meta documents `max` for standard-tier
|
|
29
|
+
* 1.3 only, and the gate may change server-side without notice.
|
|
30
|
+
*
|
|
31
|
+
* Off by default. It impersonates Meta's first-party client, so users opt
|
|
32
|
+
* in with {@link MUSE_USER_AGENT_ENV_VAR}; see {@link isMuseUserAgentEnabled}.
|
|
33
|
+
*/
|
|
34
|
+
export const MUSE_USER_AGENT =
|
|
35
|
+
"muse-build/1.3.0 (non-interactive; linux-x86_64; build ac7280f2aca67769d1455a8847bb502b617d50f6)";
|
|
36
|
+
/** Set to `1`/`true`/`yes` to send {@link MUSE_USER_AGENT} and expose Contributor `max`. */
|
|
37
|
+
export const MUSE_USER_AGENT_ENV_VAR = "META_MUSE_USER_AGENT";
|
|
38
|
+
/** Models whose `max` effort Meta accepts only with the Muse fingerprint. */
|
|
39
|
+
const MUSE_USER_AGENT_MODEL_IDS: ReadonlySet<string> = new Set([
|
|
40
|
+
"muse-spark-1.3-contributor",
|
|
41
|
+
]);
|
|
19
42
|
const META_ENV_VAR = "META_API_KEY";
|
|
20
43
|
|
|
21
44
|
const DEVICE_AUTHORIZATION_URL = `${META_AUTH_BASE_URL}/oidc/device/authorization/`;
|
|
@@ -88,7 +111,7 @@ const FALLBACK_MODELS: MetaProviderModel[] = [
|
|
|
88
111
|
medium: "medium",
|
|
89
112
|
high: "high",
|
|
90
113
|
xhigh: "xhigh",
|
|
91
|
-
max:
|
|
114
|
+
max: "max",
|
|
92
115
|
},
|
|
93
116
|
input: ["text", "image"],
|
|
94
117
|
cost: { input: 1.25, output: 4.25, cacheRead: 0.15, cacheWrite: 0 },
|
|
@@ -107,6 +130,9 @@ const FALLBACK_MODELS: MetaProviderModel[] = [
|
|
|
107
130
|
medium: "medium",
|
|
108
131
|
high: "high",
|
|
109
132
|
xhigh: "xhigh",
|
|
133
|
+
// Accepted only with the Muse User-Agent fingerprint (live-verified
|
|
134
|
+
// 2026-09-25). gateMuseMaxEffort() maps this to "max" when
|
|
135
|
+
// META_MUSE_USER_AGENT opts in; otherwise it stays unexposed.
|
|
110
136
|
max: null,
|
|
111
137
|
},
|
|
112
138
|
input: ["text", "image"],
|
|
@@ -174,6 +200,37 @@ const FALLBACK_MODELS: MetaProviderModel[] = [
|
|
|
174
200
|
},
|
|
175
201
|
];
|
|
176
202
|
|
|
203
|
+
type MetaEnv = Record<string, string | undefined>;
|
|
204
|
+
|
|
205
|
+
/** True when the user opted in to the Muse User-Agent fingerprint. */
|
|
206
|
+
export function isMuseUserAgentEnabled(env: MetaEnv = process.env): boolean {
|
|
207
|
+
const value = env[MUSE_USER_AGENT_ENV_VAR]?.trim().toLowerCase();
|
|
208
|
+
return value === "1" || value === "true" || value === "yes";
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
/**
|
|
212
|
+
* Expose `max` on fingerprint-gated models only when the fingerprint is
|
|
213
|
+
* enabled, so users who have not opted in are never offered a level that
|
|
214
|
+
* 400s. Any other explicit `max` mapping (e.g. a future server-advertised
|
|
215
|
+
* effort name) passes through unchanged.
|
|
216
|
+
*/
|
|
217
|
+
function gateMuseMaxEffort(
|
|
218
|
+
model: MetaProviderModel,
|
|
219
|
+
env: MetaEnv = process.env,
|
|
220
|
+
): MetaProviderModel {
|
|
221
|
+
const map = model.thinkingLevelMap;
|
|
222
|
+
if (!map || !MUSE_USER_AGENT_MODEL_IDS.has(model.id)) return model;
|
|
223
|
+
if (map.max != null && map.max !== "max") return model;
|
|
224
|
+
return {
|
|
225
|
+
...model,
|
|
226
|
+
thinkingLevelMap: { ...map, max: isMuseUserAgentEnabled(env) ? "max" : null },
|
|
227
|
+
};
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
function fallbackModels(): MetaProviderModel[] {
|
|
231
|
+
return FALLBACK_MODELS.map((model) => gateMuseMaxEffort(model));
|
|
232
|
+
}
|
|
233
|
+
|
|
177
234
|
function delay(milliseconds: number): Promise<void> {
|
|
178
235
|
return new Promise((resolve) => setTimeout(resolve, milliseconds));
|
|
179
236
|
}
|
|
@@ -249,8 +306,14 @@ export async function mintMetaApiKey(
|
|
|
249
306
|
const body = (await responseBody(response)) as MintResponse &
|
|
250
307
|
Record<string, unknown>;
|
|
251
308
|
if (!response.ok) {
|
|
309
|
+
const detail = errorDetail(body);
|
|
310
|
+
if (response.status === 401 || response.status === 403) {
|
|
311
|
+
throw new Error(
|
|
312
|
+
`Meta session expired (HTTP ${response.status}); run /login meta again${detail ? `: ${detail}` : ""}`,
|
|
313
|
+
);
|
|
314
|
+
}
|
|
252
315
|
throw new Error(
|
|
253
|
-
`Meta API-key mint failed (HTTP ${response.status})${
|
|
316
|
+
`Meta API-key mint failed (HTTP ${response.status})${detail ? `: ${detail}` : ""}`,
|
|
254
317
|
);
|
|
255
318
|
}
|
|
256
319
|
if (typeof body.api_key !== "string" || !body.api_key) {
|
|
@@ -406,7 +469,8 @@ export function toProviderModels(
|
|
|
406
469
|
if (typeof entry.id !== "string" || !entry.id) return [];
|
|
407
470
|
const metadata = entry.metadata?.["muse-code"];
|
|
408
471
|
if (metadata?.is_hidden) return [];
|
|
409
|
-
|
|
472
|
+
// Gated fallbacks: a server-advertised variants.max still wins below.
|
|
473
|
+
const fallback = fallbackModels().find((model) => model.id === entry.id);
|
|
410
474
|
const catalogName = metadata?.name === entry.id ? undefined : metadata?.name;
|
|
411
475
|
const variants = metadata?.variants ?? {};
|
|
412
476
|
const thinkingLevelMap: NonNullable<MetaProviderModel["thinkingLevelMap"]> = {
|
|
@@ -416,7 +480,7 @@ export function toProviderModels(
|
|
|
416
480
|
medium: variants.medium?.reasoningEffort ?? "medium",
|
|
417
481
|
high: variants.high?.reasoningEffort ?? "high",
|
|
418
482
|
xhigh: variants.xhigh?.reasoningEffort ?? "xhigh",
|
|
419
|
-
max: null,
|
|
483
|
+
max: variants.max?.reasoningEffort ?? fallback?.thinkingLevelMap?.max ?? null,
|
|
420
484
|
};
|
|
421
485
|
return [
|
|
422
486
|
{
|
|
@@ -470,8 +534,10 @@ function providerModelsFromStore(
|
|
|
470
534
|
return (entry?.models ?? []).flatMap((model: Model<Api>) => {
|
|
471
535
|
if (model.provider !== META_PROVIDER_ID || model.api !== "openai-responses")
|
|
472
536
|
return [];
|
|
537
|
+
// Re-gate on restore: a catalog cached while the fingerprint was enabled
|
|
538
|
+
// must not keep offering Contributor `max` after the user opts out.
|
|
473
539
|
return [
|
|
474
|
-
{
|
|
540
|
+
gateMuseMaxEffort({
|
|
475
541
|
id: model.id,
|
|
476
542
|
name: model.name,
|
|
477
543
|
api: model.api,
|
|
@@ -484,7 +550,7 @@ function providerModelsFromStore(
|
|
|
484
550
|
maxTokens: model.maxTokens,
|
|
485
551
|
headers: model.headers,
|
|
486
552
|
compat: model.compat as MetaProviderModel["compat"],
|
|
487
|
-
},
|
|
553
|
+
}),
|
|
488
554
|
];
|
|
489
555
|
});
|
|
490
556
|
}
|
|
@@ -534,7 +600,7 @@ export async function refreshMetaModels(
|
|
|
534
600
|
const compatibleContext = context as unknown as CompatibleRefreshContext;
|
|
535
601
|
if (!context.allowNetwork || context.signal?.aborted) {
|
|
536
602
|
const cached = await cachedMetaModels(compatibleContext);
|
|
537
|
-
return cached.length > 0 ? cached :
|
|
603
|
+
return cached.length > 0 ? cached : fallbackModels();
|
|
538
604
|
}
|
|
539
605
|
const apiKey =
|
|
540
606
|
context.credential?.type === "oauth"
|
|
@@ -544,7 +610,7 @@ export async function refreshMetaModels(
|
|
|
544
610
|
: undefined;
|
|
545
611
|
if (!apiKey) {
|
|
546
612
|
const cached = await cachedMetaModels(compatibleContext);
|
|
547
|
-
return cached.length > 0 ? cached :
|
|
613
|
+
return cached.length > 0 ? cached : fallbackModels();
|
|
548
614
|
}
|
|
549
615
|
|
|
550
616
|
try {
|
|
@@ -566,7 +632,7 @@ export async function refreshMetaModels(
|
|
|
566
632
|
const models = toProviderModels(body);
|
|
567
633
|
if (models.length === 0) {
|
|
568
634
|
const cached = await cachedMetaModels(compatibleContext);
|
|
569
|
-
return cached.length > 0 ? cached :
|
|
635
|
+
return cached.length > 0 ? cached : fallbackModels();
|
|
570
636
|
}
|
|
571
637
|
if (!context.signal?.aborted) {
|
|
572
638
|
try {
|
|
@@ -582,7 +648,7 @@ export async function refreshMetaModels(
|
|
|
582
648
|
} catch (error) {
|
|
583
649
|
if (context.signal?.aborted) throw error;
|
|
584
650
|
const cached = await cachedMetaModels(compatibleContext);
|
|
585
|
-
return cached.length > 0 ? cached :
|
|
651
|
+
return cached.length > 0 ? cached : fallbackModels();
|
|
586
652
|
}
|
|
587
653
|
}
|
|
588
654
|
|
|
@@ -601,6 +667,53 @@ function asRecord(value: unknown): Record<string, unknown> | undefined {
|
|
|
601
667
|
: undefined;
|
|
602
668
|
}
|
|
603
669
|
|
|
670
|
+
/**
|
|
671
|
+
* Strict direct-endpoint check: https scheme, api.meta.ai hostname
|
|
672
|
+
* (case-insensitive), default port, and a bare /v1 path. WHATWG URL parsing
|
|
673
|
+
* normalizes the equivalent forms (uppercase host, explicit :443) to match;
|
|
674
|
+
* proxies, lookalike hosts (api.meta.ai.evil.com), subpaths, and
|
|
675
|
+
* non-default ports never match, so they never receive the fingerprint.
|
|
676
|
+
*/
|
|
677
|
+
export function isDirectMetaModelApiUrl(baseUrl: unknown): boolean {
|
|
678
|
+
if (typeof baseUrl !== "string" || !baseUrl) return false;
|
|
679
|
+
let parsed: URL;
|
|
680
|
+
try {
|
|
681
|
+
parsed = new URL(baseUrl);
|
|
682
|
+
} catch {
|
|
683
|
+
return false;
|
|
684
|
+
}
|
|
685
|
+
if (parsed.protocol !== "https:") return false;
|
|
686
|
+
if (parsed.hostname.toLowerCase() !== "api.meta.ai") return false;
|
|
687
|
+
if (parsed.port !== "") return false;
|
|
688
|
+
return parsed.pathname.replace(/\/+$/, "") === "/v1";
|
|
689
|
+
}
|
|
690
|
+
|
|
691
|
+
function hasUserAgentHeader(
|
|
692
|
+
headers: Record<string, string | null>,
|
|
693
|
+
): boolean {
|
|
694
|
+
return Object.keys(headers).some(
|
|
695
|
+
(name) => name.toLowerCase() === "user-agent",
|
|
696
|
+
);
|
|
697
|
+
}
|
|
698
|
+
|
|
699
|
+
/**
|
|
700
|
+
* Setdefault the Muse fingerprint for direct Meta Model API requests.
|
|
701
|
+
* Explicit User-Agent headers (any casing, including null suppression)
|
|
702
|
+
* always win; anything but the direct endpoint is left untouched.
|
|
703
|
+
* Applies regardless of credential type: API-key and Muse Code login
|
|
704
|
+
* (minted) credentials both reach the same direct wire. Returns true when
|
|
705
|
+
* the fingerprint was applied.
|
|
706
|
+
*/
|
|
707
|
+
export function applyMetaUserAgentFingerprint(
|
|
708
|
+
headers: Record<string, string | null>,
|
|
709
|
+
baseUrl: unknown,
|
|
710
|
+
): boolean {
|
|
711
|
+
if (!isDirectMetaModelApiUrl(baseUrl)) return false;
|
|
712
|
+
if (hasUserAgentHeader(headers)) return false;
|
|
713
|
+
headers["User-Agent"] = MUSE_USER_AGENT;
|
|
714
|
+
return true;
|
|
715
|
+
}
|
|
716
|
+
|
|
604
717
|
/**
|
|
605
718
|
* Hermes-equivalent Responses hints for api.meta.ai:
|
|
606
719
|
* setdefault `prompt_cache_retention: 24h`, and drop `reasoning.effort: none`
|
|
@@ -632,7 +745,7 @@ export function createMetaProviderConfig(): ProviderConfig {
|
|
|
632
745
|
baseUrl: META_API_BASE_URL,
|
|
633
746
|
api: "openai-responses",
|
|
634
747
|
apiKey: "$META_API_KEY",
|
|
635
|
-
models:
|
|
748
|
+
models: fallbackModels(),
|
|
636
749
|
refreshModels: refreshMetaModels,
|
|
637
750
|
oauth: {
|
|
638
751
|
name: "Meta Model API (browser login)",
|
|
@@ -662,4 +775,15 @@ export default function metaOAuthProvider(pi: ExtensionAPI): void {
|
|
|
662
775
|
if (ctx.model?.provider !== META_PROVIDER_ID) return undefined;
|
|
663
776
|
return applyMetaResponsesCacheHints(event.payload);
|
|
664
777
|
});
|
|
778
|
+
pi.on("before_provider_headers", (event, ctx) => {
|
|
779
|
+
const model = ctx.model;
|
|
780
|
+
if (model?.provider !== META_PROVIDER_ID) return;
|
|
781
|
+
// Opt-in only, and only for models that need it: every other Meta
|
|
782
|
+
// request keeps Pi's own User-Agent.
|
|
783
|
+
if (!MUSE_USER_AGENT_MODEL_IDS.has(model.id)) return;
|
|
784
|
+
if (!isMuseUserAgentEnabled()) return;
|
|
785
|
+
// The composed model always carries the effective baseUrl; without
|
|
786
|
+
// it the endpoint cannot be verified, so no fingerprint is sent.
|
|
787
|
+
applyMetaUserAgentFingerprint(event.headers, model.baseUrl);
|
|
788
|
+
});
|
|
665
789
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pi-meta-oauth",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.7.0",
|
|
4
4
|
"description": "Meta Model API OAuth provider for pi",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"pi-package",
|
|
@@ -31,12 +31,12 @@
|
|
|
31
31
|
"image": "https://raw.githubusercontent.com/BlockedPath/pi-meta-oauth/main/preview.png"
|
|
32
32
|
},
|
|
33
33
|
"peerDependencies": {
|
|
34
|
-
"@earendil-works/pi-ai": ">=0.83.0 <0.
|
|
35
|
-
"@earendil-works/pi-coding-agent": ">=0.83.0 <0.
|
|
34
|
+
"@earendil-works/pi-ai": ">=0.83.0 <0.88.0",
|
|
35
|
+
"@earendil-works/pi-coding-agent": ">=0.83.0 <0.88.0"
|
|
36
36
|
},
|
|
37
37
|
"devDependencies": {
|
|
38
|
-
"@earendil-works/pi-ai": "0.
|
|
39
|
-
"@earendil-works/pi-coding-agent": "0.
|
|
38
|
+
"@earendil-works/pi-ai": "0.87.1",
|
|
39
|
+
"@earendil-works/pi-coding-agent": "0.87.1",
|
|
40
40
|
"bun-types": "latest",
|
|
41
41
|
"typescript": "^5.9.0"
|
|
42
42
|
},
|