u1s1-cli 0.3.0 → 0.4.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 +1 -0
- package/dist/brand.js +56 -20
- package/dist/import/claude.js +372 -0
- package/dist/import/codex.js +362 -0
- package/dist/import/index.js +354 -0
- package/dist/import/types.js +1 -0
- package/dist/import/util.js +231 -0
- package/dist/import/write.js +95 -0
- package/dist/index.js +14 -0
- package/dist/login.js +6 -3
- package/dist/style.js +6 -284
- package/dist/themes.js +6 -4
- package/package.json +5 -4
package/dist/index.js
CHANGED
|
@@ -49,6 +49,15 @@ function ensureDefaultSettings() {
|
|
|
49
49
|
settings.theme = DEFAULT_THEME_SETTING;
|
|
50
50
|
changed = true;
|
|
51
51
|
}
|
|
52
|
+
// Pi default is one-at-a-time (one queued message per turn). u1s1 delivers
|
|
53
|
+
// all queued messages together so burst typing isn't split across turns.
|
|
54
|
+
// Only fill in missing keys so an explicit /settings choice still sticks.
|
|
55
|
+
for (const key of ["steeringMode", "followUpMode"]) {
|
|
56
|
+
if (!(key in settings)) {
|
|
57
|
+
settings[key] = "all";
|
|
58
|
+
changed = true;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
52
61
|
if (changed)
|
|
53
62
|
writeFileSync(p, JSON.stringify(settings, null, 2) + "\n");
|
|
54
63
|
}
|
|
@@ -206,6 +215,11 @@ async function run() {
|
|
|
206
215
|
await update();
|
|
207
216
|
return;
|
|
208
217
|
}
|
|
218
|
+
if (cmd === "import") {
|
|
219
|
+
const { importCommand } = await import("./import/index.js");
|
|
220
|
+
await importCommand(args.slice(1));
|
|
221
|
+
return;
|
|
222
|
+
}
|
|
209
223
|
const { ensureAuth } = await import("./login.js");
|
|
210
224
|
const cfg = await ensureAuth();
|
|
211
225
|
await runAgent(cfg, args);
|
package/dist/login.js
CHANGED
|
@@ -1,7 +1,10 @@
|
|
|
1
|
+
import { createRequire } from "node:module";
|
|
1
2
|
import { createInterface } from "node:readline/promises";
|
|
2
3
|
import { fetchMe } from "./api.js";
|
|
4
|
+
import { DASHBOARD_URL, printConsoleBanner } from "./brand.js";
|
|
3
5
|
import { loadConfig, saveConfig } from "./config.js";
|
|
4
|
-
const
|
|
6
|
+
const require = createRequire(import.meta.url);
|
|
7
|
+
const VERSION = require("../package.json").version;
|
|
5
8
|
function tryOpenBrowser(url) {
|
|
6
9
|
import("node:child_process")
|
|
7
10
|
.then(({ spawn }) => {
|
|
@@ -14,8 +17,8 @@ export async function login(keyArg) {
|
|
|
14
17
|
const cfg = loadConfig();
|
|
15
18
|
let key = keyArg?.trim();
|
|
16
19
|
if (!key) {
|
|
17
|
-
|
|
18
|
-
console.log("
|
|
20
|
+
printConsoleBanner(VERSION);
|
|
21
|
+
console.log(" 需要一把 API Key(免费):");
|
|
19
22
|
console.log(` 1. 打开 ${DASHBOARD_URL} 注册/登录(30 秒,送 $10 额度)`);
|
|
20
23
|
console.log(" 2. 复制你的 API Key,粘贴到下面");
|
|
21
24
|
console.log("");
|
package/dist/style.js
CHANGED
|
@@ -1,299 +1,21 @@
|
|
|
1
|
-
import { homedir } from "node:os";
|
|
2
1
|
import { basename } from "node:path";
|
|
3
|
-
import {
|
|
2
|
+
import { truncateToWidth } from "@earendil-works/pi-tui";
|
|
4
3
|
import { renderBrandHeader } from "./brand.js";
|
|
5
|
-
function row(text) {
|
|
6
|
-
return {
|
|
7
|
-
render() {
|
|
8
|
-
return text.split("\n");
|
|
9
|
-
},
|
|
10
|
-
invalidate() { },
|
|
11
|
-
};
|
|
12
|
-
}
|
|
13
|
-
function shortPath(path) {
|
|
14
|
-
if (typeof path !== "string" || !path)
|
|
15
|
-
return "";
|
|
16
|
-
const home = homedir();
|
|
17
|
-
if (path === home)
|
|
18
|
-
return "~";
|
|
19
|
-
if (path.startsWith(`${home}/`) || path.startsWith(`${home}\\`)) {
|
|
20
|
-
return `~${path.slice(home.length)}`;
|
|
21
|
-
}
|
|
22
|
-
const cwd = process.cwd();
|
|
23
|
-
if (path === cwd)
|
|
24
|
-
return ".";
|
|
25
|
-
if (path.startsWith(`${cwd}/`) || path.startsWith(`${cwd}\\`)) {
|
|
26
|
-
return path.slice(cwd.length + 1);
|
|
27
|
-
}
|
|
28
|
-
return path;
|
|
29
|
-
}
|
|
30
|
-
function asString(value) {
|
|
31
|
-
return typeof value === "string" ? value : "";
|
|
32
|
-
}
|
|
33
|
-
function clip(text, max = 72) {
|
|
34
|
-
if (text.length <= max)
|
|
35
|
-
return text;
|
|
36
|
-
return `${text.slice(0, max - 1)}…`;
|
|
37
|
-
}
|
|
38
|
-
function callLine(theme, name, detail) {
|
|
39
|
-
let text = `${theme.fg("accent", "⏺")} ${theme.bold(theme.fg("toolTitle", name))}`;
|
|
40
|
-
if (detail)
|
|
41
|
-
text += ` ${theme.fg("muted", clip(detail))}`;
|
|
42
|
-
return text;
|
|
43
|
-
}
|
|
44
|
-
function resultLine(theme, text, color = "dim") {
|
|
45
|
-
return ` ${theme.fg("dim", "⎿")} ${theme.fg(color, text)}`;
|
|
46
|
-
}
|
|
47
|
-
function indentOutput(theme, output, limit = 20) {
|
|
48
|
-
const all = output.split("\n");
|
|
49
|
-
const shown = all.slice(0, limit);
|
|
50
|
-
let text = shown.map((line) => ` ${theme.fg("dim", line)}`).join("\n");
|
|
51
|
-
if (all.length > limit) {
|
|
52
|
-
text += `\n ${theme.fg("muted", `… ${all.length - limit} 行`)}`;
|
|
53
|
-
}
|
|
54
|
-
return text;
|
|
55
|
-
}
|
|
56
|
-
const toolCache = new Map();
|
|
57
|
-
function createBuiltInTools(cwd) {
|
|
58
|
-
return {
|
|
59
|
-
read: createReadTool(cwd),
|
|
60
|
-
bash: createBashTool(cwd),
|
|
61
|
-
edit: createEditTool(cwd),
|
|
62
|
-
write: createWriteTool(cwd),
|
|
63
|
-
find: createFindTool(cwd),
|
|
64
|
-
grep: createGrepTool(cwd),
|
|
65
|
-
ls: createLsTool(cwd),
|
|
66
|
-
};
|
|
67
|
-
}
|
|
68
|
-
function toolsFor(cwd) {
|
|
69
|
-
let cached = toolCache.get(cwd);
|
|
70
|
-
if (!cached) {
|
|
71
|
-
cached = createBuiltInTools(cwd);
|
|
72
|
-
toolCache.set(cwd, cached);
|
|
73
|
-
}
|
|
74
|
-
return cached;
|
|
75
|
-
}
|
|
76
4
|
/**
|
|
77
|
-
*
|
|
78
|
-
*
|
|
5
|
+
* Brand chrome is just the startup hero + window title; everything else
|
|
6
|
+
* (tool rows, thinking blocks, spinner) stays Pi's default UI.
|
|
79
7
|
*/
|
|
80
8
|
export function applyBrandUi(pi, version) {
|
|
81
9
|
pi.on("session_start", async (_event, ctx) => {
|
|
82
10
|
if (ctx.mode !== "tui")
|
|
83
11
|
return;
|
|
84
12
|
ctx.ui.setHeader((_tui, theme) => ({
|
|
85
|
-
render() {
|
|
86
|
-
|
|
13
|
+
render(width) {
|
|
14
|
+
// pi-tui crashes on lines wider than the terminal, so truncate defensively.
|
|
15
|
+
return renderBrandHeader(theme, version, process.cwd(), width).map((line) => truncateToWidth(line, width));
|
|
87
16
|
},
|
|
88
17
|
invalidate() { },
|
|
89
18
|
}));
|
|
90
|
-
const theme = ctx.ui.theme;
|
|
91
|
-
ctx.ui.setWorkingMessage("思考中");
|
|
92
|
-
ctx.ui.setWorkingIndicator({
|
|
93
|
-
frames: [
|
|
94
|
-
theme.fg("dim", "·"),
|
|
95
|
-
theme.fg("muted", "•"),
|
|
96
|
-
theme.fg("accent", "●"),
|
|
97
|
-
theme.fg("muted", "•"),
|
|
98
|
-
],
|
|
99
|
-
intervalMs: 140,
|
|
100
|
-
});
|
|
101
|
-
ctx.ui.setHiddenThinkingLabel("思考中");
|
|
102
19
|
ctx.ui.setTitle(`u1s1 — ${basename(process.cwd())}`);
|
|
103
20
|
});
|
|
104
|
-
const cwd = process.cwd();
|
|
105
|
-
const originals = toolsFor(cwd);
|
|
106
|
-
pi.registerTool({
|
|
107
|
-
name: "read",
|
|
108
|
-
label: "read",
|
|
109
|
-
description: originals.read.description,
|
|
110
|
-
parameters: originals.read.parameters,
|
|
111
|
-
renderShell: "self",
|
|
112
|
-
async execute(toolCallId, params, signal, onUpdate, ctx) {
|
|
113
|
-
return toolsFor(ctx.cwd).read.execute(toolCallId, params, signal, onUpdate);
|
|
114
|
-
},
|
|
115
|
-
renderCall(args, theme) {
|
|
116
|
-
return row(callLine(theme, "Read", shortPath(args.path)));
|
|
117
|
-
},
|
|
118
|
-
renderResult(result, { expanded, isPartial }, theme) {
|
|
119
|
-
if (isPartial)
|
|
120
|
-
return row(resultLine(theme, "读取中…", "warning"));
|
|
121
|
-
const content = result.content[0];
|
|
122
|
-
if (content?.type === "image")
|
|
123
|
-
return row(resultLine(theme, "图片", "success"));
|
|
124
|
-
if (content?.type !== "text")
|
|
125
|
-
return row(resultLine(theme, "无内容", "error"));
|
|
126
|
-
const details = result.details;
|
|
127
|
-
const count = content.text.split("\n").length;
|
|
128
|
-
let text = resultLine(theme, `${count} 行`, "success");
|
|
129
|
-
if (details?.truncation?.truncated)
|
|
130
|
-
text += theme.fg("warning", " · 已截断");
|
|
131
|
-
if (expanded)
|
|
132
|
-
text += `\n${indentOutput(theme, content.text)}`;
|
|
133
|
-
return row(text);
|
|
134
|
-
},
|
|
135
|
-
});
|
|
136
|
-
pi.registerTool({
|
|
137
|
-
name: "bash",
|
|
138
|
-
label: "bash",
|
|
139
|
-
description: originals.bash.description,
|
|
140
|
-
parameters: originals.bash.parameters,
|
|
141
|
-
renderShell: "self",
|
|
142
|
-
async execute(toolCallId, params, signal, onUpdate, ctx) {
|
|
143
|
-
return toolsFor(ctx.cwd).bash.execute(toolCallId, params, signal, onUpdate);
|
|
144
|
-
},
|
|
145
|
-
renderCall(args, theme) {
|
|
146
|
-
return row(callLine(theme, "Bash", asString(args.command)));
|
|
147
|
-
},
|
|
148
|
-
renderResult(result, { expanded, isPartial }, theme) {
|
|
149
|
-
if (isPartial)
|
|
150
|
-
return row(resultLine(theme, "执行中…", "warning"));
|
|
151
|
-
const content = result.content[0];
|
|
152
|
-
const output = content?.type === "text" ? content.text : "";
|
|
153
|
-
const exitMatch = output.match(/exit code: (\d+)/);
|
|
154
|
-
const exitCode = exitMatch ? Number(exitMatch[1]) : null;
|
|
155
|
-
const details = result.details;
|
|
156
|
-
const ok = exitCode === 0 || exitCode === null;
|
|
157
|
-
let text = resultLine(theme, ok ? "完成" : `退出码 ${exitCode}`, ok ? "success" : "error");
|
|
158
|
-
if (details?.truncation?.truncated)
|
|
159
|
-
text += theme.fg("warning", " · 已截断");
|
|
160
|
-
if (expanded && output)
|
|
161
|
-
text += `\n${indentOutput(theme, output)}`;
|
|
162
|
-
return row(text);
|
|
163
|
-
},
|
|
164
|
-
});
|
|
165
|
-
pi.registerTool({
|
|
166
|
-
name: "edit",
|
|
167
|
-
label: "edit",
|
|
168
|
-
description: originals.edit.description,
|
|
169
|
-
parameters: originals.edit.parameters,
|
|
170
|
-
renderShell: "self",
|
|
171
|
-
async execute(toolCallId, params, signal, onUpdate, ctx) {
|
|
172
|
-
return toolsFor(ctx.cwd).edit.execute(toolCallId, params, signal, onUpdate);
|
|
173
|
-
},
|
|
174
|
-
renderCall(args, theme) {
|
|
175
|
-
return row(callLine(theme, "Edit", shortPath(args.path)));
|
|
176
|
-
},
|
|
177
|
-
renderResult(result, { expanded, isPartial }, theme) {
|
|
178
|
-
if (isPartial)
|
|
179
|
-
return row(resultLine(theme, "修改中…", "warning"));
|
|
180
|
-
const content = result.content[0];
|
|
181
|
-
if (content?.type === "text" && content.text.startsWith("Error")) {
|
|
182
|
-
return row(resultLine(theme, content.text.split("\n")[0] ?? "失败", "error"));
|
|
183
|
-
}
|
|
184
|
-
const details = result.details;
|
|
185
|
-
if (!details?.diff)
|
|
186
|
-
return row(resultLine(theme, "已写入", "success"));
|
|
187
|
-
let additions = 0;
|
|
188
|
-
let removals = 0;
|
|
189
|
-
for (const line of details.diff.split("\n")) {
|
|
190
|
-
if (line.startsWith("+") && !line.startsWith("+++"))
|
|
191
|
-
additions++;
|
|
192
|
-
if (line.startsWith("-") && !line.startsWith("---"))
|
|
193
|
-
removals++;
|
|
194
|
-
}
|
|
195
|
-
let text = resultLine(theme, `+${additions} / -${removals}`, "success");
|
|
196
|
-
if (expanded)
|
|
197
|
-
text += `\n${indentOutput(theme, details.diff, 30)}`;
|
|
198
|
-
return row(text);
|
|
199
|
-
},
|
|
200
|
-
});
|
|
201
|
-
pi.registerTool({
|
|
202
|
-
name: "write",
|
|
203
|
-
label: "write",
|
|
204
|
-
description: originals.write.description,
|
|
205
|
-
parameters: originals.write.parameters,
|
|
206
|
-
renderShell: "self",
|
|
207
|
-
async execute(toolCallId, params, signal, onUpdate, ctx) {
|
|
208
|
-
return toolsFor(ctx.cwd).write.execute(toolCallId, params, signal, onUpdate);
|
|
209
|
-
},
|
|
210
|
-
renderCall(args, theme) {
|
|
211
|
-
const count = asString(args.content).split("\n").length;
|
|
212
|
-
const detail = `${shortPath(args.path)}${count ? ` (${count} 行)` : ""}`;
|
|
213
|
-
return row(callLine(theme, "Write", detail));
|
|
214
|
-
},
|
|
215
|
-
renderResult(result, { isPartial }, theme) {
|
|
216
|
-
if (isPartial)
|
|
217
|
-
return row(resultLine(theme, "写入中…", "warning"));
|
|
218
|
-
const content = result.content[0];
|
|
219
|
-
if (content?.type === "text" && content.text.startsWith("Error")) {
|
|
220
|
-
return row(resultLine(theme, content.text.split("\n")[0] ?? "失败", "error"));
|
|
221
|
-
}
|
|
222
|
-
return row(resultLine(theme, "已写入", "success"));
|
|
223
|
-
},
|
|
224
|
-
});
|
|
225
|
-
pi.registerTool({
|
|
226
|
-
name: "grep",
|
|
227
|
-
label: "grep",
|
|
228
|
-
description: originals.grep.description,
|
|
229
|
-
parameters: originals.grep.parameters,
|
|
230
|
-
renderShell: "self",
|
|
231
|
-
async execute(toolCallId, params, signal, onUpdate, ctx) {
|
|
232
|
-
return toolsFor(ctx.cwd).grep.execute(toolCallId, params, signal, onUpdate);
|
|
233
|
-
},
|
|
234
|
-
renderCall(args, theme) {
|
|
235
|
-
const where = shortPath(args.path);
|
|
236
|
-
const detail = where ? `${asString(args.pattern)} ${where}` : asString(args.pattern);
|
|
237
|
-
return row(callLine(theme, "Grep", detail));
|
|
238
|
-
},
|
|
239
|
-
renderResult(result, { expanded, isPartial }, theme) {
|
|
240
|
-
if (isPartial)
|
|
241
|
-
return row(resultLine(theme, "搜索中…", "warning"));
|
|
242
|
-
const content = result.content[0];
|
|
243
|
-
const output = content?.type === "text" ? content.text : "";
|
|
244
|
-
const hits = output ? output.split("\n").filter(Boolean).length : 0;
|
|
245
|
-
let text = resultLine(theme, hits ? `${hits} 处` : "无匹配", hits ? "success" : "dim");
|
|
246
|
-
if (expanded && output)
|
|
247
|
-
text += `\n${indentOutput(theme, output)}`;
|
|
248
|
-
return row(text);
|
|
249
|
-
},
|
|
250
|
-
});
|
|
251
|
-
pi.registerTool({
|
|
252
|
-
name: "find",
|
|
253
|
-
label: "find",
|
|
254
|
-
description: originals.find.description,
|
|
255
|
-
parameters: originals.find.parameters,
|
|
256
|
-
renderShell: "self",
|
|
257
|
-
async execute(toolCallId, params, signal, onUpdate, ctx) {
|
|
258
|
-
return toolsFor(ctx.cwd).find.execute(toolCallId, params, signal, onUpdate);
|
|
259
|
-
},
|
|
260
|
-
renderCall(args, theme) {
|
|
261
|
-
return row(callLine(theme, "Find", asString(args.pattern)));
|
|
262
|
-
},
|
|
263
|
-
renderResult(result, { expanded, isPartial }, theme) {
|
|
264
|
-
if (isPartial)
|
|
265
|
-
return row(resultLine(theme, "查找中…", "warning"));
|
|
266
|
-
const content = result.content[0];
|
|
267
|
-
const output = content?.type === "text" ? content.text : "";
|
|
268
|
-
const hits = output ? output.split("\n").filter(Boolean).length : 0;
|
|
269
|
-
let text = resultLine(theme, hits ? `${hits} 个文件` : "没找到", hits ? "success" : "dim");
|
|
270
|
-
if (expanded && output)
|
|
271
|
-
text += `\n${indentOutput(theme, output)}`;
|
|
272
|
-
return row(text);
|
|
273
|
-
},
|
|
274
|
-
});
|
|
275
|
-
pi.registerTool({
|
|
276
|
-
name: "ls",
|
|
277
|
-
label: "ls",
|
|
278
|
-
description: originals.ls.description,
|
|
279
|
-
parameters: originals.ls.parameters,
|
|
280
|
-
renderShell: "self",
|
|
281
|
-
async execute(toolCallId, params, signal, onUpdate, ctx) {
|
|
282
|
-
return toolsFor(ctx.cwd).ls.execute(toolCallId, params, signal, onUpdate);
|
|
283
|
-
},
|
|
284
|
-
renderCall(args, theme) {
|
|
285
|
-
return row(callLine(theme, "List", shortPath(args.path) || "."));
|
|
286
|
-
},
|
|
287
|
-
renderResult(result, { expanded, isPartial }, theme) {
|
|
288
|
-
if (isPartial)
|
|
289
|
-
return row(resultLine(theme, "列出中…", "warning"));
|
|
290
|
-
const content = result.content[0];
|
|
291
|
-
const output = content?.type === "text" ? content.text : "";
|
|
292
|
-
const hits = output ? output.split("\n").filter(Boolean).length : 0;
|
|
293
|
-
let text = resultLine(theme, `${hits} 项`, "success");
|
|
294
|
-
if (expanded && output)
|
|
295
|
-
text += `\n${indentOutput(theme, output)}`;
|
|
296
|
-
return row(text);
|
|
297
|
-
},
|
|
298
|
-
});
|
|
299
21
|
}
|
package/dist/themes.js
CHANGED
|
@@ -18,6 +18,7 @@ export const DARK_THEME = {
|
|
|
18
18
|
yellow: "#d4a574",
|
|
19
19
|
blue: "#89b4c4",
|
|
20
20
|
selectedBg: "#3a342e",
|
|
21
|
+
userMsgBg: "#34312c",
|
|
21
22
|
},
|
|
22
23
|
colors: {
|
|
23
24
|
accent: "orange",
|
|
@@ -35,9 +36,9 @@ export const DARK_THEME = {
|
|
|
35
36
|
scrollbarThumb: "selectedBg",
|
|
36
37
|
searchMatchBg: "selectedBg",
|
|
37
38
|
searchMatchText: "text",
|
|
38
|
-
userMessageBg: "",
|
|
39
|
+
userMessageBg: "userMsgBg",
|
|
39
40
|
userMessageText: "text",
|
|
40
|
-
customMessageBg: "",
|
|
41
|
+
customMessageBg: "userMsgBg",
|
|
41
42
|
customMessageText: "text",
|
|
42
43
|
customMessageLabel: "orangeSoft",
|
|
43
44
|
toolPendingBg: "",
|
|
@@ -95,6 +96,7 @@ export const LIGHT_THEME = {
|
|
|
95
96
|
yellow: "#9a6b2f",
|
|
96
97
|
blue: "#3d6f8a",
|
|
97
98
|
selectedBg: "#efe8e0",
|
|
99
|
+
userMsgBg: "#e8e4dc",
|
|
98
100
|
},
|
|
99
101
|
colors: {
|
|
100
102
|
accent: "orange",
|
|
@@ -112,9 +114,9 @@ export const LIGHT_THEME = {
|
|
|
112
114
|
scrollbarThumb: "selectedBg",
|
|
113
115
|
searchMatchBg: "selectedBg",
|
|
114
116
|
searchMatchText: "text",
|
|
115
|
-
userMessageBg: "",
|
|
117
|
+
userMessageBg: "userMsgBg",
|
|
116
118
|
userMessageText: "text",
|
|
117
|
-
customMessageBg: "",
|
|
119
|
+
customMessageBg: "userMsgBg",
|
|
118
120
|
customMessageText: "text",
|
|
119
121
|
customMessageLabel: "orangeSoft",
|
|
120
122
|
toolPendingBg: "",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "u1s1-cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.0",
|
|
4
4
|
"description": "u1s1 — 有一说一,最省心的 AI 编程搭子。终端里用中文说需求,AI 帮你读文件、改代码、跑命令。",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -28,11 +28,12 @@
|
|
|
28
28
|
"license": "MIT",
|
|
29
29
|
"homepage": "https://u1s1.io",
|
|
30
30
|
"dependencies": {
|
|
31
|
-
"@earendil-works/pi-coding-agent": "0.84.2"
|
|
31
|
+
"@earendil-works/pi-coding-agent": "0.84.2",
|
|
32
|
+
"@earendil-works/pi-tui": "0.84.2"
|
|
32
33
|
},
|
|
33
34
|
"devDependencies": {
|
|
34
|
-
"
|
|
35
|
+
"@types/node": "^22.15.0",
|
|
35
36
|
"tsx": "^4.20.0",
|
|
36
|
-
"
|
|
37
|
+
"typescript": "^5.9.2"
|
|
37
38
|
}
|
|
38
39
|
}
|