tune-basic-toolset 0.1.2 → 0.1.4

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
@@ -8,9 +8,11 @@ Basic toolset for [Tune](https://github.com/iovdin/tune).
8
8
  - [Tools](#tools)
9
9
  - [rf](#rf) read file
10
10
  - [wf](#wf) write file
11
- - [patch](#patch) patch file
11
+ - [patch](#patch) patch file
12
12
  - [append](#append) append to file
13
13
  - [sh](#sh) execute shell command
14
+ - [cmd](#cmd) execute Windows cmd command
15
+ - [powershell](#powershell) execute PowerShell command
14
16
  - [osa](#osa) manage reminders/notes/calendar (AppleScript/macOS)
15
17
  - [jina_r](#jina_r) fetch webpage content
16
18
  - [turn](#turn) turn based agent
@@ -31,6 +33,7 @@ Basic toolset for [Tune](https://github.com/iovdin/tune).
31
33
  - [head](#head) take first N lines of a file
32
34
  - [tail](#tail) take last N lines of a file or LLM payload
33
35
  - [slice](#slice) take lines from <start> to <finish> of a file
36
+ - [random](#random) random selection, sampling, shuffling, uniform ranges
34
37
 
35
38
 
36
39
  ## Setup
@@ -135,6 +138,40 @@ tool_result:
135
138
  ./tools/README.md:* `echo.txt` - to debug variable expansions and context
136
139
  ```
137
140
 
141
+ ### `cmd`
142
+ Execute Windows cmd command
143
+ ```chat
144
+ user: @cmd
145
+ list all files in current directory
146
+ tool_call: cmd
147
+ dir
148
+ tool_result:
149
+ Volume in drive C has no label.
150
+ Volume Serial Number is 1234-5678
151
+
152
+ Directory of C:\project
153
+
154
+ 12/01/2023 10:30 AM <DIR> .
155
+ 12/01/2023 10:30 AM <DIR> ..
156
+ 12/01/2023 09:15 AM 1,024 package.json
157
+ 12/01/2023 09:20 AM <DIR> src
158
+ 1 File(s) 1,024 bytes
159
+ 3 Dir(s) 15,728,640 bytes free
160
+ ```
161
+
162
+ ### `powershell`
163
+ Execute PowerShell command
164
+ ```chat
165
+ user: @powershell
166
+ get system information
167
+ tool_call: powershell
168
+ Get-ComputerInfo | Select-Object WindowsProductName, TotalPhysicalMemory, CsProcessors
169
+ tool_result:
170
+ WindowsProductName : Windows 11 Pro
171
+ TotalPhysicalMemory : 17179869184
172
+ CsProcessors : {Intel(R) Core(TM) i7-10700K CPU @ 3.80GHz}
173
+ ```
174
+
138
175
  ### `osa`
139
176
  AppleScript tool, manage reminders, notes, calendar etc on osx
140
177
  ```chat
@@ -515,3 +552,33 @@ user:
515
552
  @{ filename.txt | slice -20 } # last 20 lines
516
553
  @{ filename.txt | slice 1 20 } # first 20 lines (like head 20)
517
554
  ```
555
+
556
+ ### `random`
557
+ Random selection, sampling, shuffling, and uniform number generation.
558
+
559
+ Use cases:
560
+ ```chat
561
+ user:
562
+ @{| random a b c d }
563
+ @{| random choice a b c d }
564
+ @{| random "choice 1" "choice 2" }
565
+ @{| random choice @path/to/file.txt } # choose 1 line from a file
566
+ @{| random choice 2..30 } # choose 1 from range
567
+ @{| random choice -2.5..7.5 } # floats
568
+ @{| random choices 3 a b c d } # pick 3 with replacment
569
+ @{| random choices 5 @file.txt } # pick 5 lines from file.txt
570
+ @{| random sample 3 a b c d } # pick 3 without replacement
571
+ @{| random sample 10 1..5 } # will return 5 unique numbers
572
+ @{| random shuffle a b c d }
573
+ @{| random shuffle 1..10 }
574
+ @{| random uniform 1..10 } # integers
575
+ @{| random uniform -2.5..7.5 } # floats
576
+ @{| random uniform 10 20 } # two-number form
577
+
578
+ comment:
579
+ Notes:
580
+ - Quotes are respected for tokens with spaces.
581
+ - Files referenced as @file are expanded to non-empty trimmed lines.
582
+ - Integer ranges like a..b can be mixed with discrete values and files; float ranges cannot be mixed in lists.
583
+ - sample and shuffle require a discrete set; float ranges are not supported there.
584
+ - choices and sample output multiple lines (one item per line).
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "tune-basic-toolset",
3
- "version": "0.1.2",
3
+ "version": "0.1.4",
4
4
  "description": "Basic toolset for tune",
5
5
  "main": "src/index.js",
6
6
  "files": [
@@ -0,0 +1,222 @@
1
+ // @{| random choice a b c d } choose 1 from the list
2
+ // @{| random a b c d } - default is choice
3
+ // @{| random "choice with whitespaces 1" "another choice" } - default is choice, and choices are in "
4
+ // @{| random choice @file/name } choose 1 line from the filename
5
+ // @{| random choice 2..30 } choose number from the range
6
+
7
+ // @{| random choices 3 a b c d } choice with replacement
8
+ // @{| random sample 3 a b c d } choice without replacement
9
+ // @{| random shuffle a b c d } shuffle and return
10
+ // @{| random uniform 1..10 } uniformly choose a number from the range (ints -> int, floats -> float)
11
+
12
+ // Utilities
13
+ const randInt = (min, max) => Math.floor(Math.random() * (max - min + 1)) + min
14
+ const randFloat = (min, max) => Math.random() * (max - min) + min
15
+ const isInt = (n) => Number.isInteger(n)
16
+
17
+ const shuffleArray = (arr) => {
18
+ const a = arr.slice()
19
+ for (let i = a.length - 1; i > 0; i--) {
20
+ const j = Math.floor(Math.random() * (i + 1))
21
+ ;[a[i], a[j]] = [a[j], a[i]]
22
+ }
23
+ return a
24
+ }
25
+
26
+ const tokenize = (str) => {
27
+ const out = []
28
+ const re = /"([^"]*)"|(\S+)/g
29
+ let m
30
+ while ((m = re.exec(str))) {
31
+ if (m[1] !== undefined) out.push(m[1])
32
+ else out.push(m[2])
33
+ }
34
+ return out
35
+ }
36
+
37
+ const parseRange = (str) => {
38
+ const m = String(str).trim().match(/^\s*(-?\d+(?:\.\d+)?)\.\.(-?\d+(?:\.\d+)?)\s*$/)
39
+ if (!m) return null
40
+ const min = parseFloat(m[1])
41
+ const max = parseFloat(m[2])
42
+ const intRange = isInt(min) && isInt(max)
43
+ return { min: Math.min(min, max), max: Math.max(min, max), isInt: intRange }
44
+ }
45
+
46
+ const toNumber = (s) => {
47
+ if (s === null || s === undefined) return NaN
48
+ const n = Number(s)
49
+ return Number.isFinite(n) ? n : NaN
50
+ }
51
+
52
+ const methods = {
53
+ // choice: choose a single element or a value from a range
54
+ choice: (input) => {
55
+ if (input.type === 'range') {
56
+ return String(randInt(input.range.min, input.range.max))
57
+ } else if (input.type === 'floatRange') {
58
+ return String(randFloat(input.range.min, input.range.max))
59
+ } else {
60
+ const values = input.items
61
+ if (!values.length) throw new Error('random: no values to choose from')
62
+ return values[Math.floor(Math.random() * values.length)]
63
+ }
64
+ },
65
+
66
+ // choices: n picks with replacement
67
+ choices: (input, { count }) => {
68
+ if (count <= 0) return ''
69
+ const out = []
70
+ if (input.type === 'range') {
71
+ for (let i = 0; i < count; i++) out.push(String(randInt(input.range.min, input.range.max)))
72
+ } else if (input.type === 'floatRange') {
73
+ for (let i = 0; i < count; i++) out.push(String(randFloat(input.range.min, input.range.max)))
74
+ } else {
75
+ const values = input.items
76
+ if (!values.length) throw new Error('random: no values to choose from')
77
+ for (let i = 0; i < count; i++) out.push(values[Math.floor(Math.random() * values.length)])
78
+ }
79
+ return out.join("\n")
80
+ },
81
+
82
+ // sample: n picks without replacement (requires discrete set)
83
+ sample: (input, { count }) => {
84
+ if (count <= 0) return ''
85
+ if (input.type === 'floatRange') {
86
+ throw new Error('random sample: float ranges are not supported')
87
+ }
88
+ let values
89
+ if (input.type === 'range') {
90
+ const { min, max } = input.range
91
+ // build discrete list
92
+ values = []
93
+ for (let i = min; i <= max; i++) values.push(String(i))
94
+ } else {
95
+ values = input.items.slice()
96
+ }
97
+ if (!values.length) throw new Error('random: no values to sample from')
98
+ const n = Math.min(count, values.length)
99
+ return shuffleArray(values).slice(0, n).join("\n")
100
+ },
101
+
102
+ // shuffle: shuffle all values (requires discrete set)
103
+ shuffle: (input) => {
104
+ if (input.type === 'floatRange') {
105
+ throw new Error('random shuffle: float ranges are not supported')
106
+ }
107
+ let values
108
+ if (input.type === 'range') {
109
+ const { min, max } = input.range
110
+ values = []
111
+ for (let i = min; i <= max; i++) values.push(String(i))
112
+ } else {
113
+ values = input.items.slice()
114
+ }
115
+ if (!values.length) return ''
116
+ return shuffleArray(values).join(' ')
117
+ },
118
+
119
+ // uniform: numeric uniform in a..b or two numbers a b
120
+ uniform: (input) => {
121
+ let range
122
+ if (input.type === 'range' || input.type === 'floatRange') {
123
+ range = input.range
124
+ } else if (input.items.length === 2) {
125
+ const a = toNumber(input.items[0])
126
+ const b = toNumber(input.items[1])
127
+ if (!Number.isFinite(a) || !Number.isFinite(b)) throw new Error('random uniform: need a numeric range')
128
+ range = { min: Math.min(a, b), max: Math.max(a, b), isInt: isInt(a) && isInt(b) }
129
+ } else {
130
+ throw new Error('random uniform: provide range like 1..10 or two numbers')
131
+ }
132
+ if (range.isInt) return String(randInt(range.min, range.max))
133
+ return String(randFloat(range.min, range.max))
134
+ }
135
+ }
136
+
137
+ module.exports = async function (node, args, ctx) {
138
+ let params = (args || '').trim()
139
+ const m = params.match(/^(choices|choice|sample|shuffle|uniform)?\s*(.*)$/)
140
+
141
+ let method = 'choice'
142
+ if (m && m[1]) {
143
+ method = m[1]
144
+ params = m[2]
145
+ }
146
+
147
+ // Tokenize respecting quotes
148
+ let tokens = tokenize(params)
149
+
150
+ // If nothing provided -> error early in read step
151
+
152
+ // For choices/sample extract count
153
+ let count = null
154
+ if (method === 'choices' || method === 'sample') {
155
+ if (tokens.length === 0) throw new Error(`random ${method}: count and values are required`)
156
+ count = parseInt(tokens[0], 10)
157
+ if (!Number.isFinite(count) || count < 0) throw new Error(`random ${method}: invalid count`)
158
+ tokens = tokens.slice(1)
159
+ }
160
+
161
+ // Expand tokens: @file, ranges, plain values
162
+ // We allow mixing files and discrete values; range tokens will be handled below.
163
+ // Collect items and detect single-range when appropriate.
164
+
165
+ // Helper to read lines from file token
166
+ const readFileToken = async (tok) => {
167
+ const content = await ctx.read(tok.slice(1))
168
+ return String(content).split(/\r?\n/).map(s => s.trim()).filter(Boolean)
169
+ }
170
+
171
+ // Determine if we have a single token that is a range
172
+ let singleRange = null
173
+ if (tokens.length === 1) {
174
+ singleRange = parseRange(tokens[0])
175
+ }
176
+
177
+ let input
178
+ if (singleRange) {
179
+ input = singleRange.isInt ? { type: 'range', range: singleRange } : { type: 'floatRange', range: singleRange }
180
+ } else if (tokens.length === 1 && tokens[0].startsWith('@')) {
181
+ const lines = await readFileToken(tokens[0])
182
+ input = { type: 'list', items: lines }
183
+ } else {
184
+ // Build items list, expanding @file and integer ranges; float ranges in a list are not supported
185
+ const items = []
186
+ for (const tok of tokens) {
187
+ if (tok.startsWith('@')) {
188
+ const lines = await readFileToken(tok)
189
+ items.push(...lines)
190
+ } else {
191
+ const r = parseRange(tok)
192
+ if (r) {
193
+ if (!r.isInt) throw new Error('random: float ranges cannot be mixed with discrete values')
194
+ for (let i = r.min; i <= r.max; i++) items.push(String(i))
195
+ } else {
196
+ items.push(tok)
197
+ }
198
+ }
199
+ }
200
+ input = { type: 'list', items }
201
+ }
202
+
203
+ return {
204
+ type: 'text',
205
+ read: async () => {
206
+ // If no params provided
207
+ if (!input || (input.type === 'list' && input.items.length === 0)) {
208
+ throw new Error('random: no values provided')
209
+ }
210
+ const fn = methods[method]
211
+ if (!fn) throw new Error(`random: unsupported method ${method}`)
212
+ try {
213
+ const result = (method === 'choices' || method === 'sample')
214
+ ? fn(input, { count })
215
+ : fn(input)
216
+ return String(result)
217
+ } catch (e) {
218
+ return `Error: ${e.message}`
219
+ }
220
+ }
221
+ }
222
+ }
@@ -1,4 +1,4 @@
1
- {
1
+ {
2
2
  "description": "Execute a shell command",
3
3
  "parameters": {
4
4
  "type": "object",
@@ -10,4 +10,4 @@
10
10
  },
11
11
  "required": ["text"]
12
12
  }
13
- }
13
+ }
package/src/text.proc.js CHANGED
@@ -5,7 +5,7 @@ module.exports = async function(node, args, ctx) {
5
5
  return {
6
6
  ...node,
7
7
  type: "text",
8
- read : async () => ctx.read(node.name)
8
+ read : async () => ctx.read(node.fullname)
9
9
  }
10
10
 
11
11
  }