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 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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "tune-basic-toolset",
3
- "version": "0.1.2",
3
+ "version": "0.1.3",
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
  }