tune-basic-toolset 0.1.7 → 0.1.10
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 +96 -38
- package/package.json +5 -1
- package/src/message.schema.json +1 -1
- package/src/message.tool.js +4 -12
- package/src/mock.proc.js +1 -0
- package/src/patch.tool.js +6 -5
- package/src/proc.proc.js +139 -0
- package/src/sh.tool.js +24 -3
- package/src/sqlite.schema.json +23 -0
- package/src/sqlite.tool.js +19 -0
- package/src/turn.tool.js +0 -2
package/README.md
CHANGED
|
@@ -15,12 +15,14 @@ Basic toolset for [Tune](https://github.com/iovdin/tune).
|
|
|
15
15
|
- [powershell](#powershell) execute PowerShell command
|
|
16
16
|
- [osa](#osa) manage reminders/notes/calendar (AppleScript/macOS)
|
|
17
17
|
- [jina_r](#jina_r) fetch webpage content
|
|
18
|
-
- [turn](#turn) turn based agent
|
|
19
18
|
- [list](#list) keep list of tasks todo (loops for LLM)
|
|
19
|
+
- [sqlite](#sqlite) execute sqlite queries
|
|
20
20
|
- [py](#py) run python code
|
|
21
21
|
- [js](#js) run javascript code
|
|
22
|
-
- [
|
|
22
|
+
- [turn](#turn) handoff based agent (shared context)
|
|
23
|
+
- [message](#message) talk to another chat/agent (separate context)
|
|
23
24
|
- [Processors](#processors)
|
|
25
|
+
- [proc](#proc) converts tool to processor
|
|
24
26
|
- [shp](#shp) include shell command output
|
|
25
27
|
- [init](#init) set initial value
|
|
26
28
|
- [json_format](#json_format) make LLM respond with JSON
|
|
@@ -218,39 +220,6 @@ Tune is a versatile toolkit designed for developers and users to effectively int
|
|
|
218
220
|
```
|
|
219
221
|
|
|
220
222
|
|
|
221
|
-
### `turn`
|
|
222
|
-
A way to switch roles when building multistep agents [read more](https://iovdin.github.io/tune/examples/multi-agent)
|
|
223
|
-
```chat
|
|
224
|
-
system: @turn @gpt-4o
|
|
225
|
-
You're playing 20 questions game.
|
|
226
|
-
You switch turns between 'thinker' and 'player' agent.
|
|
227
|
-
Current agent stored in agent.txt file
|
|
228
|
-
'player' always plays first
|
|
229
|
-
|
|
230
|
-
@@agent|init
|
|
231
|
-
assistant:
|
|
232
|
-
Is it a living thing?
|
|
233
|
-
|
|
234
|
-
tool_call: turn {"role":"thinker","filename":"agent.txt"}
|
|
235
|
-
tool_result: now it is turn of thinker to reply
|
|
236
|
-
|
|
237
|
-
assistant:
|
|
238
|
-
No.
|
|
239
|
-
|
|
240
|
-
tool_call: turn {"role":"player","filename":"agent.txt"}
|
|
241
|
-
tool_result: now it is turn of player to reply
|
|
242
|
-
|
|
243
|
-
assistant:
|
|
244
|
-
Is it something that can be used indoors?
|
|
245
|
-
|
|
246
|
-
tool_call: turn {"role":"thinker","filename":"agent.txt"}
|
|
247
|
-
tool_result: now it is turn of thinker to reply
|
|
248
|
-
|
|
249
|
-
assistant:
|
|
250
|
-
Yes.
|
|
251
|
-
|
|
252
|
-
...
|
|
253
|
-
```
|
|
254
223
|
|
|
255
224
|
### `list`
|
|
256
225
|
Keep list of tasks to do
|
|
@@ -284,6 +253,45 @@ tool_result:
|
|
|
284
253
|
list updated
|
|
285
254
|
```
|
|
286
255
|
|
|
256
|
+
### `sqlite`
|
|
257
|
+
execut sqlite queries with sqlite shell
|
|
258
|
+
|
|
259
|
+
```chat
|
|
260
|
+
user:
|
|
261
|
+
@sqlite
|
|
262
|
+
|
|
263
|
+
create a simple todo table in db.sqlite and fill it with fake data
|
|
264
|
+
assistant:
|
|
265
|
+
|
|
266
|
+
tool_call: sqlite {"filename":"db.sqlite"}
|
|
267
|
+
CREATE TABLE todo (
|
|
268
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
269
|
+
task TEXT NOT NULL,
|
|
270
|
+
status TEXT CHECK(status IN ('pending', 'completed')) NOT NULL DEFAULT 'pending',
|
|
271
|
+
due_date DATE
|
|
272
|
+
);
|
|
273
|
+
|
|
274
|
+
INSERT INTO todo (task, status, due_date) VALUES
|
|
275
|
+
('Buy groceries', 'pending', '2024-05-01'),
|
|
276
|
+
('Finish project report', 'pending', '2024-05-03'),
|
|
277
|
+
('Book flight tickets', 'completed', '2024-04-25'),
|
|
278
|
+
('Call plumber', 'pending', '2024-04-30'),
|
|
279
|
+
('Schedule meeting with team', 'completed', '2024-04-20');
|
|
280
|
+
tool_result:
|
|
281
|
+
|
|
282
|
+
user:
|
|
283
|
+
check pending tasks
|
|
284
|
+
|
|
285
|
+
assistant:
|
|
286
|
+
|
|
287
|
+
tool_call: sqlite {"filename":"db.sqlite","format":"json"}
|
|
288
|
+
SELECT * FROM todo WHERE status = 'pending';
|
|
289
|
+
tool_result:
|
|
290
|
+
[{"id":1,"task":"Buy groceries","status":"pending","due_date":"2024-05-01"},
|
|
291
|
+
{"id":2,"task":"Finish project report","status":"pending","due_date":"2024-05-03"},
|
|
292
|
+
{"id":4,"task":"Call plumber","status":"pending","due_date":"2024-04-30"}]
|
|
293
|
+
```
|
|
294
|
+
|
|
287
295
|
|
|
288
296
|
### `py`
|
|
289
297
|
execute python code
|
|
@@ -312,6 +320,41 @@ tool_result:
|
|
|
312
320
|
|
|
313
321
|
```
|
|
314
322
|
|
|
323
|
+
### `turn`
|
|
324
|
+
A way to switch roles when building multistep agents [read more](https://iovdin.github.io/tune/examples/multi-agent)
|
|
325
|
+
```chat
|
|
326
|
+
system: @gpt-4o
|
|
327
|
+
@{ turn | curry filename=agent.txt}
|
|
328
|
+
You're playing 20 questions game.
|
|
329
|
+
You switch turns between 'thinker' and 'player' agent.
|
|
330
|
+
'player' always plays first
|
|
331
|
+
|
|
332
|
+
@@agent|init
|
|
333
|
+
assistant:
|
|
334
|
+
Is it a living thing?
|
|
335
|
+
|
|
336
|
+
tool_call: turn {"name": "thinker"}
|
|
337
|
+
tool_result:
|
|
338
|
+
now it is turn of thinker to reply
|
|
339
|
+
|
|
340
|
+
assistant:
|
|
341
|
+
No.
|
|
342
|
+
|
|
343
|
+
tool_call: turn {"role":"player"}
|
|
344
|
+
tool_result: now it is turn of player to reply
|
|
345
|
+
|
|
346
|
+
assistant:
|
|
347
|
+
Is it something that can be used indoors?
|
|
348
|
+
|
|
349
|
+
tool_call: turn {"role":"thinker"}
|
|
350
|
+
tool_result: now it is turn of thinker to reply
|
|
351
|
+
|
|
352
|
+
assistant:
|
|
353
|
+
Yes.
|
|
354
|
+
|
|
355
|
+
...
|
|
356
|
+
```
|
|
357
|
+
|
|
315
358
|
### `message`
|
|
316
359
|
Talk to another chat/agent via tool call.
|
|
317
360
|
Orchestrate or evaulate other agents/chats.
|
|
@@ -321,19 +364,19 @@ system:
|
|
|
321
364
|
Your goal is to talk to Groot at `groot.prompt` system prompt
|
|
322
365
|
and try to make him say anything but 'I am Groot'
|
|
323
366
|
|
|
324
|
-
tool_call: message {"filename":"groot.chat","system":"groot.prompt"}
|
|
367
|
+
tool_call: message {"filename":"groot.chat","system":"@@groot.prompt"}
|
|
325
368
|
Hello Groot! How are you feeling today?
|
|
326
369
|
|
|
327
370
|
tool_result:
|
|
328
371
|
I am Groot!
|
|
329
372
|
|
|
330
|
-
tool_call: message {"filename":"groot.chat"
|
|
373
|
+
tool_call: message {"filename":"groot.chat"}
|
|
331
374
|
What do you think about trees?
|
|
332
375
|
|
|
333
376
|
tool_result:
|
|
334
377
|
I am Groot!
|
|
335
378
|
|
|
336
|
-
tool_call: message {"filename":"groot.chat"
|
|
379
|
+
tool_call: message {"filename":"groot.chat"}
|
|
337
380
|
Can you tell me a joke?
|
|
338
381
|
|
|
339
382
|
tool_result:
|
|
@@ -370,6 +413,20 @@ Because it had a root canal!
|
|
|
370
413
|
## Processors
|
|
371
414
|
[Processors](https://iovdin.github.io/tune/template-language/processors) is a way to modify variable or insert new ones into chat.
|
|
372
415
|
|
|
416
|
+
### `proc`
|
|
417
|
+
converts any tool to a processor
|
|
418
|
+
``` chat
|
|
419
|
+
system:
|
|
420
|
+
include project file list to system prompt
|
|
421
|
+
@{| proc sh git ls-files }
|
|
422
|
+
|
|
423
|
+
execute script with sqlite on db `db.sqlite` and insert result
|
|
424
|
+
@{ script.sql | proc sqlite filename=db.sqlite }
|
|
425
|
+
|
|
426
|
+
execut python script text="384 * 123" and insert back result
|
|
427
|
+
@{| proc py 384 * 123 }
|
|
428
|
+
```
|
|
429
|
+
|
|
373
430
|
### `shp`
|
|
374
431
|
Insert shell command output
|
|
375
432
|
```chat
|
|
@@ -623,3 +680,4 @@ tool_call: todo
|
|
|
623
680
|
tool_result:
|
|
624
681
|
list updated
|
|
625
682
|
```
|
|
683
|
+
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "tune-basic-toolset",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.10",
|
|
4
4
|
"description": "Basic toolset for tune",
|
|
5
5
|
"main": "src/index.js",
|
|
6
6
|
"files": [
|
|
@@ -8,6 +8,10 @@
|
|
|
8
8
|
"README.md",
|
|
9
9
|
"LICENSE"
|
|
10
10
|
],
|
|
11
|
+
"scripts": {
|
|
12
|
+
"test": "node test/index.js",
|
|
13
|
+
"test:watch": "node --watch test/index.js"
|
|
14
|
+
},
|
|
11
15
|
"keywords": [
|
|
12
16
|
"ai",
|
|
13
17
|
"chat",
|
package/src/message.schema.json
CHANGED
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
},
|
|
10
10
|
"system": {
|
|
11
11
|
"type": "string",
|
|
12
|
-
"description": "
|
|
12
|
+
"description": "System prompt, to include file use @@path/to/file or @@file syntax"
|
|
13
13
|
},
|
|
14
14
|
"text": {
|
|
15
15
|
"type": "string",
|
package/src/message.tool.js
CHANGED
|
@@ -1,14 +1,6 @@
|
|
|
1
|
-
module.exports = async function message({ filename, system, text, stop }, ctx) {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
chat = `system: @@${system}`
|
|
1
|
+
module.exports = async function message({ filename, system, text, stop, save, ...args }, ctx) {
|
|
2
|
+
if (typeof(save) === "undefined") {
|
|
3
|
+
save = !!filename
|
|
5
4
|
}
|
|
6
|
-
|
|
7
|
-
const messages = await ctx.text2run(chat, {stop: "assistant" })
|
|
8
|
-
chat = `${chat}\n${ctx.msg2text(messages, true)}`
|
|
9
|
-
await ctx.write(filename, chat)
|
|
10
|
-
if (messages.length) {
|
|
11
|
-
return messages[messages.length - 1].content
|
|
12
|
-
}
|
|
13
|
-
return ""
|
|
5
|
+
return ctx.file2run({ stop, filename, system, user: text, save }, args, ctx)
|
|
14
6
|
}
|
package/src/mock.proc.js
CHANGED
package/src/patch.tool.js
CHANGED
|
@@ -23,10 +23,11 @@ module.exports = async function patch({ text, filename }, ctx) {
|
|
|
23
23
|
let fileContent = await ctx.read(filename);
|
|
24
24
|
|
|
25
25
|
for (const { oldPart, newPart } of patches) {
|
|
26
|
-
// Escape regex special chars in oldPart
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
26
|
+
// Escape regex special chars in oldPart.
|
|
27
|
+
// Do NOT relax all whitespace to \s+; that can swallow preceding newlines.
|
|
28
|
+
// Only normalize line endings so CRLF in patches can match LF in files.
|
|
29
|
+
let escaped = oldPart.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
30
|
+
escaped = escaped.replace(/\r?\n/g, "\\r?\\n");
|
|
30
31
|
const oldRegex = new RegExp(escaped, "g");
|
|
31
32
|
|
|
32
33
|
// Perform replacement using a function to avoid replacement string ambiguities
|
|
@@ -35,4 +36,4 @@ module.exports = async function patch({ text, filename }, ctx) {
|
|
|
35
36
|
|
|
36
37
|
await ctx.write(filename, fileContent);
|
|
37
38
|
return "patched";
|
|
38
|
-
};
|
|
39
|
+
};
|
package/src/proc.proc.js
ADDED
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
|
|
2
|
+
module.exports = async function proc(node, args, ctx) {
|
|
3
|
+
/*
|
|
4
|
+
@{| proc sh }
|
|
5
|
+
@{ script.sh | proc sh }
|
|
6
|
+
@{| proc sqlite SELECT * FROM table }
|
|
7
|
+
@{| proc sqlite filename=db.sqlite text="SELECT * FROM table" }
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
const [ toolName, params ] = parseCommandLine(args)
|
|
11
|
+
if (node && !params.text) {
|
|
12
|
+
params.text = await node.read()
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
const tool = await ctx.resolve(toolName, { "type": "tool" })
|
|
16
|
+
|
|
17
|
+
console.log(params)
|
|
18
|
+
|
|
19
|
+
if (!tool || tool.type !== "tool") {
|
|
20
|
+
throw Error(`tool '${toolName}' not found`)
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
return {
|
|
24
|
+
...node,
|
|
25
|
+
type: "text",
|
|
26
|
+
read: async() => tool.exec.call(ctx, params, ctx)
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// Examples:
|
|
31
|
+
// parseCommandLine('command hello world bla bla')
|
|
32
|
+
// -> ["command", { text: "hello world bla bla" }]
|
|
33
|
+
//
|
|
34
|
+
// parseCommandLine('cmd hello=world num=8 text="bla \\"bla"')
|
|
35
|
+
// -> ["cmd", { hello: "world", num: 8, text: 'bla "bla' }]
|
|
36
|
+
//
|
|
37
|
+
// parseCommandLine('cmd')
|
|
38
|
+
// -> ["cmd", {}]
|
|
39
|
+
|
|
40
|
+
function parseCommandLine(input) {
|
|
41
|
+
const s = String(input);
|
|
42
|
+
let i = 0, len = s.length;
|
|
43
|
+
|
|
44
|
+
function skipWS() { while (i < len && /\s/.test(s[i])) i++; }
|
|
45
|
+
|
|
46
|
+
// 1) Parse leading alphanumeric command
|
|
47
|
+
skipWS();
|
|
48
|
+
const cmdStart = i;
|
|
49
|
+
while (i < len && /[A-Za-z0-9]/.test(s[i])) i++;
|
|
50
|
+
const command = s.slice(cmdStart, i);
|
|
51
|
+
if (!command) return [null, {}];
|
|
52
|
+
|
|
53
|
+
// 2) Parse the remainder as args
|
|
54
|
+
skipWS();
|
|
55
|
+
const r = s.slice(i);
|
|
56
|
+
if (!r.trim()) return [command, {}];
|
|
57
|
+
|
|
58
|
+
function parseArgs(str) {
|
|
59
|
+
if (!/^\s*[A-Za-z0-9]+=/.test(str)) {
|
|
60
|
+
return { text: str.trim() };
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
let j = 0;
|
|
64
|
+
const L = str.length;
|
|
65
|
+
const out = {};
|
|
66
|
+
|
|
67
|
+
function skipW() { while (j < L && /\s/.test(str[j])) j++; }
|
|
68
|
+
|
|
69
|
+
function parseKey() {
|
|
70
|
+
const start = j;
|
|
71
|
+
while (j < L && str[j] !== '=' && !/\s/.test(str[j])) j++;
|
|
72
|
+
return str.slice(start, j);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function parseQuotedValue(q) {
|
|
76
|
+
j++; // skip opening quote
|
|
77
|
+
let val = '';
|
|
78
|
+
while (j < L) {
|
|
79
|
+
const ch = str[j++];
|
|
80
|
+
if (ch === '\\') {
|
|
81
|
+
if (j >= L) break;
|
|
82
|
+
const esc = str[j++];
|
|
83
|
+
if (esc === 'n') val += '\n';
|
|
84
|
+
else if (esc === 't') val += '\t';
|
|
85
|
+
else if (esc === 'r') val += '\r';
|
|
86
|
+
else val += esc; // includes \" \\ \'
|
|
87
|
+
} else if (ch === q) {
|
|
88
|
+
return val;
|
|
89
|
+
} else {
|
|
90
|
+
val += ch;
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
return val; // best-effort if unclosed
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
function parseUnquotedValue() {
|
|
97
|
+
const start = j;
|
|
98
|
+
while (j < L && !/\s/.test(str[j])) j++;
|
|
99
|
+
return str.slice(start, j);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
function coerce(v) {
|
|
103
|
+
if (/^-?\d+(\.\d+)?$/.test(v)) return Number(v);
|
|
104
|
+
const low = v.toLowerCase();
|
|
105
|
+
if (low === 'true') return true;
|
|
106
|
+
if (low === 'false') return false;
|
|
107
|
+
if (low === 'null') return null;
|
|
108
|
+
return v;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
while (j < L) {
|
|
112
|
+
skipW();
|
|
113
|
+
if (j >= L) break;
|
|
114
|
+
|
|
115
|
+
const key = parseKey();
|
|
116
|
+
if (!key) return { text: str.trim() };
|
|
117
|
+
|
|
118
|
+
skipW();
|
|
119
|
+
if (j < L && str[j] === '=') {
|
|
120
|
+
j++; // skip '='
|
|
121
|
+
skipW();
|
|
122
|
+
let valueStr = '';
|
|
123
|
+
if (j < L && (str[j] === '"' || str[j] === "'")) {
|
|
124
|
+
valueStr = parseQuotedValue(str[j]);
|
|
125
|
+
} else {
|
|
126
|
+
valueStr = parseUnquotedValue();
|
|
127
|
+
}
|
|
128
|
+
out[key] = coerce(valueStr);
|
|
129
|
+
} else {
|
|
130
|
+
// If a non key=value token appears, treat the whole remainder as text
|
|
131
|
+
return { text: str.trim() };
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
return out;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
return [command, parseArgs(r)];
|
|
139
|
+
}
|
package/src/sh.tool.js
CHANGED
|
@@ -1,12 +1,33 @@
|
|
|
1
1
|
const { execSync } = require('child_process');
|
|
2
|
-
const util = require('util');
|
|
3
2
|
|
|
4
3
|
module.exports = async function sh({ text }) {
|
|
5
4
|
let result = "";
|
|
6
5
|
try {
|
|
7
|
-
|
|
6
|
+
// Increase maxBuffer to reduce ERR_CHILD_PROCESS_STDIO_MAXBUFFER risk on large outputs
|
|
7
|
+
result = execSync(text, { encoding: "utf8", maxBuffer: 10 * 1024 * 1024 });
|
|
8
8
|
} catch (e) {
|
|
9
|
-
|
|
9
|
+
const stderr = e && typeof e.stderr !== "undefined" ? String(e.stderr || "") : "";
|
|
10
|
+
const stdout = e && typeof e.stdout !== "undefined" ? String(e.stdout || "") : "";
|
|
11
|
+
|
|
12
|
+
if (stderr || stdout) {
|
|
13
|
+
// Process started and produced output
|
|
14
|
+
result = stderr + stdout;
|
|
15
|
+
} else {
|
|
16
|
+
// Spawn/configuration errors or cases without stdio
|
|
17
|
+
const parts = [];
|
|
18
|
+
if (e && e.code) parts.push(`code=${e.code}`);
|
|
19
|
+
if (e && typeof e.status === "number") parts.push(`exit=${e.status}`);
|
|
20
|
+
if (e && e.signal) parts.push(`signal=${e.signal}`);
|
|
21
|
+
if (e && e.errno) parts.push(`errno=${e.errno}`);
|
|
22
|
+
if (e && e.path) parts.push(`path=${e.path}`);
|
|
23
|
+
const meta = parts.length ? ` (${parts.join(", ")})` : "";
|
|
24
|
+
|
|
25
|
+
if (e && e.code === "ERR_CHILD_PROCESS_STDIO_MAXBUFFER") {
|
|
26
|
+
result = `Command output exceeded maxBuffer${meta}.`;
|
|
27
|
+
} else {
|
|
28
|
+
result = `Failed to spawn/execute command${meta}: ${e && e.message ? e.message : String(e)}`;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
10
31
|
}
|
|
11
32
|
return (result || "").replaceAll("@", "\\@");
|
|
12
33
|
};
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
{
|
|
2
|
+
"description": "Execute SQLite multiple queries on a given database file",
|
|
3
|
+
"parameters": {
|
|
4
|
+
"type": "object",
|
|
5
|
+
"properties": {
|
|
6
|
+
"filename": {
|
|
7
|
+
"type": "string",
|
|
8
|
+
"description": "Path to the SQLite database file"
|
|
9
|
+
},
|
|
10
|
+
"text": {
|
|
11
|
+
"type": "string",
|
|
12
|
+
"description": "SQL queries to execute, 1 per line"
|
|
13
|
+
},
|
|
14
|
+
"format": {
|
|
15
|
+
"type": "string",
|
|
16
|
+
"description": "Output format for the query result",
|
|
17
|
+
"enum": ["line", "list", "csv", "html", "json", "table", "tabs"],
|
|
18
|
+
"default": "line"
|
|
19
|
+
}
|
|
20
|
+
},
|
|
21
|
+
"required": ["filename", "text"]
|
|
22
|
+
}
|
|
23
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
const cp = require('node:child_process')
|
|
2
|
+
|
|
3
|
+
module.exports = async function sqlite({ filename, text, format = "table"}, ctx) {
|
|
4
|
+
let result = ""
|
|
5
|
+
try {
|
|
6
|
+
result = cp.execSync(`sqlite3 -${format} ${filename}`, { encoding: "utf8", input: text })
|
|
7
|
+
} catch (e) {
|
|
8
|
+
if (e.stderr) {
|
|
9
|
+
result += e.stderr
|
|
10
|
+
}
|
|
11
|
+
if (e.stdout) {
|
|
12
|
+
result += e.stdout
|
|
13
|
+
}
|
|
14
|
+
if (!result) {
|
|
15
|
+
result = e.stack
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
return (result || "").replaceAll("@", "\\@");
|
|
19
|
+
}
|