otoro-cli 1.2.0 → 1.3.0
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/lib/agent.js +44 -2
- package/lib/tools.js +69 -1
- package/package.json +1 -1
package/lib/agent.js
CHANGED
|
@@ -3,7 +3,7 @@ const os = require('os')
|
|
|
3
3
|
const path = require('path')
|
|
4
4
|
const chalk = require('chalk')
|
|
5
5
|
const { getConfig } = require('./config')
|
|
6
|
-
const { readFile, writeFile, editFile, listFiles, runCommand, searchCode } = require('./tools')
|
|
6
|
+
const { readFile, writeFile, editFile, listFiles, runCommand, searchCode, openApp, openUrl, getSystemInfo, takeScreenshot } = require('./tools')
|
|
7
7
|
const { chatCompletion } = require('./api')
|
|
8
8
|
|
|
9
9
|
class OtoroAgent {
|
|
@@ -117,6 +117,21 @@ class OtoroAgent {
|
|
|
117
117
|
console.log(chalk.yellow(` ⚡ Running: ${payload.cmd}`))
|
|
118
118
|
result = runCommand(payload.cmd, payload.timeout || 30000)
|
|
119
119
|
break
|
|
120
|
+
case 'open_app':
|
|
121
|
+
console.log(chalk.yellow(` 🚀 Opening: ${payload.app}`))
|
|
122
|
+
result = openApp(payload.app)
|
|
123
|
+
break
|
|
124
|
+
case 'open_url':
|
|
125
|
+
console.log(chalk.yellow(` 🌐 Opening: ${payload.url}`))
|
|
126
|
+
result = openUrl(payload.url)
|
|
127
|
+
break
|
|
128
|
+
case 'screenshot':
|
|
129
|
+
console.log(chalk.yellow(` 📸 Taking screenshot...`))
|
|
130
|
+
result = takeScreenshot()
|
|
131
|
+
break
|
|
132
|
+
case 'system_info':
|
|
133
|
+
result = getSystemInfo()
|
|
134
|
+
break
|
|
120
135
|
case 'list_files':
|
|
121
136
|
result = listFiles(payload.dir || '.', payload.pattern || '')
|
|
122
137
|
break
|
|
@@ -158,9 +173,15 @@ Available tools (use XML tags in your response):
|
|
|
158
173
|
- <tool:read path="file"/> — read a file
|
|
159
174
|
- <tool:write path="file">content</tool:write> — write/create a file
|
|
160
175
|
- <tool:edit path="file" old="old text" new="new text"/> — edit a file
|
|
161
|
-
- <tool:run cmd="command"/> — run a shell command
|
|
176
|
+
- <tool:run cmd="command"/> — run a shell command (works on Mac/Windows/Linux)
|
|
162
177
|
- <tool:search pattern="regex"/> — search code
|
|
178
|
+
- <tool:open app="AppName"/> — open an application (Blender, Discord, VS Code, etc.)
|
|
179
|
+
- <tool:url href="https://..."/> — open a URL in the browser
|
|
180
|
+
- <tool:screenshot/> — take a screenshot of the screen
|
|
181
|
+
|
|
182
|
+
You are running LOCALLY on the user's machine. You have full access to their filesystem and can run any command. You can open apps, edit code, run builds, start servers, and interact with their development environment.
|
|
163
183
|
|
|
184
|
+
Platform: ${process.platform} (${process.arch})
|
|
164
185
|
Project context:
|
|
165
186
|
${projectContext}
|
|
166
187
|
|
|
@@ -238,6 +259,27 @@ Be direct. Execute tasks immediately. Don't ask for permission — just do it.`
|
|
|
238
259
|
results.push({ type: 'search', pattern: match[1], ...r })
|
|
239
260
|
}
|
|
240
261
|
|
|
262
|
+
// Process <tool:open>
|
|
263
|
+
for (const match of response.matchAll(/<tool:open\s+app="([^"]+)"\s*\/>/g)) {
|
|
264
|
+
console.log(chalk.yellow(` 🚀 Opening: ${match[1]}`))
|
|
265
|
+
const r = openApp(match[1])
|
|
266
|
+
results.push({ type: 'open_app', app: match[1], ...r })
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
// Process <tool:url>
|
|
270
|
+
for (const match of response.matchAll(/<tool:url\s+href="([^"]+)"\s*\/>/g)) {
|
|
271
|
+
console.log(chalk.yellow(` 🌐 Opening: ${match[1]}`))
|
|
272
|
+
const r = openUrl(match[1])
|
|
273
|
+
results.push({ type: 'open_url', url: match[1], ...r })
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
// Process <tool:screenshot>
|
|
277
|
+
for (const match of response.matchAll(/<tool:screenshot\s*\/>/g)) {
|
|
278
|
+
console.log(chalk.yellow(` 📸 Screenshot...`))
|
|
279
|
+
const r = takeScreenshot()
|
|
280
|
+
results.push({ type: 'screenshot', ...r })
|
|
281
|
+
}
|
|
282
|
+
|
|
241
283
|
return results
|
|
242
284
|
}
|
|
243
285
|
}
|
package/lib/tools.js
CHANGED
|
@@ -102,4 +102,72 @@ function processToolCalls(response) {
|
|
|
102
102
|
return actions
|
|
103
103
|
}
|
|
104
104
|
|
|
105
|
-
|
|
105
|
+
// Platform-aware app launching
|
|
106
|
+
function openApp(appName) {
|
|
107
|
+
const platform = process.platform
|
|
108
|
+
try {
|
|
109
|
+
let cmd
|
|
110
|
+
if (platform === 'darwin') {
|
|
111
|
+
cmd = `open -a "${appName}"`
|
|
112
|
+
} else if (platform === 'win32') {
|
|
113
|
+
cmd = `start "" "${appName}"`
|
|
114
|
+
} else {
|
|
115
|
+
// Linux — try common approaches
|
|
116
|
+
cmd = `${appName.toLowerCase()} &`
|
|
117
|
+
}
|
|
118
|
+
execSync(cmd, { timeout: 5000, stdio: 'ignore' })
|
|
119
|
+
return { success: true, app: appName, platform }
|
|
120
|
+
} catch (e) {
|
|
121
|
+
return { success: false, error: e.message, platform }
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
function openUrl(url) {
|
|
126
|
+
const platform = process.platform
|
|
127
|
+
try {
|
|
128
|
+
let cmd
|
|
129
|
+
if (platform === 'darwin') cmd = `open "${url}"`
|
|
130
|
+
else if (platform === 'win32') cmd = `start "" "${url}"`
|
|
131
|
+
else cmd = `xdg-open "${url}"`
|
|
132
|
+
execSync(cmd, { timeout: 5000, stdio: 'ignore' })
|
|
133
|
+
return { success: true, url }
|
|
134
|
+
} catch (e) {
|
|
135
|
+
return { success: false, error: e.message }
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
function getSystemInfo() {
|
|
140
|
+
const os = require('os')
|
|
141
|
+
return {
|
|
142
|
+
platform: process.platform,
|
|
143
|
+
arch: process.arch,
|
|
144
|
+
hostname: os.hostname(),
|
|
145
|
+
user: os.userInfo().username,
|
|
146
|
+
home: os.homedir(),
|
|
147
|
+
cwd: process.cwd(),
|
|
148
|
+
node: process.version,
|
|
149
|
+
cpus: os.cpus().length,
|
|
150
|
+
memory: Math.round(os.totalmem() / 1024 / 1024 / 1024) + 'GB',
|
|
151
|
+
freeMemory: Math.round(os.freemem() / 1024 / 1024 / 1024) + 'GB',
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
function takeScreenshot() {
|
|
156
|
+
const platform = process.platform
|
|
157
|
+
const tmpFile = path.join(require('os').tmpdir(), `otoro-screenshot-${Date.now()}.png`)
|
|
158
|
+
try {
|
|
159
|
+
let cmd
|
|
160
|
+
if (platform === 'darwin') cmd = `screencapture -x "${tmpFile}"`
|
|
161
|
+
else if (platform === 'win32') cmd = `powershell -command "Add-Type -AssemblyName System.Windows.Forms; [System.Windows.Forms.Screen]::PrimaryScreen | ForEach-Object { $bitmap = New-Object System.Drawing.Bitmap($_.Bounds.Width, $_.Bounds.Height); $graphics = [System.Drawing.Graphics]::FromImage($bitmap); $graphics.CopyFromScreen($_.Bounds.Location, [System.Drawing.Point]::Empty, $_.Bounds.Size); $bitmap.Save('${tmpFile}'); }"`
|
|
162
|
+
else cmd = `import -window root "${tmpFile}" 2>/dev/null || gnome-screenshot -f "${tmpFile}" 2>/dev/null || scrot "${tmpFile}" 2>/dev/null`
|
|
163
|
+
execSync(cmd, { timeout: 10000 })
|
|
164
|
+
if (fs.existsSync(tmpFile)) {
|
|
165
|
+
return { success: true, path: tmpFile, size: fs.statSync(tmpFile).size }
|
|
166
|
+
}
|
|
167
|
+
return { success: false, error: 'Screenshot file not created' }
|
|
168
|
+
} catch (e) {
|
|
169
|
+
return { success: false, error: e.message }
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
module.exports = { readFile, writeFile, editFile, listFiles, runCommand, searchCode, processToolCalls, openApp, openUrl, getSystemInfo, takeScreenshot }
|
package/package.json
CHANGED