atlas-terminal 1.0.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/LICENSE +21 -0
- package/README.md +127 -0
- package/cli/commands/ask.js +53 -0
- package/cli/commands/chat.js +109 -0
- package/cli/commands/config.js +47 -0
- package/cli/index.js +28 -0
- package/cli/utils/groq.js +26 -0
- package/cli/utils/system.js +32 -0
- package/package.json +27 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Chris Ispasou - Ispasa
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
# ATLAS CLI š¤
|
|
2
|
+
|
|
3
|
+
A fast, lightweight AI assistant that lives in your terminal. Powered by Groq.
|
|
4
|
+
|
|
5
|
+

|
|
6
|
+

|
|
7
|
+

|
|
8
|
+
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
## Installation
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
npm install -g atlas-cli
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
---
|
|
18
|
+
|
|
19
|
+
## Quick Start
|
|
20
|
+
|
|
21
|
+
### 1. Configure ATLAS with your Groq API key
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
atlas config
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
Get a free Groq API key at [console.groq.com](https://console.groq.com/keys)
|
|
28
|
+
|
|
29
|
+
### 2. Start chatting
|
|
30
|
+
|
|
31
|
+
```bash
|
|
32
|
+
atlas chat
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
---
|
|
36
|
+
|
|
37
|
+
## Commands
|
|
38
|
+
|
|
39
|
+
### `atlas chat`
|
|
40
|
+
Start a back and forth conversation with ATLAS. Conversation memory is maintained throughout the session.
|
|
41
|
+
|
|
42
|
+
```bash
|
|
43
|
+
atlas chat
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
```
|
|
47
|
+
āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
|
|
48
|
+
ATLAS ā type your message, or "exit" to quit
|
|
49
|
+
āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
|
|
50
|
+
|
|
51
|
+
You: what is a REST API?
|
|
52
|
+
ATLAS: A REST API is...
|
|
53
|
+
|
|
54
|
+
You: exit
|
|
55
|
+
ATLAS: Goodbye!
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
---
|
|
59
|
+
|
|
60
|
+
### `atlas ask`
|
|
61
|
+
Ask a single question and get an answer immediately. Perfect for quick lookups without starting a full conversation.
|
|
62
|
+
|
|
63
|
+
```bash
|
|
64
|
+
atlas ask "what is the difference between null and undefined in JavaScript?"
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
Supports piping directly from the terminal:
|
|
68
|
+
|
|
69
|
+
```bash
|
|
70
|
+
cat error.log | atlas ask "what is wrong here?"
|
|
71
|
+
echo "explain this code" | atlas ask --pipe < index.js
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
---
|
|
75
|
+
|
|
76
|
+
### `atlas config`
|
|
77
|
+
First time setup. Saves your Groq API key securely on your machine.
|
|
78
|
+
|
|
79
|
+
```bash
|
|
80
|
+
atlas config
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
Your API key is stored locally at `~/.config/atlas-cli/config.json` and never sent anywhere except directly to Groq.
|
|
84
|
+
|
|
85
|
+
---
|
|
86
|
+
|
|
87
|
+
## Requirements
|
|
88
|
+
|
|
89
|
+
- Node.js 18 or higher
|
|
90
|
+
- A free Groq API key from [console.groq.com](https://console.groq.com/keys)
|
|
91
|
+
|
|
92
|
+
---
|
|
93
|
+
|
|
94
|
+
## Tech Stack
|
|
95
|
+
|
|
96
|
+
- **Node.js** ā runtime
|
|
97
|
+
- **Commander.js** ā CLI command handling
|
|
98
|
+
- **Groq SDK** ā AI responses via llama-3.3-70b-versatile
|
|
99
|
+
- **Chalk** ā terminal colors
|
|
100
|
+
- **Ora** ā loading spinners
|
|
101
|
+
- **Inquirer** ā interactive config prompts
|
|
102
|
+
- **Conf** ā local config storage
|
|
103
|
+
|
|
104
|
+
---
|
|
105
|
+
|
|
106
|
+
## Roadmap
|
|
107
|
+
|
|
108
|
+
- [x] `atlas chat` ā conversational mode
|
|
109
|
+
- [x] `atlas ask` ā single question mode
|
|
110
|
+
- [x] `atlas config` ā API key setup
|
|
111
|
+
- [ ] Streaming responses
|
|
112
|
+
- [ ] `atlas chat` with file context (`atlas chat --file index.js`)
|
|
113
|
+
- [ ] Web search tool
|
|
114
|
+
- [ ] Read and write files
|
|
115
|
+
- [ ] TTS voice mode
|
|
116
|
+
- [ ] User accounts and hosted backend
|
|
117
|
+
- [ ] Subscription tiers (Free / Pro / Ultimate)
|
|
118
|
+
|
|
119
|
+
---
|
|
120
|
+
|
|
121
|
+
## License
|
|
122
|
+
|
|
123
|
+
MIT ā feel free to use and modify.
|
|
124
|
+
|
|
125
|
+
---
|
|
126
|
+
|
|
127
|
+
Built by [Chris](https://github.com/PSGtatitos)
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import chalk from 'chalk'
|
|
2
|
+
import ora from 'ora'
|
|
3
|
+
import { askGroq } from '../utils/groq.js'
|
|
4
|
+
import Conf from 'conf'
|
|
5
|
+
|
|
6
|
+
const config = new Conf({ projectName: 'atlas-cli' })
|
|
7
|
+
|
|
8
|
+
export async function askCommand(question) {
|
|
9
|
+
// Check config exists
|
|
10
|
+
if (!config.get('groqApiKey')) {
|
|
11
|
+
console.log(chalk.red('No API key found. Run atlas config first.'))
|
|
12
|
+
process.exit(1)
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
// Handle piped input
|
|
16
|
+
if (!question) {
|
|
17
|
+
const chunks = []
|
|
18
|
+
for await (const chunk of process.stdin) {
|
|
19
|
+
chunks.push(chunk)
|
|
20
|
+
}
|
|
21
|
+
question = Buffer.concat(chunks).toString().trim()
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
if (!question) {
|
|
25
|
+
console.log(chalk.red('No question provided.'))
|
|
26
|
+
process.exit(1)
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const spinner = ora('Thinking...').start()
|
|
30
|
+
|
|
31
|
+
try {
|
|
32
|
+
spinner.stop()
|
|
33
|
+
process.stdout.write(chalk.blue('ATLAS: '))
|
|
34
|
+
|
|
35
|
+
const stream = await askGroq(question, [])
|
|
36
|
+
|
|
37
|
+
for await (const chunk of stream) {
|
|
38
|
+
const token = chunk.choices[0]?.delta?.content || ''
|
|
39
|
+
process.stdout.write(token)
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
console.log('\n')
|
|
43
|
+
|
|
44
|
+
} catch (error) {
|
|
45
|
+
spinner.stop()
|
|
46
|
+
if (error.message?.includes('API key')) {
|
|
47
|
+
console.log(chalk.red('Invalid API key. Run atlas config to update it.'))
|
|
48
|
+
} else {
|
|
49
|
+
console.log(chalk.red(`Error: ${error.message}`))
|
|
50
|
+
}
|
|
51
|
+
process.exit(1)
|
|
52
|
+
}
|
|
53
|
+
}
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import readline from 'readline'
|
|
2
|
+
import chalk from 'chalk'
|
|
3
|
+
import { askGroq } from '../utils/groq.js'
|
|
4
|
+
import { handleSystemCommand } from '../utils/system.js'
|
|
5
|
+
import Conf from 'conf'
|
|
6
|
+
|
|
7
|
+
const config = new Conf({ projectName: 'atlas-cli' })
|
|
8
|
+
|
|
9
|
+
const STOP_PHRASES = [
|
|
10
|
+
'goodbye atlas',
|
|
11
|
+
'goodbye',
|
|
12
|
+
'exit',
|
|
13
|
+
'quit',
|
|
14
|
+
'ok that is all for today',
|
|
15
|
+
"that's all for today",
|
|
16
|
+
"ok that's all"
|
|
17
|
+
]
|
|
18
|
+
|
|
19
|
+
const NOISE_WORDS = new Set([
|
|
20
|
+
'', 'huh', 'uh', 'um', 'hmm',
|
|
21
|
+
'ah', 'oh', 'eh', 'a', 'the', 'i', 'it'
|
|
22
|
+
])
|
|
23
|
+
|
|
24
|
+
const MIN_INPUT_LENGTH = 2
|
|
25
|
+
|
|
26
|
+
export async function chatCommand() {
|
|
27
|
+
// Check config exists
|
|
28
|
+
if (!config.get('groqApiKey')) {
|
|
29
|
+
console.log(chalk.red('No API key found. Run atlas config first.'))
|
|
30
|
+
process.exit(1)
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const conversationHistory = []
|
|
34
|
+
|
|
35
|
+
console.log(chalk.cyan('ā'.repeat(50)))
|
|
36
|
+
console.log(chalk.cyan(' ATLAS ā type your message, or "exit" to quit'))
|
|
37
|
+
console.log(chalk.cyan('ā'.repeat(50)) + '\n')
|
|
38
|
+
|
|
39
|
+
const rl = readline.createInterface({
|
|
40
|
+
input: process.stdin,
|
|
41
|
+
output: process.stdout
|
|
42
|
+
})
|
|
43
|
+
|
|
44
|
+
const askQuestion = () => {
|
|
45
|
+
rl.question(chalk.green('You: '), async (input) => {
|
|
46
|
+
const userInput = input.trim()
|
|
47
|
+
|
|
48
|
+
// Empty input
|
|
49
|
+
if (!userInput) {
|
|
50
|
+
return askQuestion()
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// Stop phrases
|
|
54
|
+
if (STOP_PHRASES.some(p => userInput.toLowerCase().includes(p))) {
|
|
55
|
+
console.log(chalk.cyan('\nATLAS: Goodbye!\n'))
|
|
56
|
+
rl.close()
|
|
57
|
+
return
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// Noise/short input
|
|
61
|
+
if (
|
|
62
|
+
userInput.length < MIN_INPUT_LENGTH ||
|
|
63
|
+
NOISE_WORDS.has(userInput.toLowerCase())
|
|
64
|
+
) {
|
|
65
|
+
console.log(chalk.gray('[Skipped] Input too short.\n'))
|
|
66
|
+
return askQuestion()
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// System command check
|
|
70
|
+
const systemResponse = handleSystemCommand(userInput)
|
|
71
|
+
if (systemResponse) {
|
|
72
|
+
console.log(chalk.yellow(`System: ${systemResponse}\n`))
|
|
73
|
+
return askQuestion()
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// Ask Groq
|
|
77
|
+
try {
|
|
78
|
+
process.stdout.write(chalk.blue('ATLAS: '))
|
|
79
|
+
|
|
80
|
+
const stream = await askGroq(userInput, conversationHistory)
|
|
81
|
+
|
|
82
|
+
let fullResponse = ''
|
|
83
|
+
|
|
84
|
+
for await (const chunk of stream) {
|
|
85
|
+
const token = chunk.choices[0]?.delta?.content || ''
|
|
86
|
+
process.stdout.write(token)
|
|
87
|
+
fullResponse += token
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
console.log('\n')
|
|
91
|
+
|
|
92
|
+
// Update conversation history
|
|
93
|
+
conversationHistory.push({ role: 'user', content: userInput })
|
|
94
|
+
conversationHistory.push({ role: 'assistant', content: fullResponse })
|
|
95
|
+
|
|
96
|
+
} catch (error) {
|
|
97
|
+
if (error.message?.includes('API key')) {
|
|
98
|
+
console.log(chalk.red('\nInvalid API key. Run atlas config to update it.\n'))
|
|
99
|
+
} else {
|
|
100
|
+
console.log(chalk.red(`\nError: ${error.message}\n`))
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
askQuestion()
|
|
105
|
+
})
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
askQuestion()
|
|
109
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import inquirer from 'inquirer'
|
|
2
|
+
import chalk from 'chalk'
|
|
3
|
+
import Conf from 'conf'
|
|
4
|
+
import Groq from 'groq-sdk'
|
|
5
|
+
|
|
6
|
+
const config = new Conf({ projectName: 'atlas-cli' })
|
|
7
|
+
|
|
8
|
+
export async function configCommand() {
|
|
9
|
+
console.log(chalk.cyan('\nWelcome to ATLAS setup!\n'))
|
|
10
|
+
|
|
11
|
+
const existing = config.get('groqApiKey')
|
|
12
|
+
if (existing) {
|
|
13
|
+
console.log(chalk.gray(`Current API key: ${existing.slice(0, 8)}...\n`))
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const answers = await inquirer.prompt([
|
|
17
|
+
{
|
|
18
|
+
type: 'password',
|
|
19
|
+
name: 'groqApiKey',
|
|
20
|
+
message: 'Enter your Groq API key (get one at console.groq.com):',
|
|
21
|
+
mask: '*',
|
|
22
|
+
validate: (val) => val.length > 0 ? true : 'API key cannot be empty'
|
|
23
|
+
}
|
|
24
|
+
])
|
|
25
|
+
|
|
26
|
+
// Validate the key by making a test call
|
|
27
|
+
console.log(chalk.gray('\nValidating API key...'))
|
|
28
|
+
|
|
29
|
+
try {
|
|
30
|
+
const groq = new Groq({ apiKey: answers.groqApiKey })
|
|
31
|
+
await groq.chat.completions.create({
|
|
32
|
+
model: 'llama-3.3-70b-versatile',
|
|
33
|
+
messages: [{ role: 'user', content: 'hi' }],
|
|
34
|
+
max_tokens: 1
|
|
35
|
+
})
|
|
36
|
+
|
|
37
|
+
config.set('groqApiKey', answers.groqApiKey)
|
|
38
|
+
|
|
39
|
+
console.log(chalk.green('\nā API key valid'))
|
|
40
|
+
console.log(chalk.green('ā ATLAS is ready'))
|
|
41
|
+
console.log(chalk.cyan('\nRun atlas chat to start.\n'))
|
|
42
|
+
|
|
43
|
+
} catch (error) {
|
|
44
|
+
console.log(chalk.red('\nā Invalid API key. Please check and try again.\n'))
|
|
45
|
+
process.exit(1)
|
|
46
|
+
}
|
|
47
|
+
}
|
package/cli/index.js
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import { program } from 'commander'
|
|
4
|
+
import { chatCommand } from './commands/chat.js'
|
|
5
|
+
import { askCommand } from './commands/ask.js'
|
|
6
|
+
import { configCommand } from './commands/config.js'
|
|
7
|
+
|
|
8
|
+
program
|
|
9
|
+
.name('atlas')
|
|
10
|
+
.description('AI assistant that lives in your terminal')
|
|
11
|
+
.version('1.0.0')
|
|
12
|
+
|
|
13
|
+
program
|
|
14
|
+
.command('chat')
|
|
15
|
+
.description('Start a conversation with ATLAS')
|
|
16
|
+
.action(chatCommand)
|
|
17
|
+
|
|
18
|
+
program
|
|
19
|
+
.command('ask <question>')
|
|
20
|
+
.description('Ask ATLAS a single question')
|
|
21
|
+
.action(askCommand)
|
|
22
|
+
|
|
23
|
+
program
|
|
24
|
+
.command('config')
|
|
25
|
+
.description('Configure ATLAS settings')
|
|
26
|
+
.action(configCommand)
|
|
27
|
+
|
|
28
|
+
program.parse()
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
// groq.js
|
|
2
|
+
import Groq from 'groq-sdk'
|
|
3
|
+
import Conf from 'conf'
|
|
4
|
+
|
|
5
|
+
const config = new Conf({ projectName: 'atlas-cli' })
|
|
6
|
+
|
|
7
|
+
export async function askGroq(text, conversationHistory) {
|
|
8
|
+
const groq = new Groq({
|
|
9
|
+
apiKey: config.get('groqApiKey')
|
|
10
|
+
})
|
|
11
|
+
|
|
12
|
+
const response = await groq.chat.completions.create({
|
|
13
|
+
model: 'llama-3.3-70b-versatile',
|
|
14
|
+
messages: [
|
|
15
|
+
{
|
|
16
|
+
role: 'system',
|
|
17
|
+
content: 'You are ATLAS, an AI assistant in the terminal.'
|
|
18
|
+
},
|
|
19
|
+
...conversationHistory,
|
|
20
|
+
{ role: 'user', content: text }
|
|
21
|
+
],
|
|
22
|
+
stream: true // streaming so responses print token by token
|
|
23
|
+
})
|
|
24
|
+
|
|
25
|
+
return response
|
|
26
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
export function handleSystemCommand(text) {
|
|
2
|
+
const t = text.toLowerCase()
|
|
3
|
+
|
|
4
|
+
const commonSites = {
|
|
5
|
+
youtube: 'https://youtube.com',
|
|
6
|
+
google: 'https://google.com',
|
|
7
|
+
netflix: 'https://netflix.com',
|
|
8
|
+
github: 'https://github.com',
|
|
9
|
+
reddit: 'https://reddit.com',
|
|
10
|
+
twitter: 'https://twitter.com',
|
|
11
|
+
instagram: 'https://instagram.com',
|
|
12
|
+
gmail: 'https://gmail.com',
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
// Common site shortcuts
|
|
16
|
+
for (const [name, url] of Object.entries(commonSites)) {
|
|
17
|
+
if (t.includes(`open ${name}`)) {
|
|
18
|
+
// Will be handled by agent tool in Phase 2
|
|
19
|
+
return `Would open ${url} (system commands coming in v2)`
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
// Search
|
|
24
|
+
if (t.includes('search for') || t.includes('google search')) {
|
|
25
|
+
const query = t.includes('search for')
|
|
26
|
+
? t.split('search for')[1].trim()
|
|
27
|
+
: t.split('google search')[1].trim()
|
|
28
|
+
return `Would search for "${query}" (system commands coming in v2)`
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
return null
|
|
32
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "atlas-terminal",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "AI assistant that lives in your terminal",
|
|
5
|
+
"main": "cli/index.js",
|
|
6
|
+
"bin": {
|
|
7
|
+
"atlas": "./cli/index.js"
|
|
8
|
+
},
|
|
9
|
+
"files": [
|
|
10
|
+
"cli/"
|
|
11
|
+
],
|
|
12
|
+
"type": "module",
|
|
13
|
+
"scripts": {
|
|
14
|
+
"start": "node cli/index.js",
|
|
15
|
+
"dev": "node --watch cli/index.js"
|
|
16
|
+
},
|
|
17
|
+
"keywords": ["ai", "cli", "assistant", "terminal"],
|
|
18
|
+
"license": "MIT",
|
|
19
|
+
"dependencies": {
|
|
20
|
+
"commander": "^12.0.0",
|
|
21
|
+
"chalk": "^5.3.0",
|
|
22
|
+
"ora": "^8.0.0",
|
|
23
|
+
"inquirer": "^9.0.0",
|
|
24
|
+
"conf": "^12.0.0",
|
|
25
|
+
"groq-sdk": "^0.3.0"
|
|
26
|
+
}
|
|
27
|
+
}
|