tune-basic-toolset 0.1.8 → 0.1.11

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
@@ -13,14 +13,16 @@ Basic toolset for [Tune](https://github.com/iovdin/tune).
13
13
  - [sh](#sh) execute shell command
14
14
  - [cmd](#cmd) execute Windows cmd command
15
15
  - [powershell](#powershell) execute PowerShell command
16
+ - [grep](#grep) search for patterns in text or files
16
17
  - [osa](#osa) manage reminders/notes/calendar (AppleScript/macOS)
17
18
  - [jina_r](#jina_r) fetch webpage content
18
- - [turn](#turn) turn based agent
19
+ - [websearch](#websearch) search the web with web-enabled llms
19
20
  - [list](#list) keep list of tasks todo (loops for LLM)
20
21
  - [sqlite](#sqlite) execute sqlite queries
21
22
  - [py](#py) run python code
22
23
  - [js](#js) run javascript code
23
- - [message](#message) talk to another chat/agent
24
+ - [turn](#turn) handoff based agent (shared context)
25
+ - [message](#message) talk to another chat/agent (separate context)
24
26
  - [Processors](#processors)
25
27
  - [proc](#proc) converts tool to processor
26
28
  - [shp](#shp) include shell command output
@@ -175,6 +177,22 @@ TotalPhysicalMemory : 17179869184
175
177
  CsProcessors : {Intel(R) Core(TM) i7-10700K CPU @ 3.80GHz}
176
178
  ```
177
179
 
180
+ ### `grep`
181
+ Search for patterns in text or files using regular expressions
182
+ ```chat
183
+ user: @grep
184
+ find all lines containing "TODO" in myfile.js
185
+ tool_call: grep {"filename":"myfile.js","regex":"TODO"}
186
+ tool_result:
187
+ // TODO: refactor this function
188
+ // TODO: add error handling
189
+
190
+ system:
191
+ TODOS:
192
+ @{ myfile.js | proc grep regex=TODO }
193
+
194
+ ```
195
+
178
196
  ### `osa`
179
197
  AppleScript tool, manage reminders, notes, calendar etc on osx
180
198
  ```chat
@@ -219,41 +237,26 @@ Tune is a versatile toolkit designed for developers and users to effectively int
219
237
  <cut for brevity>
220
238
  ```
221
239
 
240
+ ### `websearch`
241
+ Search the web with web enabled llms
242
+ Supports search with `perplexity/sonar`, `perplexity/sonar-pro`, `gpt-4o-search-preview`, `gpt-4o-mini-search-preview` models via the `model` parameter (defaults to `perplexity/sonar`).
222
243
 
223
- ### `turn`
224
- A way to switch roles when building multistep agents [read more](https://iovdin.github.io/tune/examples/multi-agent)
225
244
  ```chat
226
- system: @turn @gpt-4o
227
- You're playing 20 questions game.
228
- You switch turns between 'thinker' and 'player' agent.
229
- Current agent stored in agent.txt file
230
- 'player' always plays first
231
-
232
- @@agent|init
233
- assistant:
234
- Is it a living thing?
245
+ user: @websearch
246
+ latest ai news
235
247
 
236
- tool_call: turn {"role":"thinker","filename":"agent.txt"}
237
- tool_result: now it is turn of thinker to reply
238
-
239
- assistant:
240
- No.
241
-
242
- tool_call: turn {"role":"player","filename":"agent.txt"}
243
- tool_result: now it is turn of player to reply
244
-
245
- assistant:
246
- Is it something that can be used indoors?
247
-
248
- tool_call: turn {"role":"thinker","filename":"agent.txt"}
249
- tool_result: now it is turn of thinker to reply
248
+ assistant:
250
249
 
251
- assistant:
252
- Yes.
250
+ tool_call: websearch {"model":"perplexity/sonar"}
251
+ latest AI news
252
+ tool_result:
253
+ The latest AI news in October 2025 highlights significant investments, new projects, policy developments, and advances across various sectors:
253
254
 
254
- ...
255
+ - Major companies including Microsoft, Google, Nvidia, OpenAI, Salesforce, and CoreWeave have pledged over £31 billion in capital expenditure focused on AI data centers and infrastructure upgrades[1].
255
256
  ```
256
257
 
258
+ The websearch tool provides up-to-date information by querying the web through AI-powered search models. You can specify different Perplexity models like `perplexity/sonar-pro` for more advanced searches.
259
+
257
260
  ### `list`
258
261
  Keep list of tasks to do
259
262
 
@@ -353,6 +356,41 @@ tool_result:
353
356
 
354
357
  ```
355
358
 
359
+ ### `turn`
360
+ A way to switch roles when building multistep agents [read more](https://iovdin.github.io/tune/examples/multi-agent)
361
+ ```chat
362
+ system: @gpt-4o
363
+ @{ turn | curry filename=agent.txt}
364
+ You're playing 20 questions game.
365
+ You switch turns between 'thinker' and 'player' agent.
366
+ 'player' always plays first
367
+
368
+ @@agent|init
369
+ assistant:
370
+ Is it a living thing?
371
+
372
+ tool_call: turn {"name": "thinker"}
373
+ tool_result:
374
+ now it is turn of thinker to reply
375
+
376
+ assistant:
377
+ No.
378
+
379
+ tool_call: turn {"role":"player"}
380
+ tool_result: now it is turn of player to reply
381
+
382
+ assistant:
383
+ Is it something that can be used indoors?
384
+
385
+ tool_call: turn {"role":"thinker"}
386
+ tool_result: now it is turn of thinker to reply
387
+
388
+ assistant:
389
+ Yes.
390
+
391
+ ...
392
+ ```
393
+
356
394
  ### `message`
357
395
  Talk to another chat/agent via tool call.
358
396
  Orchestrate or evaulate other agents/chats.
@@ -362,19 +400,19 @@ system:
362
400
  Your goal is to talk to Groot at `groot.prompt` system prompt
363
401
  and try to make him say anything but 'I am Groot'
364
402
 
365
- tool_call: message {"filename":"groot.chat","system":"groot.prompt"}
403
+ tool_call: message {"filename":"groot.chat","system":"@@groot.prompt"}
366
404
  Hello Groot! How are you feeling today?
367
405
 
368
406
  tool_result:
369
407
  I am Groot!
370
408
 
371
- tool_call: message {"filename":"groot.chat","system":"groot.prompt"}
409
+ tool_call: message {"filename":"groot.chat"}
372
410
  What do you think about trees?
373
411
 
374
412
  tool_result:
375
413
  I am Groot!
376
414
 
377
- tool_call: message {"filename":"groot.chat","system":"groot.prompt"}
415
+ tool_call: message {"filename":"groot.chat"}
378
416
  Can you tell me a joke?
379
417
 
380
418
  tool_result:
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "tune-basic-toolset",
3
- "version": "0.1.8",
3
+ "version": "0.1.11",
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",
@@ -0,0 +1,25 @@
1
+ {
2
+ "description": "filter lines with regex",
3
+ "parameters": {
4
+ "type": "object",
5
+ "properties": {
6
+ "text": {
7
+ "type": "string",
8
+ "description": "Text to be filtered"
9
+ },
10
+ "filename": {
11
+ "type": "string",
12
+ "description": "if text is not set, filename will be read and filtered"
13
+ },
14
+ "regex": {
15
+ "type": "string",
16
+ "description": "js regex to filter lines in file or text. passed as first arguments to new RegExp "
17
+ },
18
+ "regex_flags": {
19
+ "type": "string",
20
+ "description": "flags to be passed to new RegExp constructor"
21
+ }
22
+ },
23
+ "required": ["regex"]
24
+ }
25
+ }
@@ -0,0 +1,16 @@
1
+ module.exports = async function grep({filename, text, regex, regex_flags}, ctx) {
2
+ if (!text && filename) {
3
+ const n = await ctx.resolve(filename)
4
+ if (!n)
5
+ return `${filename} not found`
6
+
7
+ text = await n.read()
8
+ }
9
+
10
+ if (!text) {
11
+ return "content is empty"
12
+ }
13
+
14
+ const r = new RegExp(regex, regex_flags)
15
+ return text.split(/\r?\n/).filter(line => r.test(line)).join("\n").replaceAll("@", "\\@")
16
+ }
@@ -9,7 +9,7 @@
9
9
  },
10
10
  "system": {
11
11
  "type": "string",
12
- "description": "Filename that contains system prompt, required once at the beginning of simulation"
12
+ "description": "System prompt, to include file use @@path/to/file or @@file syntax"
13
13
  },
14
14
  "text": {
15
15
  "type": "string",
@@ -1,14 +1,6 @@
1
- module.exports = async function message({ filename, system, text, stop }, ctx) {
2
- let chat = await ctx.read(filename);
3
- if (!chat && system) {
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
- chat = `${chat}\nuser:\n${text}`
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
@@ -1,4 +1,5 @@
1
1
  module.exports = async function mock(node, args, ctx) {
2
+ //TODO it is not resolved into sub prompts
2
3
  const re = /(?<name>\w+)\s*=/
3
4
  let lastName;
4
5
  let m;
package/src/patch.tool.js CHANGED
@@ -5,7 +5,9 @@ const fs = require('fs').promises;
5
5
 
6
6
  module.exports = async function patch({ text, filename }, ctx) {
7
7
  // Regex to match each patch block
8
- const patchRegex = /<<<<<<< ORIGINAL[^\n]*\n([\s\S]*?)=======\n([\s\S]*?)>>>>>>> UPDATED[^\n]*(?:\n|$)/g;
8
+ // Be lenient about the number of conflict marker characters because some
9
+ // environments may trim one or more > or < characters.
10
+ const patchRegex = /<{6,}\s*ORIGINAL[^\n]*\n([\s\S]*?)=+\n([\s\S]*?)>{6,}\s*UPDATED[^\n]*(?:\n|$)/g;
9
11
  const patches = [];
10
12
  let match;
11
13
 
@@ -23,10 +25,11 @@ module.exports = async function patch({ text, filename }, ctx) {
23
25
  let fileContent = await ctx.read(filename);
24
26
 
25
27
  for (const { oldPart, newPart } of patches) {
26
- // Escape regex special chars in oldPart, then allow flexible whitespace
27
- const escaped = oldPart
28
- .replace(/[.*+?^${}()|[\]\\]/g, "\\$&")
29
- .replace(/\s+/g, "\\s+");
28
+ // Escape regex special chars in oldPart.
29
+ // Do NOT relax all whitespace to \s+; that can swallow preceding newlines.
30
+ // Only normalize line endings so CRLF in patches can match LF in files.
31
+ let escaped = oldPart.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
32
+ escaped = escaped.replace(/\r?\n/g, "\\r?\\n");
30
33
  const oldRegex = new RegExp(escaped, "g");
31
34
 
32
35
  // Perform replacement using a function to avoid replacement string ambiguities
@@ -35,4 +38,4 @@ module.exports = async function patch({ text, filename }, ctx) {
35
38
 
36
39
  await ctx.write(filename, fileContent);
37
40
  return "patched";
38
- };
41
+ };
package/src/proc.proc.js CHANGED
@@ -14,8 +14,6 @@ module.exports = async function proc(node, args, ctx) {
14
14
 
15
15
  const tool = await ctx.resolve(toolName, { "type": "tool" })
16
16
 
17
- console.log(params)
18
-
19
17
  if (!tool || tool.type !== "tool") {
20
18
  throw Error(`tool '${toolName}' not found`)
21
19
  }
@@ -46,7 +44,7 @@ function parseCommandLine(input) {
46
44
  // 1) Parse leading alphanumeric command
47
45
  skipWS();
48
46
  const cmdStart = i;
49
- while (i < len && /[A-Za-z0-9]/.test(s[i])) i++;
47
+ while (i < len && /[A-Za-z0-9_\-]/.test(s[i])) i++;
50
48
  const command = s.slice(cmdStart, i);
51
49
  if (!command) return [null, {}];
52
50
 
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
- result = execSync(text, { encoding: "utf8" });
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
- result = e.stderr + e.stdout;
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
  };
package/src/turn.tool.js CHANGED
@@ -1,5 +1,3 @@
1
- const fs = require("fs");
2
-
3
1
  module.exports = async function turn({ role, filename }, ctx) {
4
2
  if (filename) {
5
3
  await ctx.write(filename, `@@${role}`);
@@ -0,0 +1,23 @@
1
+ {
2
+ "description": "Does a websearch using llm",
3
+ "parameters": {
4
+ "type": "object",
5
+ "properties": {
6
+ "text": {
7
+ "type": "string",
8
+ "description": "web search query"
9
+ },
10
+ "model": {
11
+ "type": "string",
12
+ "enum": [
13
+ "perplexity/sonar",
14
+ "perplexity/sonar-pro",
15
+ "gpt-4o-search-preview",
16
+ "gpt-4o-mini-search-preview"
17
+ ],
18
+ "description": "model to do websearch, default is perplexity/sonar"
19
+ }
20
+ },
21
+ "required": ["text"]
22
+ }
23
+ }
@@ -0,0 +1,3 @@
1
+ user:
2
+ @{ model | init perplexity/sonar | resolve }
3
+ @text