tune-basic-toolset 0.1.6 → 0.1.8

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 CHANGED
@@ -17,10 +17,12 @@ Basic toolset for [Tune](https://github.com/iovdin/tune).
17
17
  - [jina_r](#jina_r) fetch webpage content
18
18
  - [turn](#turn) turn based agent
19
19
  - [list](#list) keep list of tasks todo (loops for LLM)
20
+ - [sqlite](#sqlite) execute sqlite queries
20
21
  - [py](#py) run python code
21
22
  - [js](#js) run javascript code
22
23
  - [message](#message) talk to another chat/agent
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
@@ -284,6 +286,45 @@ tool_result:
284
286
  list updated
285
287
  ```
286
288
 
289
+ ### `sqlite`
290
+ execut sqlite queries with sqlite shell
291
+
292
+ ```chat
293
+ user:
294
+ @sqlite
295
+
296
+ create a simple todo table in db.sqlite and fill it with fake data
297
+ assistant:
298
+
299
+ tool_call: sqlite {"filename":"db.sqlite"}
300
+ CREATE TABLE todo (
301
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
302
+ task TEXT NOT NULL,
303
+ status TEXT CHECK(status IN ('pending', 'completed')) NOT NULL DEFAULT 'pending',
304
+ due_date DATE
305
+ );
306
+
307
+ INSERT INTO todo (task, status, due_date) VALUES
308
+ ('Buy groceries', 'pending', '2024-05-01'),
309
+ ('Finish project report', 'pending', '2024-05-03'),
310
+ ('Book flight tickets', 'completed', '2024-04-25'),
311
+ ('Call plumber', 'pending', '2024-04-30'),
312
+ ('Schedule meeting with team', 'completed', '2024-04-20');
313
+ tool_result:
314
+
315
+ user:
316
+ check pending tasks
317
+
318
+ assistant:
319
+
320
+ tool_call: sqlite {"filename":"db.sqlite","format":"json"}
321
+ SELECT * FROM todo WHERE status = 'pending';
322
+ tool_result:
323
+ [{"id":1,"task":"Buy groceries","status":"pending","due_date":"2024-05-01"},
324
+ {"id":2,"task":"Finish project report","status":"pending","due_date":"2024-05-03"},
325
+ {"id":4,"task":"Call plumber","status":"pending","due_date":"2024-04-30"}]
326
+ ```
327
+
287
328
 
288
329
  ### `py`
289
330
  execute python code
@@ -370,6 +411,20 @@ Because it had a root canal!
370
411
  ## Processors
371
412
  [Processors](https://iovdin.github.io/tune/template-language/processors) is a way to modify variable or insert new ones into chat.
372
413
 
414
+ ### `proc`
415
+ converts any tool to a processor
416
+ ``` chat
417
+ system:
418
+ include project file list to system prompt
419
+ @{| proc sh git ls-files }
420
+
421
+ execute script with sqlite on db `db.sqlite` and insert result
422
+ @{ script.sql | proc sqlite filename=db.sqlite }
423
+
424
+ execut python script text="384 * 123" and insert back result
425
+ @{| proc py 384 * 123 }
426
+ ```
427
+
373
428
  ### `shp`
374
429
  Insert shell command output
375
430
  ```chat
@@ -623,3 +678,4 @@ tool_call: todo
623
678
  tool_result:
624
679
  list updated
625
680
  ```
681
+
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "tune-basic-toolset",
3
- "version": "0.1.6",
3
+ "version": "0.1.8",
4
4
  "description": "Basic toolset for tune",
5
5
  "main": "src/index.js",
6
6
  "files": [
package/src/patch.tool.js CHANGED
@@ -17,7 +17,7 @@ module.exports = async function patch({ text, filename }, ctx) {
17
17
  }
18
18
 
19
19
  if (patches.length === 0) {
20
- throw new Error("No valid patch segments found");
20
+ return "No valid patch segments found";
21
21
  }
22
22
 
23
23
  let fileContent = await ctx.read(filename);
@@ -35,4 +35,4 @@ module.exports = async function patch({ text, filename }, ctx) {
35
35
 
36
36
  await ctx.write(filename, fileContent);
37
37
  return "patched";
38
- };
38
+ };
@@ -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/shp.proc.js CHANGED
@@ -12,7 +12,7 @@ const shp = async (node, args, ctx) => ({
12
12
  let result;
13
13
  try {
14
14
  if (input !== null) {
15
- const res = spawnSync(args.trim(), {
15
+ const res = spawnSync(args.trim() || "sh", {
16
16
  input,
17
17
  encoding: 'utf8',
18
18
  shell: true
@@ -28,4 +28,4 @@ const shp = async (node, args, ctx) => ({
28
28
  }
29
29
  });
30
30
 
31
- module.exports = shp;
31
+ module.exports = shp;
@@ -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
+ }