dsh-agy-link 0.4.33 → 0.4.35
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/CHANGELOG.md +21 -0
- package/dist/client.js +76 -6
- package/dist/index.js +135 -73
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,26 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.4.35 (2026-09-18)
|
|
4
|
+
|
|
5
|
+
### English
|
|
6
|
+
|
|
7
|
+
- **Reasoning UI: only show thinking when there is real content.** Empty `[agy thinking turn · N thinking tokens]` chips no longer spam tool-heavy turns. When agy stores only `toolAction`/`toolSummary` (common), those become the visible thought line (e.g. `Searching the web — Search Wo Tianyu profile`). Full CoT prose still renders when present in the conversation DB.
|
|
8
|
+
|
|
9
|
+
### 中文 (Chinese)
|
|
10
|
+
|
|
11
|
+
- **思考 UI:** 无正文时不再刷 token 横幅;工具步骤优先展示 `toolAction`/`toolSummary` 作为意图说明;完整思维链仍按 DB 正文展示。
|
|
12
|
+
|
|
13
|
+
## 0.4.34 (2026-09-18)
|
|
14
|
+
|
|
15
|
+
### English
|
|
16
|
+
|
|
17
|
+
- **UI: cleaner tool cards in Code Mode.** When DSH registers `agy_tool`, the bridge now emits native tool-call blocks instead of `run_code` wrappers. Code Mode titles use a human preview (`$ ls · run_command`) instead of `replay agy tool step N`. A `run_code` toolview renders Antigravity cards when the program is still a mirror wrapper.
|
|
18
|
+
- **UI: less thinking spam.** Banner-only `[agy thinking turn · N tokens]` chips (no prose) emit at most once per run; turns that extract thought prose still show full reasoning.
|
|
19
|
+
|
|
20
|
+
### 中文 (Chinese)
|
|
21
|
+
|
|
22
|
+
- **界面:** Code Mode 下工具卡片更干净;有 prose 的思考仍完整展示,无正文的 token 横幅每轮最多一条。
|
|
23
|
+
|
|
3
24
|
## 0.4.33 (2026-09-18)
|
|
4
25
|
|
|
5
26
|
### English
|
package/dist/client.js
CHANGED
|
@@ -664,13 +664,83 @@ window.__ModuleLoader__.load({
|
|
|
664
664
|
inspect
|
|
665
665
|
}))] : []);
|
|
666
666
|
}
|
|
667
|
+
/** Extract the agy mirror cursor (and tool name) from a run_code program. */
|
|
668
|
+
function parseAgyMirrorFromCode(argsRaw) {
|
|
669
|
+
try {
|
|
670
|
+
const parsed = JSON.parse(argsRaw);
|
|
671
|
+
const code = typeof parsed?.code === "string" ? parsed.code : "";
|
|
672
|
+
const m = /tools\['agy_tool'\]\((\{.*?"step":\d+\})\)/.exec(code);
|
|
673
|
+
if (!m) return null;
|
|
674
|
+
const v = JSON.parse(m[1]);
|
|
675
|
+
if (typeof v.run !== "string" || typeof v.step !== "number") return null;
|
|
676
|
+
const tm = /replay recorded agy tool step \d+ \(([^)]+)\)/.exec(code);
|
|
677
|
+
return {
|
|
678
|
+
run: v.run,
|
|
679
|
+
step: v.step,
|
|
680
|
+
tool: tm?.[1]
|
|
681
|
+
};
|
|
682
|
+
} catch {}
|
|
683
|
+
return null;
|
|
684
|
+
}
|
|
685
|
+
/**
|
|
686
|
+
* run_code toolview: when the program is an agy mirror wrapper, render the
|
|
687
|
+
* native Antigravity card instead of a raw code row. Other run_code calls
|
|
688
|
+
* fall through to the host renderer via a minimal wrapper that still shows
|
|
689
|
+
* something useful.
|
|
690
|
+
*/
|
|
691
|
+
function AgyRunCodeToolView(props) {
|
|
692
|
+
const block = props?.block;
|
|
693
|
+
const raw = block !== void 0 ? parsedArgsRaw(block) : "{}";
|
|
694
|
+
const mirror = parseAgyMirrorFromCode(raw);
|
|
695
|
+
if (mirror === null) {
|
|
696
|
+
let code = "";
|
|
697
|
+
try {
|
|
698
|
+
const parsed = JSON.parse(raw);
|
|
699
|
+
code = typeof parsed?.code === "string" ? parsed.code : raw;
|
|
700
|
+
} catch {
|
|
701
|
+
code = raw;
|
|
702
|
+
}
|
|
703
|
+
return hx("div", { className: cls("agy-tv-root") }, hx("div", { className: "agy-tv-header" }, hx("span", { className: "agy-tv-title" }, "run_code"), hx("span", { className: "agy-tv-badge" }, "code")), hx("pre", {
|
|
704
|
+
className: "agy-tv-pre",
|
|
705
|
+
style: {
|
|
706
|
+
margin: 0,
|
|
707
|
+
whiteSpace: "pre-wrap",
|
|
708
|
+
fontSize: "12px"
|
|
709
|
+
}
|
|
710
|
+
}, code.slice(0, 400)));
|
|
711
|
+
}
|
|
712
|
+
return AgyMirrorToolView({ block: {
|
|
713
|
+
...block,
|
|
714
|
+
call: {
|
|
715
|
+
...block?.call ?? {},
|
|
716
|
+
name: "agy_tool",
|
|
717
|
+
argsRaw: JSON.stringify({
|
|
718
|
+
run: mirror.run,
|
|
719
|
+
step: mirror.step,
|
|
720
|
+
...mirror.tool !== void 0 ? { tool: mirror.tool } : {}
|
|
721
|
+
})
|
|
722
|
+
}
|
|
723
|
+
} });
|
|
724
|
+
}
|
|
667
725
|
function installAgyToolView(ctx) {
|
|
668
|
-
ctx.slots.inject("tool.call.toolview", () =>
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
726
|
+
ctx.slots.inject("tool.call.toolview", () => {
|
|
727
|
+
const d1 = ctx.slots.register({
|
|
728
|
+
name: "tool.call.toolview",
|
|
729
|
+
key: "agy_tool",
|
|
730
|
+
id: "agy-tool-view",
|
|
731
|
+
label: "Antigravity tool"
|
|
732
|
+
}, AgyMirrorToolView);
|
|
733
|
+
const d2 = ctx.slots.register({
|
|
734
|
+
name: "tool.call.toolview",
|
|
735
|
+
key: "run_code",
|
|
736
|
+
id: "agy-run-code-view",
|
|
737
|
+
label: "Antigravity (code mode)"
|
|
738
|
+
}, AgyRunCodeToolView);
|
|
739
|
+
return () => {
|
|
740
|
+
d2();
|
|
741
|
+
d1();
|
|
742
|
+
};
|
|
743
|
+
});
|
|
674
744
|
}
|
|
675
745
|
const ROW_CSS = `
|
|
676
746
|
/* Container */
|
package/dist/index.js
CHANGED
|
@@ -650,16 +650,37 @@ function getGitHeadContent(filePath, execFn = execFileSync) {
|
|
|
650
650
|
* whose single statement calls the registered mirror tool — the inner
|
|
651
651
|
* dispatch is what renders the native tool card.
|
|
652
652
|
*/
|
|
653
|
-
function buildMirrorRunCode(runId, eventIndex, toolName) {
|
|
653
|
+
function buildMirrorRunCode(runId, eventIndex, toolName, brief) {
|
|
654
654
|
const invocation = JSON.stringify({
|
|
655
655
|
run: runId,
|
|
656
656
|
step: eventIndex
|
|
657
657
|
});
|
|
658
658
|
return {
|
|
659
659
|
code: "// dsh-agy-link mirror: replay recorded agy tool step " + eventIndex + " (" + toolName + ")\nreturn await tools['agy_tool'](" + invocation + ")",
|
|
660
|
-
description:
|
|
660
|
+
description: brief !== void 0 && brief !== "" ? brief + " · " + toolName : toolName
|
|
661
661
|
};
|
|
662
662
|
}
|
|
663
|
+
/** Short human preview for a recorded tool step (used in Code Mode titles). */
|
|
664
|
+
function toolStepBrief(toolName, args) {
|
|
665
|
+
const a = args !== null && typeof args === "object" ? args : {};
|
|
666
|
+
const pickStr = (...keys) => {
|
|
667
|
+
for (const k of keys) {
|
|
668
|
+
const v = a[k];
|
|
669
|
+
if (typeof v === "string" && v.trim() !== "") return v.trim();
|
|
670
|
+
}
|
|
671
|
+
return "";
|
|
672
|
+
};
|
|
673
|
+
if (toolName === "run_command" || toolName === "execute_command") {
|
|
674
|
+
const cmd = pickStr("Command", "command", "Cmd", "cmd");
|
|
675
|
+
return cmd !== "" ? "$ " + cmd.slice(0, 80) : "run command";
|
|
676
|
+
}
|
|
677
|
+
if (toolName === "view_file" || toolName === "read_file") return "read " + (pickStr("Path", "path", "TargetFile", "File") || "file");
|
|
678
|
+
if (toolName === "write_to_file" || toolName === "create_file") return "write " + (pickStr("Path", "path", "TargetFile", "File") || "file");
|
|
679
|
+
if (toolName === "replace_file_content" || toolName === "edit_file") return "edit " + (pickStr("Path", "path", "TargetFile", "File") || "file");
|
|
680
|
+
if (toolName === "grep_search" || toolName === "search") return "search " + (pickStr("Query", "query", "SearchPattern") || "");
|
|
681
|
+
if (toolName === "list_dir" || toolName === "list_directory") return "ls " + (pickStr("Path", "path", "DirectoryPath") || ".");
|
|
682
|
+
return toolName;
|
|
683
|
+
}
|
|
663
684
|
/** agy serializes some tool args as a JSON string; presenters get an object. */
|
|
664
685
|
function toolInput(args) {
|
|
665
686
|
const raw = args.input;
|
|
@@ -967,6 +988,7 @@ var EventMapper = class {
|
|
|
967
988
|
emittedByKey = /* @__PURE__ */ new Map();
|
|
968
989
|
announcedTools = /* @__PURE__ */ new Set();
|
|
969
990
|
thinkingAnnounced = /* @__PURE__ */ new Set();
|
|
991
|
+
bannerOnlyThinkingEmitted = false;
|
|
970
992
|
sawTextStep;
|
|
971
993
|
finished = false;
|
|
972
994
|
constructor(opts) {
|
|
@@ -1029,18 +1051,11 @@ var EventMapper = class {
|
|
|
1029
1051
|
*/
|
|
1030
1052
|
*emitThinking(absIndex, thoughtTokens) {
|
|
1031
1053
|
const text = this.opts.resolvedThoughts?.get(absIndex);
|
|
1032
|
-
|
|
1033
|
-
if (!hasText && thoughtTokens <= 0) return;
|
|
1054
|
+
if (!(text !== void 0 && text.trim() !== "")) return;
|
|
1034
1055
|
yield* this.ensureBlock("reasoning");
|
|
1035
|
-
const
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
const d = this.appendDelta(combined);
|
|
1039
|
-
if (d) yield d;
|
|
1040
|
-
} else {
|
|
1041
|
-
const d = this.appendDelta(`${banner}\n`);
|
|
1042
|
-
if (d) yield d;
|
|
1043
|
-
}
|
|
1056
|
+
const combined = `${thoughtTokens > 0 ? "[agy thinking turn · " + thoughtTokens + " thinking tokens]" : "[agy thinking turn]"} ${text.trim()}\n`;
|
|
1057
|
+
const d = this.appendDelta(combined);
|
|
1058
|
+
if (d) yield d;
|
|
1044
1059
|
}
|
|
1045
1060
|
/**
|
|
1046
1061
|
* Map one event. `absIndex` is the event's position in the run recording;
|
|
@@ -1125,7 +1140,7 @@ var EventMapper = class {
|
|
|
1125
1140
|
...fullArgs,
|
|
1126
1141
|
...typeof ev.tool.args === "object" ? ev.tool.args : {}
|
|
1127
1142
|
} : ev.tool.args;
|
|
1128
|
-
const argumentsJson = useCode ? JSON.stringify(buildMirrorRunCode(this.opts.runId, absIndex, ev.tool.name)) : JSON.stringify({
|
|
1143
|
+
const argumentsJson = useCode ? JSON.stringify(buildMirrorRunCode(this.opts.runId, absIndex, ev.tool.name, toolStepBrief(ev.tool.name, effectiveArgs))) : JSON.stringify({
|
|
1129
1144
|
run: this.opts.runId,
|
|
1130
1145
|
step: absIndex,
|
|
1131
1146
|
tool: ev.tool.name,
|
|
@@ -2307,65 +2322,111 @@ function readVarint(b, offset) {
|
|
|
2307
2322
|
/**
|
|
2308
2323
|
* Extract model thought text from a protobuf step_payload.
|
|
2309
2324
|
*
|
|
2310
|
-
*
|
|
2311
|
-
*
|
|
2312
|
-
*
|
|
2325
|
+
* Primary: field 20 -> sub-field 3 (verified on simple print-mode turns).
|
|
2326
|
+
* Fallback: longest non-JSON prose string, then toolAction/toolSummary
|
|
2327
|
+
* embedded in tool-arg JSON (tool-heavy agent turns often store only those).
|
|
2313
2328
|
*/
|
|
2314
2329
|
function extractStepThoughts(payload) {
|
|
2315
|
-
|
|
2316
|
-
|
|
2317
|
-
|
|
2318
|
-
|
|
2319
|
-
|
|
2320
|
-
|
|
2321
|
-
|
|
2322
|
-
|
|
2323
|
-
if (
|
|
2324
|
-
|
|
2325
|
-
|
|
2326
|
-
|
|
2327
|
-
const
|
|
2328
|
-
if (
|
|
2329
|
-
|
|
2330
|
-
|
|
2331
|
-
|
|
2332
|
-
|
|
2333
|
-
|
|
2334
|
-
|
|
2335
|
-
|
|
2336
|
-
|
|
2337
|
-
|
|
2338
|
-
|
|
2339
|
-
|
|
2340
|
-
|
|
2341
|
-
|
|
2342
|
-
|
|
2343
|
-
|
|
2344
|
-
|
|
2345
|
-
|
|
2346
|
-
|
|
2347
|
-
|
|
2348
|
-
|
|
2349
|
-
|
|
2350
|
-
|
|
2351
|
-
|
|
2352
|
-
|
|
2353
|
-
|
|
2354
|
-
|
|
2355
|
-
|
|
2356
|
-
|
|
2357
|
-
|
|
2358
|
-
|
|
2330
|
+
const proseCandidates = [];
|
|
2331
|
+
const actionCandidates = [];
|
|
2332
|
+
const consider = (str) => {
|
|
2333
|
+
const t = str.trim();
|
|
2334
|
+
if (t.length < 8) return;
|
|
2335
|
+
if (/^SYSTEM ROLE/i.test(t)) return;
|
|
2336
|
+
if (t.startsWith("/plan ") || t.includes("<PLAN>The user is requesting that you think")) return;
|
|
2337
|
+
if (/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(t)) return;
|
|
2338
|
+
if (/^bot-|^call_/.test(t)) return;
|
|
2339
|
+
const act = /"toolAction"\s*:\s*"([^"]{3,200})"/.exec(t);
|
|
2340
|
+
const sum = /"toolSummary"\s*:\s*"([^"]{3,200})"/.exec(t);
|
|
2341
|
+
if (act || sum) {
|
|
2342
|
+
const line = [act?.[1] ?? "", sum?.[1] ?? ""].filter((s) => s !== "").join(" — ");
|
|
2343
|
+
if (line !== "") actionCandidates.push(line);
|
|
2344
|
+
}
|
|
2345
|
+
const looksJson = t.startsWith("{") || t.startsWith("[");
|
|
2346
|
+
if (!looksJson && t.length >= 24 && t.length <= 4e3 && /[a-zA-Z一-鿿]/.test(t) && !/^[0-9a-f-]{30,}$/i.test(t)) {
|
|
2347
|
+
if ((t.match(/[.!?]|,|。| /g) ?? []).length >= 2 || /[a-zA-Z]{4,}\s+[a-zA-Z]{3,}/.test(t)) proseCandidates.push(t);
|
|
2348
|
+
return;
|
|
2349
|
+
}
|
|
2350
|
+
if (looksJson) try {
|
|
2351
|
+
const obj = JSON.parse(t);
|
|
2352
|
+
const line = [typeof obj.toolAction === "string" ? obj.toolAction.trim() : "", typeof obj.toolSummary === "string" ? obj.toolSummary.trim() : ""].filter((s) => s !== "").join(" — ");
|
|
2353
|
+
if (line !== "") actionCandidates.push(line);
|
|
2354
|
+
} catch {}
|
|
2355
|
+
};
|
|
2356
|
+
const walk = (buf, depth) => {
|
|
2357
|
+
if (depth > 3) return;
|
|
2358
|
+
let pos = 0;
|
|
2359
|
+
while (pos < buf.length) {
|
|
2360
|
+
const res = readVarint(buf, pos);
|
|
2361
|
+
if (res === null) break;
|
|
2362
|
+
pos = res.next;
|
|
2363
|
+
const tag = res.val;
|
|
2364
|
+
const wireType = tag & 7;
|
|
2365
|
+
const fieldNum = tag >>> 3;
|
|
2366
|
+
if (wireType === 2) {
|
|
2367
|
+
const lenRes = readVarint(buf, pos);
|
|
2368
|
+
if (lenRes === null) break;
|
|
2369
|
+
pos = lenRes.next;
|
|
2370
|
+
const len = lenRes.val;
|
|
2371
|
+
if (pos + len > buf.length) break;
|
|
2372
|
+
const slice = buf.subarray(pos, pos + len);
|
|
2373
|
+
pos += len;
|
|
2374
|
+
if (fieldNum === 20) {
|
|
2375
|
+
let p20 = 0;
|
|
2376
|
+
while (p20 < slice.length) {
|
|
2377
|
+
const t20Res = readVarint(slice, p20);
|
|
2378
|
+
if (t20Res === null) break;
|
|
2379
|
+
p20 = t20Res.next;
|
|
2380
|
+
const t20 = t20Res.val;
|
|
2381
|
+
const w20 = t20 & 7;
|
|
2382
|
+
const f20 = t20 >>> 3;
|
|
2383
|
+
if (w20 === 2) {
|
|
2384
|
+
const l20Res = readVarint(slice, p20);
|
|
2385
|
+
if (l20Res === null) break;
|
|
2386
|
+
p20 = l20Res.next;
|
|
2387
|
+
const l20 = l20Res.val;
|
|
2388
|
+
if (p20 + l20 > slice.length) break;
|
|
2389
|
+
const s20 = slice.subarray(p20, p20 + l20);
|
|
2390
|
+
p20 += l20;
|
|
2391
|
+
if (f20 === 3) {
|
|
2392
|
+
const str = s20.toString("utf-8");
|
|
2393
|
+
if (str.trim() !== "") {
|
|
2394
|
+
consider(str);
|
|
2395
|
+
if (!str.trim().startsWith("{") && str.trim().length >= 24) proseCandidates.unshift(str.trim());
|
|
2396
|
+
}
|
|
2397
|
+
} else if (f20 === 2 || f20 === 1) consider(s20.toString("utf-8"));
|
|
2398
|
+
} else if (w20 === 0) {
|
|
2399
|
+
const v20Res = readVarint(slice, p20);
|
|
2400
|
+
if (v20Res === null) break;
|
|
2401
|
+
p20 = v20Res.next;
|
|
2402
|
+
} else if (w20 === 1) p20 += 8;
|
|
2403
|
+
else if (w20 === 5) p20 += 4;
|
|
2404
|
+
else break;
|
|
2405
|
+
}
|
|
2359
2406
|
}
|
|
2360
|
-
|
|
2361
|
-
|
|
2362
|
-
|
|
2363
|
-
|
|
2364
|
-
|
|
2365
|
-
|
|
2366
|
-
|
|
2367
|
-
|
|
2368
|
-
|
|
2407
|
+
if (len >= 8 && len < 2e5) walk(slice, depth + 1);
|
|
2408
|
+
} else if (wireType === 0) {
|
|
2409
|
+
const vRes = readVarint(buf, pos);
|
|
2410
|
+
if (vRes === null) break;
|
|
2411
|
+
pos = vRes.next;
|
|
2412
|
+
} else if (wireType === 1) pos += 8;
|
|
2413
|
+
else if (wireType === 5) pos += 4;
|
|
2414
|
+
else break;
|
|
2415
|
+
}
|
|
2416
|
+
};
|
|
2417
|
+
walk(payload, 0);
|
|
2418
|
+
const full = payload.toString("utf8");
|
|
2419
|
+
const act = /"toolAction"\s*:\s*"([^"]{3,200})"/.exec(full);
|
|
2420
|
+
const sum = /"toolSummary"\s*:\s*"([^"]{3,200})"/.exec(full);
|
|
2421
|
+
if (act || sum) {
|
|
2422
|
+
const line = [act?.[1] ?? "", sum?.[1] ?? ""].filter((s) => s !== "").join(" — ");
|
|
2423
|
+
if (line !== "") actionCandidates.push(line);
|
|
2424
|
+
}
|
|
2425
|
+
if (proseCandidates.length > 0) {
|
|
2426
|
+
proseCandidates.sort((a, b) => b.length - a.length);
|
|
2427
|
+
return proseCandidates[0];
|
|
2428
|
+
}
|
|
2429
|
+
if (actionCandidates.length > 0) return actionCandidates[0];
|
|
2369
2430
|
return null;
|
|
2370
2431
|
}
|
|
2371
2432
|
/**
|
|
@@ -2417,9 +2478,9 @@ async function loadAgyDbData(conversationId, accountHome) {
|
|
|
2417
2478
|
const info = extractToolInfo(payload);
|
|
2418
2479
|
if (info !== null) steps.set(idx, info);
|
|
2419
2480
|
}
|
|
2420
|
-
if (stepType === 14 || stepType === 15) {
|
|
2481
|
+
if (stepType === 14 || stepType === 15 || TOOL_STEP_TYPES.has(stepType)) {
|
|
2421
2482
|
const th = extractStepThoughts(payload);
|
|
2422
|
-
if (th !== null && th.trim() !== "") thoughts.set(idx, th);
|
|
2483
|
+
if (th !== null && th.trim() !== "" && !/^SYSTEM ROLE/i.test(th.trim())) thoughts.set(idx, th);
|
|
2423
2484
|
}
|
|
2424
2485
|
}
|
|
2425
2486
|
} catch {} finally {
|
|
@@ -2824,7 +2885,8 @@ var AgyAdapter = class extends LlmAdapter {
|
|
|
2824
2885
|
if (!bin) throw new LlmError("agy binary not found on PATH — install it via https://antigravity.google/docs/cli/install", Err.AGY_NOT_INSTALLED);
|
|
2825
2886
|
const isAux = options.purpose === "compaction" || options.purpose === "session-title";
|
|
2826
2887
|
if (isAux && !cfg.allowAuxiliary) throw new LlmError("auxiliary calls are disabled for the antigravity route (allowAuxiliary: false)", Err.AUX_DISABLED);
|
|
2827
|
-
const
|
|
2888
|
+
const toolNames = new Set((options.tools ?? []).map((t) => t.name));
|
|
2889
|
+
const isCodeMode = toolNames.has("run_code") && !toolNames.has("agy_tool");
|
|
2828
2890
|
const sessionKey = options.sessionId !== void 0 ? String(options.sessionId) : "";
|
|
2829
2891
|
let workspaceRoot = cfg.workspaceRoot;
|
|
2830
2892
|
if (workspaceRoot === "") {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-agy-link",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.35",
|
|
4
4
|
"description": "Google Antigravity (agy CLI) models for DeepSeek Harness — stream Gemini/Claude/GPT-OSS subscriptions into DSH with thinking, tool activity, token usage and in-GUI Google OAuth login.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|