@magpie-community/opencode-trae-auth 0.2.21 → 0.2.23
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 +63 -19
- package/index.mjs +100 -32
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -231,7 +231,9 @@ which levels it takes — its `reasoning_effort_config`, with the options
|
|
|
231
231
|
(`light`, `high`, `extra_high`) and the default. The request takes the same
|
|
232
232
|
names as `reasoning_effort`, so a level an agent picks is sent as Trae
|
|
233
233
|
spells it: the plugin offers each model's own levels as `variants`
|
|
234
|
-
(`low`/`high`/`xhigh` for `light`/`high`/`extra_high`
|
|
234
|
+
(`low`/`high`/`xhigh` for `light`/`high`/`extra_high`; `medium` and `max`
|
|
235
|
+
keep their names, offered only where the entry names them, as for DeepSeek
|
|
236
|
+
V4.1 Flash's `max` in Trae's app), and sends nothing
|
|
235
237
|
for a level the model doesn't name, or for a model whose entry names none —
|
|
236
238
|
Trae then serves it at its own default. An entry naming levels this map
|
|
237
239
|
can't spell offers none, rather than being sent one Trae would reject. A
|
|
@@ -354,12 +356,15 @@ an empty answer.
|
|
|
354
356
|
Trae CN gives credits for a daily check-in (每日签到); Trae Global has no
|
|
355
357
|
such page, so this is CN only. magpie can press it
|
|
356
358
|
once a day for each account (Settings, or the switch on the usage card),
|
|
357
|
-
through `auth.checkin`: `claimed` when
|
|
358
|
-
|
|
359
|
+
through `auth.checkin`: `claimed` when the status says today's is in after
|
|
360
|
+
the claim, `done` when it said so before it, `inactive` when the page is
|
|
359
361
|
disabled, `ineligible` when the claim answers 9095 (this device has
|
|
360
|
-
checked in today) or 9074 (
|
|
361
|
-
|
|
362
|
-
|
|
362
|
+
checked in today) or 9074 (Trae did not take it for this account's
|
|
363
|
+
device) or answers code 0 while the read-back still says the day is out,
|
|
364
|
+
and `failed` for a turned-away sign-in or any answer that got nowhere,
|
|
365
|
+
which is tried again later that day. An `ineligible` is the day's answer:
|
|
366
|
+
magpie asks that account no more until tomorrow, and only reads its
|
|
367
|
+
status.
|
|
363
368
|
It asks Trae CN's own pages through
|
|
364
369
|
this plugin's fetch, which sends them as the account, as Trae CN's IDE
|
|
365
370
|
(3.3.104) sends them: its Cloud-IDE-JWT (renewed first when near its
|
|
@@ -390,10 +395,27 @@ answered it 9074 even with the client's headers.
|
|
|
390
395
|
plugin's, on its own device, got 9074 (yetone/magpie#808, #1920); that
|
|
391
396
|
this header is what changes it hasn't been tried on a real account
|
|
392
397
|
yet. Every other account keeps its own device, and a file that can't be
|
|
393
|
-
read or made out, or holds no single device, changes nothing.
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
398
|
+
read or made out, or holds no single device, changes nothing.
|
|
399
|
+
- **Every profile is read (0.2.21):** a machine can hold more than one
|
|
400
|
+
Trae CN client profile — the IDE's and TRAE SOLO CN's each write their
|
|
401
|
+
own `storage.json`, and each names the account that profile is signed in
|
|
402
|
+
as, with that account's device. So every profile is read and the one
|
|
403
|
+
signed in as the account answers with its device, which is how two
|
|
404
|
+
accounts can each check in on a real client's device. Before, only the
|
|
405
|
+
first profile was read and it decided for all of them: a second
|
|
406
|
+
account's own profile was never seen, and one profile signed in as
|
|
407
|
+
someone else (or one file that didn't parse) hid every profile after it.
|
|
408
|
+
A profile that isn't signed in as the account, or that names no single
|
|
409
|
+
device, is passed over rather than ending the search. Files: macOS
|
|
410
|
+
`~/Library/Application Support/Trae CN/…` and `…/TRAE SOLO CN/…`, Windows
|
|
411
|
+
`%APPDATA%\Trae CN\…`, `…\TRAE SOLO CN\…` (or `trae-cn`), Linux
|
|
412
|
+
`~/.config/trae-cn/…`, `…/Trae CN/…`, `…/TRAE SOLO CN/…`. The IDE's
|
|
413
|
+
folder has been seen in a real install (macOS and Windows); TRAE SOLO
|
|
414
|
+
CN's name and its `iCubeAuthInfo://` keys are read as the same client
|
|
415
|
+
of the same realm writes them, which hasn't been checked against real
|
|
416
|
+
files — harmless if they differ, since a folder that isn't there, or a
|
|
417
|
+
key that isn't named, is simply not read. A client started with its own
|
|
418
|
+
`--user-data-dir` is not found: there is no list of those to read.
|
|
397
419
|
- **Every other account's device (0.2.16):** its own device id, in the
|
|
398
420
|
client's shape. Trae CN answered a claim on the plugin's 19-digit id
|
|
399
421
|
9074, while the same account and token on a 16-digit id were taken
|
|
@@ -404,15 +426,37 @@ answered it 9074 even with the client's headers.
|
|
|
404
426
|
keep the 19-digit device it signed in with.
|
|
405
427
|
- **What comes back:** Trae's answer as it is. A 401 or code 1001 marks
|
|
406
428
|
the account for a new sign-in. Code 9095 means this device has checked
|
|
407
|
-
in today, and that holds for the day.
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
429
|
+
in today, and that holds for the day. Code 9074 means Trae did not take
|
|
430
|
+
the claim for this account's device: its own words say the pool is busy,
|
|
431
|
+
which two measurements do not bear out — the same account, token and
|
|
432
|
+
headers were taken on a 16-digit id where a 19-digit one got 9074
|
|
433
|
+
(plugins#194), and on 2026-10-23 the same account and headers were taken
|
|
434
|
+
on a device id Trae had never seen while this account's own id answered
|
|
435
|
+
9074. The message says so, and does not send people to Trae CN's app,
|
|
436
|
+
which is not where the problem is. Signing the account in to a Trae CN
|
|
437
|
+
client on this machine is the one thing seen to change a 9074, but it
|
|
438
|
+
hasn't been tried on a real account, so it isn't named in the message;
|
|
439
|
+
nor is it named when the claim already rode a client's device, where it
|
|
440
|
+
would only tell the user to do what they did. No device id is ever
|
|
441
|
+
changed or rotated to get round a 9095 or a 9074, and no account is
|
|
442
|
+
given another account's device or its own client's device unless a
|
|
443
|
+
client is signed in as that account.
|
|
444
|
+
- **A claim's code 0 is not the answer (0.2.22):** Trae answers a
|
|
445
|
+
claim code 0 for one that registered nothing, so the status is read back
|
|
446
|
+
after every claim and only a day that is in is reported as `claimed`.
|
|
447
|
+
Since 0.2.22 one that isn't is an `ineligible` — the day's answer, not a
|
|
448
|
+
`failed` to be tried again: a `failed` is retried with backoff for the
|
|
449
|
+
rest of the day and each retry is another claim, which for an account
|
|
450
|
+
Trae keeps answering code 0 would turn one claim a day into many, and a
|
|
451
|
+
status that lags a claim that did land would read as not in once and
|
|
452
|
+
claim again. Nothing is claimed again that day; the status alone is
|
|
453
|
+
read, so a lag that catches up is still reported as `done`. The credit a
|
|
454
|
+
claim granted is its `credits`, or its `extra_credits` when it names
|
|
455
|
+
only that; Trae CN's claim usually names neither
|
|
456
|
+
(`{"code":0,"message":"success"}`), and then the read-back's `credits`
|
|
457
|
+
is the credit. The two are never added: the status carries
|
|
458
|
+
`extra_credits` beside `credits` (100 and 100 on a real account) while
|
|
459
|
+
the day gives 100.
|
|
416
460
|
|
|
417
461
|
Only `api.trae.cn`'s `/trae/api/` pages are sent this way; any other URL
|
|
418
462
|
that isn't a chat request is still refused (400). Needs 0.1.4 or later.
|
package/index.mjs
CHANGED
|
@@ -826,9 +826,16 @@ const ownClient = {
|
|
|
826
826
|
paths: () => clientStoragePaths(process.platform, homedir(), process.env),
|
|
827
827
|
}
|
|
828
828
|
|
|
829
|
+
// CLIENT_PROFILES are the folder names a Trae CN client profile is kept
|
|
830
|
+
// under: the IDE's own, and TRAE SOLO CN's, which is a client of the same
|
|
831
|
+
// realm and signs in its own account. A profile started with its own
|
|
832
|
+
// --user-data-dir is found only if it is one of these; there is no list of
|
|
833
|
+
// them to read, and nothing is guessed from a folder's contents.
|
|
834
|
+
const CLIENT_PROFILES = ["Trae CN", "TRAE SOLO CN", "trae-cn"]
|
|
835
|
+
|
|
829
836
|
function clientStoragePaths(platform, home, env) {
|
|
830
837
|
const file = (root, name) => join(root, name, "User", "globalStorage", "storage.json")
|
|
831
|
-
if (platform === "darwin") return
|
|
838
|
+
if (platform === "darwin") return CLIENT_PROFILES.filter((n) => n !== "trae-cn").map((n) => file(join(home, "Library", "Application Support"), n))
|
|
832
839
|
if (platform === "win32") {
|
|
833
840
|
// the two roots are often the same folder: APPDATA and the home's own
|
|
834
841
|
// AppData\Roaming. They are compared as one system spells them, so a
|
|
@@ -840,19 +847,28 @@ function clientStoragePaths(platform, home, env) {
|
|
|
840
847
|
if (roots.some((was) => spell(was) === spell(r))) continue
|
|
841
848
|
roots.push(r)
|
|
842
849
|
}
|
|
843
|
-
return roots.flatMap((r) =>
|
|
850
|
+
return roots.flatMap((r) => CLIENT_PROFILES.map((n) => file(r, n)))
|
|
844
851
|
}
|
|
845
|
-
if (platform === "linux") return
|
|
852
|
+
if (platform === "linux") return CLIENT_PROFILES.map((n) => file(env.XDG_CONFIG_HOME || join(home, ".config"), n))
|
|
846
853
|
return []
|
|
847
854
|
}
|
|
848
855
|
|
|
849
|
-
// clientDevice is the device id of
|
|
850
|
-
//
|
|
851
|
-
//
|
|
852
|
-
//
|
|
853
|
-
//
|
|
854
|
-
//
|
|
855
|
-
//
|
|
856
|
+
// clientDevice is the device id of a Trae CN client profile on this machine
|
|
857
|
+
// that is signed in as uid, else undefined. It only reads: nothing of the
|
|
858
|
+
// client's is written, kept or sent but that id, and the client's own token
|
|
859
|
+
// is read past, never used. Never an id made up or changed: an account no
|
|
860
|
+
// profile is signed in as never gets a client's id.
|
|
861
|
+
//
|
|
862
|
+
// A machine can hold more than one profile — Trae CN and TRAE SOLO CN each
|
|
863
|
+
// write one, and a client started with its own --user-data-dir writes
|
|
864
|
+
// another — and each profile's storage.json names the account it is signed
|
|
865
|
+
// in as, with that account's device. So every profile is read and the one
|
|
866
|
+
// signed in as uid answers with its device, which is what lets two accounts
|
|
867
|
+
// each check in on a real client's device. A profile that isn't signed in
|
|
868
|
+
// as uid, that can't be read or made out, or that names no single device
|
|
869
|
+
// is passed over rather than ending the search: before, the first profile
|
|
870
|
+
// read decided for all of them, so a second account's own profile was never
|
|
871
|
+
// seen (and one unreadable file hid every profile after it).
|
|
856
872
|
async function clientDevice(uid) {
|
|
857
873
|
if (!uid) return undefined
|
|
858
874
|
for (const p of ownClient.paths()) {
|
|
@@ -862,14 +878,19 @@ async function clientDevice(uid) {
|
|
|
862
878
|
} catch {
|
|
863
879
|
continue // not installed here
|
|
864
880
|
}
|
|
881
|
+
let doc
|
|
882
|
+
try {
|
|
883
|
+
doc = JSON.parse(text)
|
|
884
|
+
} catch {
|
|
885
|
+
continue // not a file we can make out: the next profile may be
|
|
886
|
+
}
|
|
865
887
|
try {
|
|
866
|
-
const doc = JSON.parse(text)
|
|
867
888
|
const ids = Object.keys(doc ?? {}).filter((k) => k.startsWith(DC_KEY)).map((k) => k.slice(DC_KEY.length))
|
|
868
|
-
if (ids.length !== 1 || !/^\d{8,20}$/.test(ids[0]))
|
|
889
|
+
if (ids.length !== 1 || !/^\d{8,20}$/.test(ids[0])) continue
|
|
869
890
|
const who = clientAuth(doc[AUTH_KEY])?.userId
|
|
870
|
-
|
|
891
|
+
if (who != null && String(who) === String(uid)) return ids[0]
|
|
871
892
|
} catch {
|
|
872
|
-
|
|
893
|
+
continue // a sign-in that doesn't decode: the next profile may be
|
|
873
894
|
}
|
|
874
895
|
}
|
|
875
896
|
return undefined
|
|
@@ -2572,13 +2593,16 @@ const MAX = "-max"
|
|
|
2572
2593
|
// (light, high, extra_high) and the one it defaults to. The request takes the
|
|
2573
2594
|
// same names, as reasoning_effort, so a level an agent picks is sent as Trae
|
|
2574
2595
|
// spells it. OpenCode knows the three as low/high/xhigh, so the map goes both
|
|
2575
|
-
// ways.
|
|
2596
|
+
// ways. Trae's client names two more, medium and max (its bundle's
|
|
2597
|
+
// reasoning_effort.{low,medium,high,extra_high,max}), handed out per model:
|
|
2598
|
+
// its app offers DeepSeek V4.1 Flash max (#434), so they are spelled here too,
|
|
2599
|
+
// and still offered only for a model whose entry names them.
|
|
2576
2600
|
//
|
|
2577
2601
|
// An entry without the config takes no level: Trae serves it at its own
|
|
2578
2602
|
// default and nothing is sent. The plugin used to send none for every model,
|
|
2579
2603
|
// which read as "Trae takes no level" — it takes them, per model, and an
|
|
2580
2604
|
// entry that names none is the exception rather than the rule.
|
|
2581
|
-
const LEVELS = { light: "low", high: "high", extra_high: "xhigh" }
|
|
2605
|
+
const LEVELS = { light: "low", medium: "medium", high: "high", extra_high: "xhigh", max: "max" }
|
|
2582
2606
|
|
|
2583
2607
|
// levelsOf is an entry's levels in OpenCode's names, in Trae's order: [] when
|
|
2584
2608
|
// the entry names none, null when it names ones this map can't spell (so a
|
|
@@ -2874,34 +2898,78 @@ const makePlugin = (site) => async ({ client }) => {
|
|
|
2874
2898
|
return new Response(text, { status: res.status, headers })
|
|
2875
2899
|
}
|
|
2876
2900
|
|
|
2877
|
-
//
|
|
2878
|
-
//
|
|
2879
|
-
//
|
|
2901
|
+
// checkinStatus reads the check-in's status page: its body, or an outcome
|
|
2902
|
+
// to give back as it is (a turned-away sign-in, Trae's own error, a reply
|
|
2903
|
+
// that isn't one). checkin reads it before the claim, and again after it.
|
|
2904
|
+
const checkinStatus = async (getAuth, a0) => {
|
|
2905
|
+
const res = await ownRequest(getAuth, a0, site.hosts.auth + site.checkin.status.path, "POST", "{}")
|
|
2906
|
+
const text = await res.text()
|
|
2907
|
+
let body
|
|
2908
|
+
try { body = JSON.parse(text) } catch {}
|
|
2909
|
+
if (!body || typeof body !== "object" || Array.isArray(body)) return { outcome: { outcome: "failed", message: `check-in status: unreadable reply ${text.trim().slice(0, 120) || "(empty)"}` } }
|
|
2910
|
+
const e = errorOf(body)
|
|
2911
|
+
if (res.status === 401 || lapsedCode(e.code, site)) return { outcome: { outcome: "failed", message: e.message || `${a0.name || a0.uid}: Trae's sign-in has expired` } }
|
|
2912
|
+
if (res.status !== 200 || (e.code && String(e.code) !== "0")) return { outcome: { outcome: "failed", message: e.message || statusLine(res.status) } }
|
|
2913
|
+
return { body }
|
|
2914
|
+
}
|
|
2915
|
+
|
|
2880
2916
|
const checkin = async (getAuth) => {
|
|
2881
2917
|
if (!site.checkin) return { outcome: "inactive", message: `${site.realm} has no daily check-in` }
|
|
2882
2918
|
const a0 = fromAuth(await getAuth(), site)
|
|
2883
2919
|
if (!a0) return { outcome: "failed", message: "not signed in" }
|
|
2884
|
-
const
|
|
2885
|
-
|
|
2886
|
-
const
|
|
2887
|
-
|
|
2888
|
-
|
|
2889
|
-
if (!body || typeof body !== "object" || Array.isArray(body)) return { outcome: "failed", message: `check-in status: unreadable reply ${statusText.trim().slice(0, 120) || "(empty)"}` }
|
|
2890
|
-
const e = errorOf(body)
|
|
2891
|
-
if (status.status === 401 || lapsedCode(e.code, site)) return { outcome: "failed", message: e.message || `${a0.name || a0.uid}: Trae's sign-in has expired` }
|
|
2892
|
-
if (status.status !== 200 || (e.code && String(e.code) !== "0")) return { outcome: "failed", message: e.message || statusLine(status.status) }
|
|
2893
|
-
if (body?.checked_in) return { outcome: "done", ...(Number(body.credits) > 0 ? { credit: Number(body.credits) } : {}) }
|
|
2894
|
-
if (!body?.enable) return { outcome: "inactive" }
|
|
2920
|
+
const first = await checkinStatus(getAuth, a0)
|
|
2921
|
+
if (first.outcome) return first.outcome
|
|
2922
|
+
const body = first.body
|
|
2923
|
+
if (body.checked_in) return { outcome: "done", ...(Number(body.credits) > 0 ? { credit: Number(body.credits) } : {}) }
|
|
2924
|
+
if (!body.enable) return { outcome: "inactive" }
|
|
2895
2925
|
const claim = await ownRequest(getAuth, a0, site.hosts.auth + site.checkin.claim.path, "POST", "{}")
|
|
2896
2926
|
const claimText = await claim.text()
|
|
2897
2927
|
let answer
|
|
2898
2928
|
try { answer = JSON.parse(claimText) } catch {}
|
|
2899
2929
|
const ce = errorOf(answer)
|
|
2900
2930
|
if (claim.status === 401 || lapsedCode(ce.code, site)) return { outcome: "failed", message: ce.message || `${a0.name || a0.uid}: Trae's sign-in has expired` }
|
|
2931
|
+
// 9095: this device is in for today, whoever took it — the day's answer
|
|
2901
2932
|
if (String(ce.code) === "9095") return { outcome: "ineligible", message: ce.message || "this device has checked in today" }
|
|
2902
|
-
|
|
2933
|
+
// 9074: Trae did not take this claim for this account's device. Its own
|
|
2934
|
+
// words say the pool is busy, which measurements do not bear out: the
|
|
2935
|
+
// same account, token and headers were taken on a 16-digit id where a
|
|
2936
|
+
// 19-digit one got 9074 (plugins#194), and on 2026-10-23 the same
|
|
2937
|
+
// account and headers were taken on a device id Trae had never seen
|
|
2938
|
+
// while this account's own id answered 9074. So the message says what
|
|
2939
|
+
// Trae said and that the device is what it refused, rather than
|
|
2940
|
+
// sending people to check in in Trae CN's app, which is not where the
|
|
2941
|
+
// problem is. No device id is made up or rotated to get round this:
|
|
2942
|
+
// the account keeps its own until a client of its own is here to sign
|
|
2943
|
+
// it in. Nothing is said of signing one in: it is the one thing seen
|
|
2944
|
+
// to change a 9074, but it hasn't been tried on a real account, and
|
|
2945
|
+
// on a claim that already rode a client's device it is what the user
|
|
2946
|
+
// did — the advice would only send them back to do it again.
|
|
2947
|
+
if (String(ce.code) === "9074") return { outcome: "ineligible", message: `${ce.message} (9074) — Trae CN did not take this check-in for this account's device` }
|
|
2903
2948
|
if (claim.status !== 200 || (ce.code && String(ce.code) !== "0")) return { outcome: "failed", message: ce.message || statusLine(claim.status) }
|
|
2904
2949
|
if (!answer || typeof answer !== "object" || Array.isArray(answer)) return { outcome: "failed", message: `check-in claim: unreadable reply ${claimText.trim().slice(0, 120) || "(empty)"}` }
|
|
2950
|
+
// a claim's own code 0 is not proof the day is in: Trae answers it for
|
|
2951
|
+
// a claim that registered nothing, which cli2api works around by asking
|
|
2952
|
+
// the status again (internal/providers/trae/checkin.go). So success is
|
|
2953
|
+
// what the status page says after the claim, and one that didn't land
|
|
2954
|
+
// is never reported as claimed.
|
|
2955
|
+
//
|
|
2956
|
+
// Nor is it a `failed`. magpie retries a failed check-in with backoff
|
|
2957
|
+
// for the rest of the day, up to every 30 minutes, and each retry is
|
|
2958
|
+
// another claim: a claim answered code 0 while the status still says
|
|
2959
|
+
// the day is out is the very case that would multiply, since code 0
|
|
2960
|
+
// for a claim that registered nothing is this plugin's own premise —
|
|
2961
|
+
// an account that claimed once a day would claim many times. A status
|
|
2962
|
+
// that lags a claim that did land reads as not in once, which is a
|
|
2963
|
+
// false failure and a second claim. So once a claim has been sent,
|
|
2964
|
+
// what the read-back says is the day's answer and nothing is claimed
|
|
2965
|
+
// again: `ineligible`, as a 9095 is. A read-back that itself comes to
|
|
2966
|
+
// nothing — the network, an answer that isn't one — is the same: the
|
|
2967
|
+
// claim was made, and whether the day is in is not known, so it is
|
|
2968
|
+
// read again on the next press rather than claimed again.
|
|
2969
|
+
const noMore = (o) => (o.outcome === "failed" ? { outcome: "ineligible", message: `${o.message} — nothing is claimed again today` } : o)
|
|
2970
|
+
const back = await checkinStatus(getAuth, a0)
|
|
2971
|
+
if (back.outcome) return noMore(back.outcome)
|
|
2972
|
+
if (!back.body.checked_in) return { outcome: "ineligible", message: `${site.realm}: the claim answered success, but the day's check-in is not in — nothing is claimed again today` }
|
|
2905
2973
|
// The credit is the day's credits, never credits plus extra_credits.
|
|
2906
2974
|
// Trae CN's real claim answers {"code":0,"message":"success"}, with no
|
|
2907
2975
|
// credit at all (plugins#194), so the status's credits stand in. Its
|
|
@@ -2912,7 +2980,7 @@ const makePlugin = (site) => async ({ client }) => {
|
|
|
2912
2980
|
// taken first; one naming extra_credits alone is that (#260). Either
|
|
2913
2981
|
// may be absent, and Number(undefined) is NaN, which reads as none.
|
|
2914
2982
|
const n = (v) => (Number.isFinite(Number(v)) ? Number(v) : 0)
|
|
2915
|
-
const credit = n(answer.credits) || n(answer.extra_credits) || n(body.credits)
|
|
2983
|
+
const credit = n(answer.credits) || n(answer.extra_credits) || n(back.body.credits)
|
|
2916
2984
|
return { outcome: "claimed", ...(credit > 0 ? { credit } : {}) }
|
|
2917
2985
|
}
|
|
2918
2986
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@magpie-community/opencode-trae-auth",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.23",
|
|
4
4
|
"description": "OpenCode provider plugins for Trae: Trae CN (trae.cn) and Trae Global (trae.ai), free tier included, with Trae's own browser sign-in",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./index.mjs",
|