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 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
- module.exports = { readFile, writeFile, editFile, listFiles, runCommand, searchCode, processToolCalls }
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "otoro-cli",
3
- "version": "1.2.0",
3
+ "version": "1.3.0",
4
4
  "description": "Otoro AGI — AI coding assistant for your terminal. Code, generate images, execute tasks, and control your projects remotely.",
5
5
  "main": "index.js",
6
6
  "bin": {