tune-basic-toolset 0.1.2 → 0.1.3
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 +67 -2
- package/package.json +1 -1
- package/src/random.proc.js +222 -0
- package/src/sh.schema.json +2 -2
- package/src/text.proc.js +1 -1
package/README.md
CHANGED
|
@@ -8,9 +8,10 @@ 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
|
|
12
|
-
- [append](#append) append to file
|
|
11
|
+
- [patch](#patch) patch file - [append](#append) append to file
|
|
13
12
|
- [sh](#sh) execute shell command
|
|
13
|
+
- [cmd](#cmd) execute Windows cmd command
|
|
14
|
+
- [powershell](#powershell) execute PowerShell command
|
|
14
15
|
- [osa](#osa) manage reminders/notes/calendar (AppleScript/macOS)
|
|
15
16
|
- [jina_r](#jina_r) fetch webpage content
|
|
16
17
|
- [turn](#turn) turn based agent
|
|
@@ -31,6 +32,7 @@ Basic toolset for [Tune](https://github.com/iovdin/tune).
|
|
|
31
32
|
- [head](#head) take first N lines of a file
|
|
32
33
|
- [tail](#tail) take last N lines of a file or LLM payload
|
|
33
34
|
- [slice](#slice) take lines from <start> to <finish> of a file
|
|
35
|
+
- [random](#random) random selection, sampling, shuffling, uniform ranges
|
|
34
36
|
|
|
35
37
|
|
|
36
38
|
## Setup
|
|
@@ -135,6 +137,40 @@ tool_result:
|
|
|
135
137
|
./tools/README.md:* `echo.txt` - to debug variable expansions and context
|
|
136
138
|
```
|
|
137
139
|
|
|
140
|
+
### `cmd`
|
|
141
|
+
Execute Windows cmd command
|
|
142
|
+
```chat
|
|
143
|
+
user: @cmd
|
|
144
|
+
list all files in current directory
|
|
145
|
+
tool_call: cmd
|
|
146
|
+
dir
|
|
147
|
+
tool_result:
|
|
148
|
+
Volume in drive C has no label.
|
|
149
|
+
Volume Serial Number is 1234-5678
|
|
150
|
+
|
|
151
|
+
Directory of C:\project
|
|
152
|
+
|
|
153
|
+
12/01/2023 10:30 AM <DIR> .
|
|
154
|
+
12/01/2023 10:30 AM <DIR> ..
|
|
155
|
+
12/01/2023 09:15 AM 1,024 package.json
|
|
156
|
+
12/01/2023 09:20 AM <DIR> src
|
|
157
|
+
1 File(s) 1,024 bytes
|
|
158
|
+
3 Dir(s) 15,728,640 bytes free
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
### `powershell`
|
|
162
|
+
Execute PowerShell command
|
|
163
|
+
```chat
|
|
164
|
+
user: @powershell
|
|
165
|
+
get system information
|
|
166
|
+
tool_call: powershell
|
|
167
|
+
Get-ComputerInfo | Select-Object WindowsProductName, TotalPhysicalMemory, CsProcessors
|
|
168
|
+
tool_result:
|
|
169
|
+
WindowsProductName : Windows 11 Pro
|
|
170
|
+
TotalPhysicalMemory : 17179869184
|
|
171
|
+
CsProcessors : {Intel(R) Core(TM) i7-10700K CPU @ 3.80GHz}
|
|
172
|
+
```
|
|
173
|
+
|
|
138
174
|
### `osa`
|
|
139
175
|
AppleScript tool, manage reminders, notes, calendar etc on osx
|
|
140
176
|
```chat
|
|
@@ -515,3 +551,32 @@ user:
|
|
|
515
551
|
@{ filename.txt | slice -20 } # last 20 lines
|
|
516
552
|
@{ filename.txt | slice 1 20 } # first 20 lines (like head 20)
|
|
517
553
|
```
|
|
554
|
+
|
|
555
|
+
### `random`
|
|
556
|
+
Random selection, sampling, shuffling, and uniform number generation.
|
|
557
|
+
|
|
558
|
+
Use cases:
|
|
559
|
+
```chat
|
|
560
|
+
user:
|
|
561
|
+
@{| random a b c d }
|
|
562
|
+
@{| random choice a b c d }
|
|
563
|
+
@{| random "choice 1" "choice 2" }
|
|
564
|
+
@{| random choice @path/to/file.txt } # choose 1 line from a file
|
|
565
|
+
@{| random choice 2..30 } # choose 1 from range
|
|
566
|
+
@{| random choice -2.5..7.5 } # floats
|
|
567
|
+
@{| random choices 3 a b c d } # pick 3 with replacment
|
|
568
|
+
@{| random choices 5 @file.txt } # pick 5 lines from file.txt
|
|
569
|
+
@{| random sample 3 a b c d } # pick 3 without replacement
|
|
570
|
+
@{| random sample 10 1..5 } # will return 5 unique numbers
|
|
571
|
+
@{| random shuffle a b c d }
|
|
572
|
+
@{| random shuffle 1..10 }
|
|
573
|
+
@{| random uniform 1..10 } # integers
|
|
574
|
+
@{| random uniform -2.5..7.5 } # floats
|
|
575
|
+
@{| random uniform 10 20 } # two-number form
|
|
576
|
+
|
|
577
|
+
Notes:
|
|
578
|
+
- Quotes are respected for tokens with spaces.
|
|
579
|
+
- Files referenced as @file are expanded to non-empty trimmed lines.
|
|
580
|
+
- Integer ranges like a..b can be mixed with discrete values and files; float ranges cannot be mixed in lists.
|
|
581
|
+
- sample and shuffle require a discrete set; float ranges are not supported there.
|
|
582
|
+
- choices and sample output multiple lines (one item per line).
|
package/package.json
CHANGED
|
@@ -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
|
+
}
|
package/src/sh.schema.json
CHANGED