otoro-cli 1.0.0 → 1.1.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/bin/otoro.js +39 -8
- package/lib/config.js +114 -13
- package/package.json +1 -1
package/bin/otoro.js
CHANGED
|
@@ -1,8 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
const { program } = require('commander')
|
|
3
|
-
const {
|
|
4
|
-
const { runTask } = require('../lib/task')
|
|
5
|
-
const { initConfig, getConfig } = require('../lib/config')
|
|
3
|
+
const { initConfig, login, logout, isLoggedIn, requireAuth } = require('../lib/config')
|
|
6
4
|
const chalk = require('chalk')
|
|
7
5
|
const pkg = require('../package.json')
|
|
8
6
|
|
|
@@ -11,12 +9,26 @@ program
|
|
|
11
9
|
.description('Otoro AGI — AI coding assistant')
|
|
12
10
|
.version(pkg.version)
|
|
13
11
|
|
|
12
|
+
program
|
|
13
|
+
.command('login')
|
|
14
|
+
.description('Sign in with your Otoro account')
|
|
15
|
+
.action(async () => {
|
|
16
|
+
await login()
|
|
17
|
+
})
|
|
18
|
+
|
|
19
|
+
program
|
|
20
|
+
.command('logout')
|
|
21
|
+
.description('Sign out and clear credentials')
|
|
22
|
+
.action(async () => {
|
|
23
|
+
await logout()
|
|
24
|
+
})
|
|
25
|
+
|
|
14
26
|
program
|
|
15
27
|
.command('init')
|
|
16
28
|
.description('Initialize Otoro in this project')
|
|
17
29
|
.action(async () => {
|
|
18
30
|
await initConfig()
|
|
19
|
-
console.log(chalk.green('
|
|
31
|
+
console.log(chalk.green(' Run `otoro` to start coding.\n'))
|
|
20
32
|
})
|
|
21
33
|
|
|
22
34
|
program
|
|
@@ -24,6 +36,8 @@ program
|
|
|
24
36
|
.alias('c')
|
|
25
37
|
.description('Start interactive chat')
|
|
26
38
|
.action(async () => {
|
|
39
|
+
requireAuth()
|
|
40
|
+
const { startChat } = require('../lib/chat')
|
|
27
41
|
await startChat()
|
|
28
42
|
})
|
|
29
43
|
|
|
@@ -31,6 +45,7 @@ program
|
|
|
31
45
|
.command('ask <prompt...>')
|
|
32
46
|
.description('Ask Otoro a single question')
|
|
33
47
|
.action(async (prompt) => {
|
|
48
|
+
requireAuth()
|
|
34
49
|
const { askOnce } = require('../lib/chat')
|
|
35
50
|
await askOnce(prompt.join(' '))
|
|
36
51
|
})
|
|
@@ -39,6 +54,8 @@ program
|
|
|
39
54
|
.command('run <task...>')
|
|
40
55
|
.description('Run a coding task (write + execute + verify)')
|
|
41
56
|
.action(async (task) => {
|
|
57
|
+
requireAuth()
|
|
58
|
+
const { runTask } = require('../lib/task')
|
|
42
59
|
await runTask(task.join(' '))
|
|
43
60
|
})
|
|
44
61
|
|
|
@@ -46,14 +63,16 @@ program
|
|
|
46
63
|
.command('image <prompt...>')
|
|
47
64
|
.description('Generate an image')
|
|
48
65
|
.action(async (prompt) => {
|
|
66
|
+
requireAuth()
|
|
49
67
|
const { generateImage } = require('../lib/image')
|
|
50
68
|
await generateImage(prompt.join(' '))
|
|
51
69
|
})
|
|
52
70
|
|
|
53
71
|
program
|
|
54
72
|
.command('start')
|
|
55
|
-
.description('Start Otoro agent daemon — connects to server
|
|
73
|
+
.description('Start Otoro agent daemon — connects to server for remote tasks')
|
|
56
74
|
.action(async () => {
|
|
75
|
+
requireAuth()
|
|
57
76
|
const { startAgent } = require('../lib/agent')
|
|
58
77
|
await startAgent()
|
|
59
78
|
})
|
|
@@ -62,8 +81,9 @@ program
|
|
|
62
81
|
.command('status')
|
|
63
82
|
.description('Check connected agents and server status')
|
|
64
83
|
.action(async () => {
|
|
84
|
+
requireAuth()
|
|
85
|
+
const config = require('../lib/config').getConfig()
|
|
65
86
|
const http = require('http')
|
|
66
|
-
const config = getConfig()
|
|
67
87
|
const url = `${config.gpu_url}/v1/agents`
|
|
68
88
|
http.get(url, { headers: { 'Authorization': `Bearer ${config.api_key}` } }, (res) => {
|
|
69
89
|
let data = ''
|
|
@@ -85,9 +105,20 @@ program
|
|
|
85
105
|
}).on('error', (e) => console.log(chalk.red(` Error: ${e.message}`)))
|
|
86
106
|
})
|
|
87
107
|
|
|
88
|
-
// Default: start
|
|
108
|
+
// Default: no args → check login then start chat
|
|
89
109
|
if (process.argv.length <= 2) {
|
|
90
|
-
|
|
110
|
+
if (!isLoggedIn()) {
|
|
111
|
+
console.log(chalk.cyan.bold('\n 🐙 Otoro AGI\n'))
|
|
112
|
+
console.log(chalk.yellow(' Not logged in. Run:\n'))
|
|
113
|
+
console.log(chalk.white(' otoro login — Sign in with your Otoro account'))
|
|
114
|
+
console.log(chalk.white(' otoro init — Initialize a project'))
|
|
115
|
+
console.log(chalk.white(' otoro — Start coding\n'))
|
|
116
|
+
console.log(chalk.gray(' Don\'t have an account? Sign up at https://otoroagi.com\n'))
|
|
117
|
+
} else {
|
|
118
|
+
requireAuth()
|
|
119
|
+
const { startChat } = require('../lib/chat')
|
|
120
|
+
startChat()
|
|
121
|
+
}
|
|
91
122
|
} else {
|
|
92
123
|
program.parse()
|
|
93
124
|
}
|
package/lib/config.js
CHANGED
|
@@ -3,6 +3,8 @@ const path = require('path')
|
|
|
3
3
|
const os = require('os')
|
|
4
4
|
const chalk = require('chalk')
|
|
5
5
|
const readline = require('readline')
|
|
6
|
+
const https = require('https')
|
|
7
|
+
const http = require('http')
|
|
6
8
|
|
|
7
9
|
const CONFIG_DIR = path.join(os.homedir(), '.otoro')
|
|
8
10
|
const CONFIG_FILE = path.join(CONFIG_DIR, 'config.json')
|
|
@@ -10,8 +12,10 @@ const PROJECT_FILE = '.otoro.json'
|
|
|
10
12
|
|
|
11
13
|
const DEFAULT_CONFIG = {
|
|
12
14
|
api_url: 'https://otoroagi.com/api/otoro',
|
|
13
|
-
gpu_url: '
|
|
14
|
-
api_key: '
|
|
15
|
+
gpu_url: '',
|
|
16
|
+
api_key: '',
|
|
17
|
+
user_id: '',
|
|
18
|
+
email: '',
|
|
15
19
|
model: 'qwen-coder',
|
|
16
20
|
theme: 'dark',
|
|
17
21
|
}
|
|
@@ -27,28 +31,125 @@ function getConfig() {
|
|
|
27
31
|
|
|
28
32
|
function saveConfig(config) {
|
|
29
33
|
fs.mkdirSync(CONFIG_DIR, { recursive: true })
|
|
30
|
-
fs.writeFileSync(CONFIG_FILE, JSON.stringify(config, null, 2))
|
|
34
|
+
fs.writeFileSync(CONFIG_FILE, JSON.stringify(config, null, 2), { mode: 0o600 })
|
|
31
35
|
}
|
|
32
36
|
|
|
33
|
-
|
|
37
|
+
function isLoggedIn() {
|
|
34
38
|
const config = getConfig()
|
|
39
|
+
return !!(config.api_key && config.user_id)
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function requireAuth() {
|
|
43
|
+
if (!isLoggedIn()) {
|
|
44
|
+
console.log(chalk.red('\n ✗ Not logged in. Run `otoro login` first.\n'))
|
|
45
|
+
process.exit(1)
|
|
46
|
+
}
|
|
47
|
+
return getConfig()
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
async function login() {
|
|
35
51
|
const rl = readline.createInterface({ input: process.stdin, output: process.stdout })
|
|
36
52
|
const ask = (q) => new Promise(r => rl.question(q, r))
|
|
37
53
|
|
|
38
|
-
console.log(chalk.cyan.bold('\n 🐙 Otoro AGI
|
|
54
|
+
console.log(chalk.cyan.bold('\n 🐙 Otoro AGI — Login\n'))
|
|
55
|
+
console.log(chalk.gray(' Sign in with your Otoro account (otoroagi.com)\n'))
|
|
56
|
+
|
|
57
|
+
const email = await ask(chalk.white(' Email: '))
|
|
58
|
+
const password = await ask(chalk.white(' Password: '))
|
|
59
|
+
|
|
60
|
+
if (!email.trim() || !password.trim()) {
|
|
61
|
+
console.log(chalk.red('\n ✗ Email and password are required.\n'))
|
|
62
|
+
rl.close()
|
|
63
|
+
return
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
console.log(chalk.gray('\n Authenticating...'))
|
|
67
|
+
|
|
68
|
+
try {
|
|
69
|
+
// Authenticate with Supabase via the website
|
|
70
|
+
const result = await new Promise((resolve, reject) => {
|
|
71
|
+
const body = JSON.stringify({ email: email.trim(), password: password.trim() })
|
|
72
|
+
const req = https.request('https://otoroagi.com/api/otoro/auth', {
|
|
73
|
+
method: 'POST',
|
|
74
|
+
headers: { 'Content-Type': 'application/json' },
|
|
75
|
+
timeout: 15000,
|
|
76
|
+
}, (res) => {
|
|
77
|
+
let data = ''
|
|
78
|
+
res.on('data', c => data += c)
|
|
79
|
+
res.on('end', () => {
|
|
80
|
+
try { resolve(JSON.parse(data)) }
|
|
81
|
+
catch { reject(new Error('Invalid response from server')) }
|
|
82
|
+
})
|
|
83
|
+
})
|
|
84
|
+
req.on('error', reject)
|
|
85
|
+
req.on('timeout', () => { req.destroy(); reject(new Error('Connection timed out')) })
|
|
86
|
+
req.write(body)
|
|
87
|
+
req.end()
|
|
88
|
+
})
|
|
89
|
+
|
|
90
|
+
if (result.error) {
|
|
91
|
+
console.log(chalk.red(`\n ✗ ${result.error}\n`))
|
|
92
|
+
rl.close()
|
|
93
|
+
return
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
if (result.success && result.api_key) {
|
|
97
|
+
const config = getConfig()
|
|
98
|
+
config.api_key = result.api_key
|
|
99
|
+
config.user_id = result.user_id
|
|
100
|
+
config.email = email.trim()
|
|
101
|
+
config.gpu_url = result.gpu_url || config.gpu_url
|
|
102
|
+
saveConfig(config)
|
|
103
|
+
|
|
104
|
+
console.log(chalk.green(`\n ✓ Logged in as ${email.trim()}`))
|
|
105
|
+
console.log(chalk.gray(` Config saved to ${CONFIG_FILE}\n`))
|
|
106
|
+
console.log(chalk.cyan(' Get started:'))
|
|
107
|
+
console.log(chalk.gray(' otoro — Interactive chat'))
|
|
108
|
+
console.log(chalk.gray(' otoro start — Agent daemon (remote tasks)'))
|
|
109
|
+
console.log(chalk.gray(' otoro ask "?" — Quick question\n'))
|
|
110
|
+
} else {
|
|
111
|
+
console.log(chalk.red('\n ✗ Login failed. Check your credentials.\n'))
|
|
112
|
+
}
|
|
113
|
+
} catch (e) {
|
|
114
|
+
console.log(chalk.red(`\n ✗ ${e.message}\n`))
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
rl.close()
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
async function logout() {
|
|
121
|
+
if (fs.existsSync(CONFIG_FILE)) {
|
|
122
|
+
const config = getConfig()
|
|
123
|
+
config.api_key = ''
|
|
124
|
+
config.user_id = ''
|
|
125
|
+
config.email = ''
|
|
126
|
+
saveConfig(config)
|
|
127
|
+
console.log(chalk.green('\n ✓ Logged out. Credentials cleared.\n'))
|
|
128
|
+
} else {
|
|
129
|
+
console.log(chalk.gray('\n Already logged out.\n'))
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
async function initConfig() {
|
|
134
|
+
const config = requireAuth()
|
|
135
|
+
const rl = readline.createInterface({ input: process.stdin, output: process.stdout })
|
|
136
|
+
const ask = (q) => new Promise(r => rl.question(q, r))
|
|
39
137
|
|
|
40
|
-
|
|
41
|
-
|
|
138
|
+
console.log(chalk.cyan.bold('\n 🐙 Otoro AGI — Project Setup\n'))
|
|
139
|
+
console.log(chalk.gray(` Logged in as: ${config.email}`))
|
|
140
|
+
console.log(chalk.gray(` Directory: ${process.cwd()}\n`))
|
|
42
141
|
|
|
43
|
-
const
|
|
44
|
-
if (key.trim()) config.api_key = key.trim()
|
|
142
|
+
const name = await ask(chalk.gray(` Project name [${path.basename(process.cwd())}]: `))
|
|
45
143
|
|
|
46
144
|
rl.close()
|
|
47
|
-
saveConfig(config)
|
|
48
145
|
|
|
49
|
-
|
|
50
|
-
|
|
146
|
+
const projectConfig = {
|
|
147
|
+
name: name.trim() || path.basename(process.cwd()),
|
|
148
|
+
created: new Date().toISOString(),
|
|
149
|
+
user_id: config.user_id,
|
|
150
|
+
}
|
|
51
151
|
fs.writeFileSync(PROJECT_FILE, JSON.stringify(projectConfig, null, 2))
|
|
152
|
+
console.log(chalk.green(`\n ✓ Project "${projectConfig.name}" initialized.\n`))
|
|
52
153
|
}
|
|
53
154
|
|
|
54
|
-
module.exports = { getConfig, saveConfig, initConfig }
|
|
155
|
+
module.exports = { getConfig, saveConfig, initConfig, login, logout, isLoggedIn, requireAuth }
|
package/package.json
CHANGED