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.
Files changed (3) hide show
  1. package/bin/otoro.js +39 -8
  2. package/lib/config.js +114 -13
  3. 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 { startChat } = require('../lib/chat')
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('✓ Otoro initialized. Run `otoro` to start coding.'))
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, waits for remote tasks')
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 interactive chat
108
+ // Default: no args → check login then start chat
89
109
  if (process.argv.length <= 2) {
90
- startChat()
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: 'http://100.124.135.21:8000',
14
- api_key: 'otoro-secret',
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
- async function initConfig() {
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 Setup\n'))
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
- const url = await ask(chalk.gray(` GPU URL [${config.gpu_url}]: `))
41
- if (url.trim()) config.gpu_url = url.trim()
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 key = await ask(chalk.gray(` API Key [${config.api_key}]: `))
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
- // Create project config
50
- const projectConfig = { name: path.basename(process.cwd()), created: new Date().toISOString() }
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "otoro-cli",
3
- "version": "1.0.0",
3
+ "version": "1.1.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": {