pi-cc-extensions 0.8.68 → 0.8.70
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.en.md +5 -3
- package/README.md +5 -3
- package/extensions/config/config.ts +26 -34
- package/extensions/config/panel.ts +53 -4
- package/extensions/feature/agent-summary/core.ts +3 -24
- package/extensions/feature/agent-summary/index.ts +1 -1
- package/extensions/feature/compact-thinking.ts +0 -3
- package/extensions/feature/context.ts +1 -0
- package/extensions/feature/reference/index.ts +7 -53
- package/extensions/feature/reference/session.ts +2 -14
- package/extensions/feature/shell/startup-header.ts +1 -6
- package/extensions/renderer/compact-mode.ts +13 -28
- package/extensions/renderer/default-mode.ts +30 -68
- package/extensions/renderer/index.ts +0 -13
- package/extensions/renderer/mouse/hover.ts +0 -5
- package/extensions/renderer/mouse/interaction.ts +57 -27
- package/extensions/renderer/mouse/packets.ts +1 -0
- package/extensions/renderer/mouse/scroll.ts +7 -14
- package/extensions/renderer/tool/diff/diff-limits.ts +2 -10
- package/extensions/renderer/tool/diff/diff-parse.ts +20 -11
- package/extensions/renderer/tool/grouping.ts +16 -5
- package/extensions/renderer/tool/names.ts +111 -10
- package/extensions/renderer/tool/result.ts +45 -31
- package/extensions/utils/patch-keys.ts +4 -10
- package/extensions/utils/sgr-mouse.ts +6 -0
- package/package.json +3 -3
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { posix, win32 } from "node:path";
|
|
2
|
+
import { visibleWidth } from "@earendil-works/pi-tui";
|
|
1
3
|
import { config } from "../../config/config.ts";
|
|
2
4
|
import { oneLine } from "../../utils/format.ts";
|
|
3
5
|
|
|
@@ -31,8 +33,107 @@ export type ToolCallSummaryOptions = {
|
|
|
31
33
|
title?: string;
|
|
32
34
|
/** 文案变体;缺省 "default"。 */
|
|
33
35
|
variant?: ToolCallSummaryVariant;
|
|
36
|
+
/** 工具执行目录;仅用于生成展示路径,不修改调用参数。 */
|
|
37
|
+
cwd?: string;
|
|
34
38
|
};
|
|
35
39
|
|
|
40
|
+
export type ToolCallSummary = {
|
|
41
|
+
main: string;
|
|
42
|
+
detail: string;
|
|
43
|
+
/** 路径摘要保留结构,供最终渲染按实际宽度优先保留文件名。 */
|
|
44
|
+
path?: { prefix: string; value: string };
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
function pathApi(value: string) {
|
|
48
|
+
if (win32.isAbsolute(value)) return win32;
|
|
49
|
+
if (posix.isAbsolute(value)) return posix;
|
|
50
|
+
return undefined;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/** cwd 内绝对路径转相对路径;cwd 外路径保持不变。 */
|
|
54
|
+
export function displayPath(value: unknown, cwd?: string): string {
|
|
55
|
+
const text = oneLine(value, 4096);
|
|
56
|
+
const base = cwd ? oneLine(cwd, 4096) : "";
|
|
57
|
+
const api = pathApi(text);
|
|
58
|
+
if (!api || !base || !api.isAbsolute(base)) return text;
|
|
59
|
+
const relative = api.relative(base, text);
|
|
60
|
+
if (!relative) return api.basename(text) || ".";
|
|
61
|
+
if (relative === ".." || relative.startsWith(`..${api.sep}`) || api.isAbsolute(relative)) {
|
|
62
|
+
return text;
|
|
63
|
+
}
|
|
64
|
+
return relative;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function headToWidth(text: string, width: number, ellipsis = ""): string {
|
|
68
|
+
if (visibleWidth(text) <= width) return text;
|
|
69
|
+
if (width <= 0) return "";
|
|
70
|
+
const suffix = visibleWidth(ellipsis) <= width ? ellipsis : "";
|
|
71
|
+
const contentWidth = width - visibleWidth(suffix);
|
|
72
|
+
let head = "";
|
|
73
|
+
for (const char of Array.from(text)) {
|
|
74
|
+
if (visibleWidth(head + char) > contentWidth) break;
|
|
75
|
+
head += char;
|
|
76
|
+
}
|
|
77
|
+
return head + suffix;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function tailToWidth(text: string, width: number): string {
|
|
81
|
+
if (width <= 0) return "";
|
|
82
|
+
let tail = "";
|
|
83
|
+
for (const char of Array.from(text).reverse()) {
|
|
84
|
+
if (visibleWidth(char + tail) > width) break;
|
|
85
|
+
tail = char + tail;
|
|
86
|
+
}
|
|
87
|
+
return tail;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/** 中间截断路径:目录保留开头,末尾优先完整保留文件名。 */
|
|
91
|
+
export function truncatePathToWidth(path: string, width: number): string {
|
|
92
|
+
if (visibleWidth(path) <= width) return path;
|
|
93
|
+
if (width <= 1) return width === 1 ? "…" : "";
|
|
94
|
+
const separatorIndex = Math.max(path.lastIndexOf("/"), path.lastIndexOf("\\"));
|
|
95
|
+
const filename = separatorIndex >= 0 ? path.slice(separatorIndex + 1) : path;
|
|
96
|
+
const filenameWidth = visibleWidth(filename);
|
|
97
|
+
if (separatorIndex >= 0 && filenameWidth + 1 <= width) {
|
|
98
|
+
const separator = path[separatorIndex]!;
|
|
99
|
+
const suffix = `${separator}${filename}`;
|
|
100
|
+
const prefixWidth = width - visibleWidth(suffix) - 1;
|
|
101
|
+
const prefix = headToWidth(path.slice(0, separatorIndex), Math.max(0, prefixWidth));
|
|
102
|
+
return `${prefix}…${prefix ? suffix : filename}`;
|
|
103
|
+
}
|
|
104
|
+
const leftWidth = Math.max(1, Math.floor((width - 1) * 0.45));
|
|
105
|
+
const rightWidth = Math.max(0, width - leftWidth - 1);
|
|
106
|
+
return `${headToWidth(filename, leftWidth)}…${tailToWidth(filename, rightWidth)}`;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/** 路径展示的统一入口:相对化后按配置和当前可用宽度截断。 */
|
|
110
|
+
export function formatDisplayPath(value: unknown, cwd: string | undefined, width: number): string {
|
|
111
|
+
return truncatePathToWidth(displayPath(value, cwd), Math.min(width, config.inputClip));
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/** 按最终终端宽度渲染摘要;路径摘要不会再被整行头部截断。 */
|
|
115
|
+
export function fitToolCallSummary(summary: ToolCallSummary, width: number): string {
|
|
116
|
+
if (!summary.path) return headToWidth(summary.main, width, "…");
|
|
117
|
+
const prefix = summary.path.prefix;
|
|
118
|
+
const pathWidth = Math.max(0, width - visibleWidth(prefix) - 1);
|
|
119
|
+
if (pathWidth <= 0) return headToWidth(prefix, width, "…");
|
|
120
|
+
return `${prefix} ${truncatePathToWidth(summary.path.value, Math.min(pathWidth, config.inputClip))}`;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
function pathSummary(
|
|
124
|
+
prefix: string,
|
|
125
|
+
value: unknown,
|
|
126
|
+
cwd: string | undefined,
|
|
127
|
+
detail = "",
|
|
128
|
+
): ToolCallSummary {
|
|
129
|
+
const path = displayPath(value, cwd);
|
|
130
|
+
return {
|
|
131
|
+
main: `${prefix} ${truncatePathToWidth(path, config.inputClip)}`,
|
|
132
|
+
detail,
|
|
133
|
+
path: { prefix, value: path },
|
|
134
|
+
};
|
|
135
|
+
}
|
|
136
|
+
|
|
36
137
|
/**
|
|
37
138
|
* 单工具调用摘要({ main, detail })。
|
|
38
139
|
*
|
|
@@ -43,7 +144,7 @@ export function toolCallSummary(
|
|
|
43
144
|
toolName: string,
|
|
44
145
|
args: any,
|
|
45
146
|
opts: ToolCallSummaryOptions = {},
|
|
46
|
-
):
|
|
147
|
+
): ToolCallSummary {
|
|
47
148
|
const title = opts.title ?? humanizeToolLabel(toolName);
|
|
48
149
|
const variant = opts.variant ?? "default";
|
|
49
150
|
if (!args || typeof args !== "object") return { main: title, detail: "" };
|
|
@@ -106,15 +207,13 @@ export function toolCallSummary(
|
|
|
106
207
|
args.offset !== undefined ? `offset=${args.offset}` : "",
|
|
107
208
|
args.limit !== undefined ? `limit=${args.limit}` : "",
|
|
108
209
|
].filter(Boolean);
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
detail: details.length ? ` (${details.join(", ")})` : "",
|
|
113
|
-
};
|
|
210
|
+
const detail = details.length ? ` (${details.join(", ")})` : "";
|
|
211
|
+
if (typeof args.path === "string" && args.path) {
|
|
212
|
+
return pathSummary(variant === "grouping" ? "Read" : title, args.path, opts.cwd, detail);
|
|
114
213
|
}
|
|
115
214
|
return {
|
|
116
|
-
main:
|
|
117
|
-
detail
|
|
215
|
+
main: variant === "grouping" ? "Read ..." : title,
|
|
216
|
+
detail,
|
|
118
217
|
};
|
|
119
218
|
}
|
|
120
219
|
if (variant === "grouping") {
|
|
@@ -135,9 +234,11 @@ export function toolCallSummary(
|
|
|
135
234
|
}
|
|
136
235
|
}
|
|
137
236
|
if (variant === "default") {
|
|
237
|
+
const preferredPath = args.path ?? args.file_path;
|
|
238
|
+
if (typeof preferredPath === "string" && preferredPath) {
|
|
239
|
+
return pathSummary(title, preferredPath, opts.cwd);
|
|
240
|
+
}
|
|
138
241
|
const preferred =
|
|
139
|
-
args.path ??
|
|
140
|
-
args.file_path ??
|
|
141
242
|
args.command ??
|
|
142
243
|
args.query ??
|
|
143
244
|
args.question ??
|
|
@@ -204,7 +204,7 @@ export class ExpandedToolResultText {
|
|
|
204
204
|
}
|
|
205
205
|
}
|
|
206
206
|
|
|
207
|
-
/**
|
|
207
|
+
/** 截断体末行 `… +N more lines` 旁的展开提示,点击打开全量预览。 */
|
|
208
208
|
export const SHOW_MORE_LABEL = "• click to show more";
|
|
209
209
|
|
|
210
210
|
export type ToolIoSection = "input" | "output";
|
|
@@ -251,8 +251,8 @@ export class ExpandedToolIoView {
|
|
|
251
251
|
inputBody: string,
|
|
252
252
|
outputBody: string,
|
|
253
253
|
isError: boolean,
|
|
254
|
-
maxOutputLines = config.
|
|
255
|
-
maxInputLines = config.
|
|
254
|
+
maxOutputLines = config.expandedOutputMaxLines,
|
|
255
|
+
maxInputLines = config.expandedInputMaxLines,
|
|
256
256
|
flushLeft = false,
|
|
257
257
|
) {
|
|
258
258
|
this.theme = theme;
|
|
@@ -309,12 +309,12 @@ export class ExpandedToolIoView {
|
|
|
309
309
|
this.invalidate();
|
|
310
310
|
}
|
|
311
311
|
|
|
312
|
-
/** True when the plain
|
|
312
|
+
/** True when the plain truncation footer carries show-more. Input 续行带 │,Output 不带。 */
|
|
313
313
|
matchShowMoreLine(plainLine: string): ToolIoSection | null {
|
|
314
314
|
const line = plainLine.replace(/\x1b\[[0-9;]*m/g, "");
|
|
315
|
-
if (!line.includes(` • ${showMoreHintText()}`)) return null;
|
|
316
|
-
if (
|
|
317
|
-
if (
|
|
315
|
+
if (!line.includes(` • ${showMoreHintText()}`) || !/\+\d+ more lines/.test(line)) return null;
|
|
316
|
+
if (this.truncated.input && line.includes("│")) return "input";
|
|
317
|
+
if (this.truncated.output) return "output";
|
|
318
318
|
return null;
|
|
319
319
|
}
|
|
320
320
|
|
|
@@ -360,24 +360,13 @@ export class ExpandedToolIoView {
|
|
|
360
360
|
this.truncated = { input: false, output: false };
|
|
361
361
|
this.showMoreHeaderRows = {};
|
|
362
362
|
|
|
363
|
-
const pushHeader = (
|
|
364
|
-
corner: "├" | "└",
|
|
365
|
-
label: string,
|
|
366
|
-
section: ToolIoSection,
|
|
367
|
-
showMore: boolean,
|
|
368
|
-
) => {
|
|
363
|
+
const pushHeader = (corner: "├" | "└", label: string) => {
|
|
369
364
|
const mark = theme.fg("dim", `${lead}${corner} `);
|
|
370
365
|
const title = theme.fg(
|
|
371
366
|
"accent",
|
|
372
367
|
typeof theme.bold === "function" ? theme.bold(label) : label,
|
|
373
368
|
);
|
|
374
|
-
|
|
375
|
-
const more = showMore
|
|
376
|
-
? theme.fg("dim", " •") +
|
|
377
|
-
theme.fg(this.hoveredSection === section ? "text" : "dim", ` ${showMoreHintText()}`)
|
|
378
|
-
: "";
|
|
379
|
-
if (showMore) this.showMoreHeaderRows[section] = lines.length;
|
|
380
|
-
lines.push(truncateToWidth(mark + title + more, safeWidth, ""));
|
|
369
|
+
lines.push(truncateToWidth(mark + title, safeWidth, ""));
|
|
381
370
|
};
|
|
382
371
|
|
|
383
372
|
const pushRailLine = (styledContent: string, continued = true) => {
|
|
@@ -400,7 +389,7 @@ export class ExpandedToolIoView {
|
|
|
400
389
|
|
|
401
390
|
const pushBody = (
|
|
402
391
|
body: string,
|
|
403
|
-
opts: { input?: boolean; limit: number; continued?: boolean },
|
|
392
|
+
opts: { input?: boolean; limit: number; continued?: boolean; section: ToolIoSection },
|
|
404
393
|
): boolean /* truncated */ => {
|
|
405
394
|
const raw = body.replace(/\t/g, " ").replace(/\n+$/, "");
|
|
406
395
|
if (!raw.trim()) {
|
|
@@ -423,7 +412,15 @@ export class ExpandedToolIoView {
|
|
|
423
412
|
if (truncated) {
|
|
424
413
|
const hidden = Math.max(0, wrapped.length - visible.length);
|
|
425
414
|
if (hidden > 0) {
|
|
426
|
-
|
|
415
|
+
// hover 只高亮文字,圆点保持 dim(与 group hint 一致)。
|
|
416
|
+
const more =
|
|
417
|
+
theme.fg("dim", " •") +
|
|
418
|
+
theme.fg(
|
|
419
|
+
this.hoveredSection === opts.section ? "text" : "dim",
|
|
420
|
+
` ${showMoreHintText()}`,
|
|
421
|
+
);
|
|
422
|
+
pushRailLine(theme.fg("dim", `… +${hidden} more lines`) + more, opts.continued);
|
|
423
|
+
this.showMoreHeaderRows[opts.section] = lines.length - 1;
|
|
427
424
|
}
|
|
428
425
|
}
|
|
429
426
|
return truncated;
|
|
@@ -447,20 +444,29 @@ export class ExpandedToolIoView {
|
|
|
447
444
|
|
|
448
445
|
if (hasInput) {
|
|
449
446
|
this.truncated.input = inputWouldTruncate;
|
|
450
|
-
pushHeader("├", "Input"
|
|
447
|
+
pushHeader("├", "Input");
|
|
451
448
|
pushBody(this.inputBody, {
|
|
452
449
|
input: true,
|
|
453
450
|
limit: this.maxInputLines,
|
|
454
451
|
continued: true,
|
|
452
|
+
section: "input",
|
|
455
453
|
});
|
|
456
454
|
pushBlankRail();
|
|
457
455
|
this.truncated.output = outputWouldTruncate;
|
|
458
|
-
pushHeader("└", "Output"
|
|
459
|
-
pushBody(outputText, {
|
|
456
|
+
pushHeader("└", "Output");
|
|
457
|
+
pushBody(outputText, {
|
|
458
|
+
limit: this.maxOutputLines,
|
|
459
|
+
continued: false,
|
|
460
|
+
section: "output",
|
|
461
|
+
});
|
|
460
462
|
} else {
|
|
461
463
|
this.truncated.output = outputWouldTruncate;
|
|
462
|
-
pushHeader("└", "Output"
|
|
463
|
-
pushBody(outputText, {
|
|
464
|
+
pushHeader("└", "Output");
|
|
465
|
+
pushBody(outputText, {
|
|
466
|
+
limit: this.maxOutputLines,
|
|
467
|
+
continued: false,
|
|
468
|
+
section: "output",
|
|
469
|
+
});
|
|
464
470
|
}
|
|
465
471
|
|
|
466
472
|
this.cachedWidth = width;
|
|
@@ -663,13 +669,21 @@ export function renderExpandedToolResult(
|
|
|
663
669
|
): ExpandedToolIoView | ExpandedToolResultText | Text {
|
|
664
670
|
const inputBody = formatToolInputArgs(args);
|
|
665
671
|
const outputBody = body;
|
|
666
|
-
const
|
|
672
|
+
const maxOutputLines = config.expandedOutputMaxLines;
|
|
673
|
+
const maxInputLines = config.expandedInputMaxLines;
|
|
667
674
|
|
|
668
675
|
// Prefer structured Input/Output when we have args or non-empty output.
|
|
669
676
|
if (inputBody.trim() || outputBody.trim()) {
|
|
670
677
|
let view: ExpandedToolIoView;
|
|
671
678
|
if (isExpandedToolIoView(lastComponent)) {
|
|
672
|
-
lastComponent.setContent(
|
|
679
|
+
lastComponent.setContent(
|
|
680
|
+
inputBody,
|
|
681
|
+
outputBody,
|
|
682
|
+
isError,
|
|
683
|
+
maxOutputLines,
|
|
684
|
+
maxInputLines,
|
|
685
|
+
flushLeft,
|
|
686
|
+
);
|
|
673
687
|
view = lastComponent;
|
|
674
688
|
} else {
|
|
675
689
|
view = new ExpandedToolIoView(
|
|
@@ -677,8 +691,8 @@ export function renderExpandedToolResult(
|
|
|
677
691
|
inputBody,
|
|
678
692
|
outputBody,
|
|
679
693
|
isError,
|
|
680
|
-
|
|
681
|
-
|
|
694
|
+
maxOutputLines,
|
|
695
|
+
maxInputLines,
|
|
682
696
|
flushLeft,
|
|
683
697
|
);
|
|
684
698
|
}
|
|
@@ -18,13 +18,10 @@ export const COMPACT_THINKING_PATCH_KEY = Symbol.for("pi.ccstyle.compact-thinkin
|
|
|
18
18
|
export const PROTOTYPE_ORIGINAL_KEY = Symbol.for("pi.ccstyle.prototype-original");
|
|
19
19
|
export const COMPACT_THINKING_OWNER = Symbol.for("pi.ccstyle.compact-thinking-owner");
|
|
20
20
|
|
|
21
|
-
// ── assistant
|
|
22
|
-
export const
|
|
23
|
-
export const
|
|
24
|
-
export const
|
|
25
|
-
export const ASSISTANT_SET_EXPANDED_KEY = Symbol.for(ASSISTANT_SET_EXPANDED_DESCRIPTION);
|
|
26
|
-
export const ASSISTANT_TOGGLE_ROUND_KEY = Symbol.for(ASSISTANT_TOGGLE_ROUND_DESCRIPTION);
|
|
27
|
-
export const ASSISTANT_REENTRY_KEY = Symbol.for(ASSISTANT_REENTRY_DESCRIPTION);
|
|
21
|
+
// ── assistant 展开状态(实例标记) ──
|
|
22
|
+
export const ASSISTANT_SET_EXPANDED_KEY = Symbol.for("pi.ccstyle.compact-assistant-set-expanded");
|
|
23
|
+
export const ASSISTANT_TOGGLE_ROUND_KEY = Symbol.for("pi.ccstyle.compact-assistant-toggle-round");
|
|
24
|
+
export const ASSISTANT_REENTRY_KEY = Symbol.for("pi.ccstyle.compact-assistant-reentry");
|
|
28
25
|
|
|
29
26
|
// ── 工具渲染补丁 ──
|
|
30
27
|
export const GLOBAL_TOOL_RENDER_PATCH = Symbol.for("pi.ccstyle.global-tool-render-patch");
|
|
@@ -36,9 +33,6 @@ export const TOOL_EXPANDED_BACKGROUND_PATCH = Symbol.for(
|
|
|
36
33
|
// ── 消息组件补丁 ──
|
|
37
34
|
export const MESSAGE_DISPLAY_PATCH = Symbol.for("pi.ccstyle.message-display-patch");
|
|
38
35
|
|
|
39
|
-
// ── 压缩渲染补丁(/reload 残留 deactivate 用) ──
|
|
40
|
-
export const GLOBAL_COMPACTION_RENDER_PATCH = Symbol.for("pi.ccstyle.compaction-render-patch");
|
|
41
|
-
|
|
42
36
|
// ── 工具分组 ──
|
|
43
37
|
export const TOOL_GROUPING_PATCH_KEY = Symbol.for("pi.ccstyle.tool-grouping-patch");
|
|
44
38
|
export const TOOL_GROUPING_PARENT_KEY = Symbol.for("pi.ccstyle.tool-grouping-parent");
|
|
@@ -53,6 +53,12 @@ export function isSgrLeftPress(packet: SgrMousePacket): boolean {
|
|
|
53
53
|
return packet.final === "M" && baseButton === 0 && (packet.code & 32) === 0;
|
|
54
54
|
}
|
|
55
55
|
|
|
56
|
+
/** 是否为左键松开。 */
|
|
57
|
+
export function isSgrLeftRelease(packet: SgrMousePacket): boolean {
|
|
58
|
+
const baseButton = mouseBaseButton(packet.code);
|
|
59
|
+
return packet.final === "m" && baseButton === 0 && (packet.code & 32) === 0;
|
|
60
|
+
}
|
|
61
|
+
|
|
56
62
|
/** 剥离修饰键位(4/8/16/32:shift/meta/ctrl/motion),得到基础按键码。 */
|
|
57
63
|
export function mouseBaseButton(code: number): number {
|
|
58
64
|
return code & ~(4 | 8 | 16 | 32);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pi-cc-extensions",
|
|
3
|
-
"version": "0.8.
|
|
3
|
+
"version": "0.8.70",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"description": "A Pi productivity suite with Claude Code-style UI, context inspection, and agent/session references",
|
|
6
6
|
"type": "module",
|
|
@@ -39,10 +39,10 @@
|
|
|
39
39
|
},
|
|
40
40
|
"scripts": {
|
|
41
41
|
"test": "node --test tests/**/*.test.ts",
|
|
42
|
-
"typecheck": "
|
|
42
|
+
"typecheck": "tsc --noEmit",
|
|
43
43
|
"format": "biome format --write .",
|
|
44
44
|
"format:check": "biome format .",
|
|
45
|
-
"docs:tool-render": "node scripts/generate-tool-render-examples.
|
|
45
|
+
"docs:tool-render": "node --experimental-strip-types scripts/generate-tool-render-examples.ts",
|
|
46
46
|
"lint": "biome check .",
|
|
47
47
|
"preversion": "npm run format && npm run lint && npm run typecheck && npm test && npm run docs:tool-render",
|
|
48
48
|
"release": "npm version patch && npm publish",
|