scorpion-cli 0.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/README.md +152 -0
- package/install.ps1 +22 -0
- package/install.sh +19 -0
- package/package.json +44 -0
- package/src/agent.js +226 -0
- package/src/commands.js +193 -0
- package/src/index.js +86 -0
- package/src/ollama.js +126 -0
- package/src/registry.js +112 -0
- package/src/tools/context.js +432 -0
- package/src/tools/deep-research.js +425 -0
- package/src/tools/documents.js +299 -0
- package/src/tools/filesystem.js +402 -0
- package/src/tools/shell.js +134 -0
- package/src/tools/system.js +379 -0
- package/src/tools/web.js +433 -0
- package/src/ui/charts.js +213 -0
- package/src/ui/export.js +141 -0
- package/src/ui/formatter.js +365 -0
- package/src/ui/images.js +153 -0
- package/src/ui/panels.js +150 -0
- package/src/ui/progress.js +131 -0
- package/src/ui/repl.js +477 -0
- package/src/ui/spinner.js +99 -0
- package/src/ui/table.js +145 -0
package/README.md
ADDED
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
# Scorpion CLI π¦
|
|
2
|
+
|
|
3
|
+
An end-to-end **agentic AI assistant** for your terminal, refined for **speed**, **aesthetics**, and **intelligence**.
|
|
4
|
+
|
|
5
|
+
Powered by [Ollama](https://ollama.com) and **qwen3.5:0.8b**, Scorpion doesn't just chatβit takes action, manages your system, and researches the web for you **(for free, without API keys)**.
|
|
6
|
+
|
|
7
|
+
```
|
|
8
|
+
ββββββββ βββββββ βββββββ βββββββ βββββββ βββ βββββββ ββββ βββ
|
|
9
|
+
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ βββ
|
|
10
|
+
βββββββββββ βββ βββββββββββββββββββββββββ βββββββββ βββ
|
|
11
|
+
βββββββββββ βββ ββββββββββββββββββ ββββββ βββββββββββββ
|
|
12
|
+
ββββββββββββββββββββββββββββ ββββββ βββββββββββββββ ββββββ
|
|
13
|
+
ββββββββ βββββββ βββββββ βββ ββββββ βββ βββββββ βββ βββββ
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
## β¨ Key Features
|
|
17
|
+
|
|
18
|
+
- **π Free & Private Web Search**: Builit-in scraping engine uses **DuckDuckGo**, **Bing**, and **Google News** to find real-time information without requiring any paid API keys.
|
|
19
|
+
- **π§ Smart Agent Core**: Automatically detects when it needs to search the web (for events after 2023) vs. using internal knowledge.
|
|
20
|
+
- **π¬ Deep Research Mode**: A dedicated tool that scans **arXiv** (papers), **Hacker News** (tech discussions), and **Wikipedia** to generate comprehensive, cited reports.
|
|
21
|
+
- **β‘ Local & Fast**: Runs entirely on your machine using Ollama. No data leaves your system except for anonymous search queries.
|
|
22
|
+
- **π¨ Beautiful UI**: Rich ASCII tables, interactive spinners, live progress updates, and structured markdown rendering.
|
|
23
|
+
- **π§ System Control**: Execute commands, manage files, check system performance (CPU/RAM), and write code directly to files.
|
|
24
|
+
|
|
25
|
+
## What It Can Do
|
|
26
|
+
|
|
27
|
+
- **"Research the latest developments in quantum computing"** (Triggers Deep Research)
|
|
28
|
+
- **"What's the current stock price of NVIDIA?"** (Triggers Web Search)
|
|
29
|
+
- **"Check my system CPU usage"** (Triggers System Tools)
|
|
30
|
+
- **"Create a Python script for a discord bot"** (Triggers File Tools)
|
|
31
|
+
|
|
32
|
+
## Requirements
|
|
33
|
+
|
|
34
|
+
- **Node.js** 22.12+
|
|
35
|
+
- [Ollama](https://ollama.com/download) running locally
|
|
36
|
+
- **qwen3.5:0.8b** model (recommended):
|
|
37
|
+
```bash
|
|
38
|
+
ollama pull qwen3.5:0.8b
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
## Quick Start
|
|
42
|
+
|
|
43
|
+
1. **Clone & Install**
|
|
44
|
+
```bash
|
|
45
|
+
git clone <repo-url>
|
|
46
|
+
cd scorpion-cli-2
|
|
47
|
+
npm install
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
2. **Run Scorpion**
|
|
51
|
+
```bash
|
|
52
|
+
npm start
|
|
53
|
+
```
|
|
54
|
+
*No API keys required! It works straight out of the box.*
|
|
55
|
+
|
|
56
|
+
### Install from npm
|
|
57
|
+
|
|
58
|
+
After the `0.1.0` release, Scorpion can be installed on Windows, macOS, and Linux:
|
|
59
|
+
|
|
60
|
+
```bash
|
|
61
|
+
# Global npm install
|
|
62
|
+
npm install --global scorpion-cli
|
|
63
|
+
scorpion
|
|
64
|
+
|
|
65
|
+
# Run without installing globally
|
|
66
|
+
npx scorpion-cli
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
PowerShell:
|
|
70
|
+
|
|
71
|
+
```powershell
|
|
72
|
+
irm https://raw.githubusercontent.com/amaansyed27/scorpion-cli-agent/v0.1.0/install.ps1 | iex
|
|
73
|
+
scorpion
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
macOS/Linux:
|
|
77
|
+
|
|
78
|
+
```bash
|
|
79
|
+
curl -fsSL https://raw.githubusercontent.com/amaansyed27/scorpion-cli-agent/v0.1.0/install.sh | sh
|
|
80
|
+
scorpion
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
The current `https://aish.dawnlightlabs.com/install.ps1` endpoint belongs to AiSH and should not be used for Scorpion. Configure a separate Scorpion subdomain, such as `https://scorpion.dawnlightlabs.com/install.ps1`, to serve these installer files if you want a branded short URL.
|
|
84
|
+
|
|
85
|
+
Scorpion is distributed as one Node.js package rather than separate native binaries, so the same release works across all three platforms. Ollama must still be installed and running locally.
|
|
86
|
+
|
|
87
|
+
## π― Usage & Power Commands
|
|
88
|
+
|
|
89
|
+
Scorpion uses a natural language interface, but also supports powerful triggers for specific workflows:
|
|
90
|
+
|
|
91
|
+
Inside the interactive session, slash commands control the session:
|
|
92
|
+
|
|
93
|
+
```text
|
|
94
|
+
/list List installed Ollama models
|
|
95
|
+
/model Interactively select an installed model
|
|
96
|
+
/model <name> Switch directly to a model
|
|
97
|
+
/settings Show the active model and settings
|
|
98
|
+
/think on|off|toggle Enable or disable visible thinking output
|
|
99
|
+
/help Show all commands
|
|
100
|
+
/clear Clear conversation history
|
|
101
|
+
/stats, /reports Show session statistics or saved reports
|
|
102
|
+
/export [md|json] Export the last report
|
|
103
|
+
/demo Show UI features
|
|
104
|
+
/exit Quit Scorpion
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
### **1. Deep Research Mode** (`@deep`)
|
|
108
|
+
Triggers an in-depth analysis session. It searches multiple sources, reads simplified content, and compiles a structured report with citations.
|
|
109
|
+
|
|
110
|
+
```bash
|
|
111
|
+
> @deep The impact of AI agents on software engineering jobs
|
|
112
|
+
```
|
|
113
|
+
*Also triggered by asking to "research", "analyze", or "explain in detail".*
|
|
114
|
+
|
|
115
|
+
### **2. Quick Response** (`@quick`)
|
|
116
|
+
Forces a fast answer without searching the web, using only the model's internal knowledge.
|
|
117
|
+
|
|
118
|
+
```bash
|
|
119
|
+
> @quick Explain the concept of recursion
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
### **3. System & Files**
|
|
123
|
+
Directly interact with your OS.
|
|
124
|
+
|
|
125
|
+
```bash
|
|
126
|
+
> show my memory usage
|
|
127
|
+
> list files in the current directory
|
|
128
|
+
> create a README.md file for a react project
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
## π οΈ Configuration
|
|
132
|
+
|
|
133
|
+
Scorpion works with default settings, but you can configure it via environment variables if needed:
|
|
134
|
+
|
|
135
|
+
- `OLLAMA_HOST`: Set if your Ollama instance is not on `localhost:11434`.
|
|
136
|
+
- `OLLAMA_MODEL`: Override the default `qwen3.5:0.8b` model.
|
|
137
|
+
|
|
138
|
+
## Troubleshooting
|
|
139
|
+
|
|
140
|
+
- **"Model not found"**: Run `ollama pull qwen3.5:0.8b` (or your preferred model) first.
|
|
141
|
+
- **"Connection failed"**: Ensure Ollama is running (`ollama serve`).
|
|
142
|
+
|
|
143
|
+
## Built With
|
|
144
|
+
|
|
145
|
+
- **Ollama** - Local AI inference
|
|
146
|
+
- **Cheerio** - specialized web scraping & parsing
|
|
147
|
+
- **Node.js** - Runtime & System Interaction
|
|
148
|
+
- **Ora/Chalk** - Terminal styling & Animations
|
|
149
|
+
|
|
150
|
+
## License
|
|
151
|
+
|
|
152
|
+
MIT
|
package/install.ps1
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
$ErrorActionPreference = 'Stop'
|
|
2
|
+
|
|
3
|
+
$requiredNode = [version]'22.12.0'
|
|
4
|
+
$nodeCommand = Get-Command node -ErrorAction SilentlyContinue
|
|
5
|
+
$npmCommand = Get-Command npm -ErrorAction SilentlyContinue
|
|
6
|
+
|
|
7
|
+
if (-not $nodeCommand -or -not $npmCommand) {
|
|
8
|
+
throw 'Node.js 22.12 or newer is required. Install it from https://nodejs.org/ and run this installer again.'
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
$nodeVersion = [version]((& node --version).TrimStart('v'))
|
|
12
|
+
if ($nodeVersion -lt $requiredNode) {
|
|
13
|
+
throw "Node.js $requiredNode or newer is required. Detected $nodeVersion."
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
& npm install --global scorpion-cli@latest
|
|
17
|
+
if ($LASTEXITCODE -ne 0) {
|
|
18
|
+
throw 'Scorpion installation failed.'
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
Write-Host 'Scorpion CLI installed successfully.' -ForegroundColor Green
|
|
22
|
+
Write-Host 'Run: scorpion'
|
package/install.sh
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
#!/usr/bin/env sh
|
|
2
|
+
set -eu
|
|
3
|
+
|
|
4
|
+
required_node='22.12.0'
|
|
5
|
+
|
|
6
|
+
if ! command -v node >/dev/null 2>&1 || ! command -v npm >/dev/null 2>&1; then
|
|
7
|
+
echo 'Node.js 22.12 or newer is required. Install it from https://nodejs.org/ and run this installer again.' >&2
|
|
8
|
+
exit 1
|
|
9
|
+
fi
|
|
10
|
+
|
|
11
|
+
node_version="$(node --version | sed 's/^v//')"
|
|
12
|
+
node -e "const [a,b,c] = process.argv[1].split('.').map(Number); const [x,y,z] = process.argv[2].split('.').map(Number); process.exit(a > x || (a === x && (b > y || (b === y && c >= z))) ? 0 : 1)" "$node_version" "$required_node" || {
|
|
13
|
+
echo "Node.js $required_node or newer is required. Detected $node_version." >&2
|
|
14
|
+
exit 1
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
npm install --global scorpion-cli@latest
|
|
18
|
+
echo 'Scorpion CLI installed successfully.'
|
|
19
|
+
echo 'Run: scorpion'
|
package/package.json
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "scorpion-cli",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Agentic AI CLI assistant powered by Ollama",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "src/index.js",
|
|
7
|
+
"bin": {
|
|
8
|
+
"scorpion": "src/index.js"
|
|
9
|
+
},
|
|
10
|
+
"files": [
|
|
11
|
+
"src",
|
|
12
|
+
"README.md",
|
|
13
|
+
"install.ps1",
|
|
14
|
+
"install.sh"
|
|
15
|
+
],
|
|
16
|
+
"scripts": {
|
|
17
|
+
"start": "node src/index.js",
|
|
18
|
+
"dev": "node --watch src/index.js"
|
|
19
|
+
},
|
|
20
|
+
"keywords": [
|
|
21
|
+
"ai",
|
|
22
|
+
"cli",
|
|
23
|
+
"ollama",
|
|
24
|
+
"agent",
|
|
25
|
+
"assistant"
|
|
26
|
+
],
|
|
27
|
+
"author": "Amaan",
|
|
28
|
+
"license": "MIT",
|
|
29
|
+
"engines": {
|
|
30
|
+
"node": ">=22.12.0"
|
|
31
|
+
},
|
|
32
|
+
"dependencies": {
|
|
33
|
+
"boxen": "^8.0.1",
|
|
34
|
+
"chalk": "^6.0.0",
|
|
35
|
+
"cheerio": "^1.2.0",
|
|
36
|
+
"commander": "^15.0.0",
|
|
37
|
+
"figlet": "^1.11.3",
|
|
38
|
+
"gradient-string": "^3.0.0",
|
|
39
|
+
"node-fetch": "^3.3.2",
|
|
40
|
+
"ollama": "^0.6.3",
|
|
41
|
+
"ora": "^9.4.1",
|
|
42
|
+
"systeminformation": "^5.33.1"
|
|
43
|
+
}
|
|
44
|
+
}
|
package/src/agent.js
ADDED
|
@@ -0,0 +1,226 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Agent Core
|
|
3
|
+
* Implements the agentic loop with tool calling and streaming
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { chat, DEFAULT_MODEL } from './ollama.js';
|
|
7
|
+
import { getAllToolSchemas, executeTool } from './registry.js';
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Get current date/time for system prompt
|
|
11
|
+
*/
|
|
12
|
+
function getCurrentDateTime() {
|
|
13
|
+
const now = new Date();
|
|
14
|
+
return {
|
|
15
|
+
date: now.toLocaleDateString('en-US', { weekday: 'long', year: 'numeric', month: 'long', day: 'numeric' }),
|
|
16
|
+
time: now.toLocaleTimeString('en-US', { hour: '2-digit', minute: '2-digit', hour12: true }),
|
|
17
|
+
year: now.getFullYear(),
|
|
18
|
+
timezone: Intl.DateTimeFormat().resolvedOptions().timeZone
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Build system prompt with real-time context
|
|
24
|
+
*/
|
|
25
|
+
function buildSystemPrompt() {
|
|
26
|
+
const dt = getCurrentDateTime();
|
|
27
|
+
const currentYear = new Date().getFullYear();
|
|
28
|
+
|
|
29
|
+
return `You are Scorpion, a helpful AI assistant. Current date: ${dt.date}
|
|
30
|
+
|
|
31
|
+
CRITICAL RULES:
|
|
32
|
+
1. Your knowledge is outdated (cutoff: mid-2023). For ANY current events, news, sports, or facts about people/companies/products, you MUST use web_search() first.
|
|
33
|
+
2. Search for anything mentioning ${currentYear - 1} or ${currentYear}, or keywords: "latest", "recent", "today", "current"
|
|
34
|
+
3. Answer from memory ONLY for: basic math, greetings, timeless concepts
|
|
35
|
+
|
|
36
|
+
MANDATORY: When answering sports/news, ALWAYS call web_search() before responding.
|
|
37
|
+
|
|
38
|
+
---
|
|
39
|
+
|
|
40
|
+
RESPONSE FORMAT - use this structure for EVERY answer:
|
|
41
|
+
|
|
42
|
+
## Main Topic
|
|
43
|
+
|
|
44
|
+
> Key Insight: One sentence summary
|
|
45
|
+
|
|
46
|
+
Your detailed explanation with **important terms** in bold and \`code\` for technical items.
|
|
47
|
+
|
|
48
|
+
- Key point 1
|
|
49
|
+
- Key point 2
|
|
50
|
+
- Key point 3
|
|
51
|
+
|
|
52
|
+
---
|
|
53
|
+
|
|
54
|
+
### Additional Context (if needed)
|
|
55
|
+
|
|
56
|
+
More details here.
|
|
57
|
+
|
|
58
|
+
---
|
|
59
|
+
|
|
60
|
+
FORMATTING RULES:
|
|
61
|
+
- ## for main sections
|
|
62
|
+
- ### for subsections
|
|
63
|
+
- **bold** for names, key terms, numbers
|
|
64
|
+
- \`code\` for technical terms
|
|
65
|
+
- > for key insights/summaries
|
|
66
|
+
- Short paragraphs (2-3 sentences)
|
|
67
|
+
- Cite sources clearly
|
|
68
|
+
|
|
69
|
+
Be concise, organized, and always cite your sources.`;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Run the agent loop
|
|
74
|
+
* @param {string} userMessage - User's input message
|
|
75
|
+
* @param {Object} options - Agent options
|
|
76
|
+
* @param {Array} options.history - Conversation history
|
|
77
|
+
* @param {Function} options.onThinking - Callback for thinking output
|
|
78
|
+
* @param {Function} options.onContent - Callback for content output
|
|
79
|
+
* @param {Function} options.onToolCall - Callback when tool is called
|
|
80
|
+
* @param {Function} options.onToolResult - Callback with tool result
|
|
81
|
+
* @param {string} options.model - Model to use
|
|
82
|
+
* @returns {Object} - Final response and updated history
|
|
83
|
+
*/
|
|
84
|
+
export async function runAgent(userMessage, options = {}) {
|
|
85
|
+
const {
|
|
86
|
+
history = [],
|
|
87
|
+
onThinking = () => { },
|
|
88
|
+
onContent = () => { },
|
|
89
|
+
onToolCall = () => { },
|
|
90
|
+
onToolResult = () => { },
|
|
91
|
+
model = DEFAULT_MODEL,
|
|
92
|
+
} = options;
|
|
93
|
+
|
|
94
|
+
// Build messages with dynamic system prompt (includes current date/time)
|
|
95
|
+
const messages = [
|
|
96
|
+
{ role: 'system', content: buildSystemPrompt() },
|
|
97
|
+
...history,
|
|
98
|
+
{ role: 'user', content: userMessage }
|
|
99
|
+
];
|
|
100
|
+
|
|
101
|
+
// Get all available tools
|
|
102
|
+
const tools = getAllToolSchemas();
|
|
103
|
+
|
|
104
|
+
let iterations = 0;
|
|
105
|
+
const maxIterations = 10; // Safety limit
|
|
106
|
+
|
|
107
|
+
while (iterations < maxIterations) {
|
|
108
|
+
iterations++;
|
|
109
|
+
|
|
110
|
+
// Call the model with streaming
|
|
111
|
+
const stream = await chat({
|
|
112
|
+
model,
|
|
113
|
+
messages,
|
|
114
|
+
tools,
|
|
115
|
+
think: true,
|
|
116
|
+
stream: true,
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
// Accumulate the response
|
|
120
|
+
let thinking = '';
|
|
121
|
+
let content = '';
|
|
122
|
+
let toolCalls = [];
|
|
123
|
+
let inThinking = false;
|
|
124
|
+
|
|
125
|
+
for await (const chunk of stream) {
|
|
126
|
+
// Handle thinking
|
|
127
|
+
if (chunk.message?.thinking) {
|
|
128
|
+
if (!inThinking) {
|
|
129
|
+
inThinking = true;
|
|
130
|
+
}
|
|
131
|
+
thinking += chunk.message.thinking;
|
|
132
|
+
onThinking(chunk.message.thinking);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
// Handle content
|
|
136
|
+
if (chunk.message?.content) {
|
|
137
|
+
if (inThinking) {
|
|
138
|
+
inThinking = false;
|
|
139
|
+
}
|
|
140
|
+
content += chunk.message.content;
|
|
141
|
+
onContent(chunk.message.content);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
// Handle tool calls
|
|
145
|
+
if (chunk.message?.tool_calls) {
|
|
146
|
+
toolCalls = chunk.message.tool_calls;
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
// Add assistant message to history
|
|
151
|
+
const assistantMessage = {
|
|
152
|
+
role: 'assistant',
|
|
153
|
+
content: content || undefined,
|
|
154
|
+
thinking: thinking || undefined,
|
|
155
|
+
tool_calls: toolCalls.length > 0 ? toolCalls : undefined,
|
|
156
|
+
};
|
|
157
|
+
messages.push(assistantMessage);
|
|
158
|
+
|
|
159
|
+
// If there are tool calls, execute them
|
|
160
|
+
if (toolCalls.length > 0) {
|
|
161
|
+
for (const call of toolCalls) {
|
|
162
|
+
const toolName = call.function?.name;
|
|
163
|
+
const toolArgs = call.function?.arguments || {};
|
|
164
|
+
|
|
165
|
+
onToolCall({ name: toolName, args: toolArgs });
|
|
166
|
+
|
|
167
|
+
// Execute the tool
|
|
168
|
+
const result = await executeTool(toolName, toolArgs);
|
|
169
|
+
|
|
170
|
+
onToolResult({ name: toolName, result });
|
|
171
|
+
|
|
172
|
+
// Add tool result to messages
|
|
173
|
+
messages.push({
|
|
174
|
+
role: 'tool',
|
|
175
|
+
tool_name: toolName,
|
|
176
|
+
content: result,
|
|
177
|
+
});
|
|
178
|
+
}
|
|
179
|
+
// Continue the loop to let model process tool results
|
|
180
|
+
} else {
|
|
181
|
+
// No tool calls - agent is done
|
|
182
|
+
break;
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
// Extract final response
|
|
187
|
+
const finalMessage = messages[messages.length - 1];
|
|
188
|
+
|
|
189
|
+
// Update history (exclude system prompt)
|
|
190
|
+
const newHistory = messages.slice(1);
|
|
191
|
+
|
|
192
|
+
return {
|
|
193
|
+
content: finalMessage.content || '',
|
|
194
|
+
thinking: finalMessage.thinking,
|
|
195
|
+
history: newHistory,
|
|
196
|
+
iterations,
|
|
197
|
+
};
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
/**
|
|
201
|
+
* Run a single query without streaming (simpler interface)
|
|
202
|
+
* @param {string} query - User query
|
|
203
|
+
* @param {Array} history - Conversation history
|
|
204
|
+
* @returns {Promise<Object>} - Response object
|
|
205
|
+
*/
|
|
206
|
+
export async function query(query, history = []) {
|
|
207
|
+
let fullContent = '';
|
|
208
|
+
let fullThinking = '';
|
|
209
|
+
const toolsUsed = [];
|
|
210
|
+
|
|
211
|
+
const result = await runAgent(query, {
|
|
212
|
+
history,
|
|
213
|
+
onThinking: (text) => { fullThinking += text; },
|
|
214
|
+
onContent: (text) => { fullContent += text; },
|
|
215
|
+
onToolCall: (tool) => { toolsUsed.push(tool.name); },
|
|
216
|
+
});
|
|
217
|
+
|
|
218
|
+
return {
|
|
219
|
+
content: fullContent || result.content,
|
|
220
|
+
thinking: fullThinking,
|
|
221
|
+
toolsUsed,
|
|
222
|
+
history: result.history,
|
|
223
|
+
};
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
export { buildSystemPrompt };
|
package/src/commands.js
ADDED
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CLI Commands
|
|
3
|
+
* Export, stats, and utility commands
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { saveResearchReport, listReports } from './ui/export.js';
|
|
7
|
+
import { renderTable } from './ui/table.js';
|
|
8
|
+
import { barChart } from './ui/charts.js';
|
|
9
|
+
import { colors } from './ui/formatter.js';
|
|
10
|
+
import { listModels } from './ollama.js';
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* List models installed in the local Ollama instance.
|
|
14
|
+
* @param {string} currentModel - Currently selected model
|
|
15
|
+
* @returns {Promise<Array>} Installed models
|
|
16
|
+
*/
|
|
17
|
+
export async function handleListModels(currentModel) {
|
|
18
|
+
try {
|
|
19
|
+
const models = await listModels();
|
|
20
|
+
|
|
21
|
+
if (models.length === 0) {
|
|
22
|
+
console.log(colors.info(' No Ollama models are installed.'));
|
|
23
|
+
console.log(colors.dim(' Pull one with: ollama pull <model>'));
|
|
24
|
+
return models;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
console.log();
|
|
28
|
+
console.log(colors.bold.white(' Ollama Models'));
|
|
29
|
+
console.log(colors.dim(' ' + 'β'.repeat(50)));
|
|
30
|
+
renderTable(models.map((model) => ({
|
|
31
|
+
Model: model.name === currentModel ? `${model.name} *` : model.name,
|
|
32
|
+
Size: formatBytes(model.size),
|
|
33
|
+
Modified: model.modified_at ? new Date(model.modified_at).toLocaleString() : 'β',
|
|
34
|
+
})), { compact: true });
|
|
35
|
+
console.log(colors.dim(' * active model'));
|
|
36
|
+
console.log();
|
|
37
|
+
return models;
|
|
38
|
+
} catch (error) {
|
|
39
|
+
console.log(colors.error(' Could not list Ollama models.'));
|
|
40
|
+
console.log(colors.dim(' Make sure Ollama is running: ollama serve'));
|
|
41
|
+
return [];
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Select an installed Ollama model using the REPL's existing input stream.
|
|
47
|
+
* @param {string} currentModel - Currently selected model
|
|
48
|
+
* @param {Function} ask - Function that prompts for one line of input
|
|
49
|
+
* @param {string} requestedModel - Optional model supplied with `/model <name>`
|
|
50
|
+
* @returns {Promise<string|null>} Selected model, or null when cancelled/unavailable
|
|
51
|
+
*/
|
|
52
|
+
export async function handleModelSelect(currentModel, ask, requestedModel = '') {
|
|
53
|
+
let models;
|
|
54
|
+
try {
|
|
55
|
+
models = await listModels();
|
|
56
|
+
} catch (error) {
|
|
57
|
+
console.log(colors.error(' Could not connect to Ollama.'));
|
|
58
|
+
console.log(colors.dim(' Make sure Ollama is running: ollama serve'));
|
|
59
|
+
return null;
|
|
60
|
+
}
|
|
61
|
+
if (models.length === 0) {
|
|
62
|
+
console.log(colors.info(' No Ollama models are installed. Pull one with: ollama pull <model>'));
|
|
63
|
+
return null;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
const requested = requestedModel.trim();
|
|
67
|
+
if (requested) {
|
|
68
|
+
const exactMatch = models.find((model) => model.name === requested);
|
|
69
|
+
if (exactMatch) return exactMatch.name;
|
|
70
|
+
console.log(colors.error(` Model '${requested}' is not installed. Use /list to see available models.`));
|
|
71
|
+
return null;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
console.log();
|
|
75
|
+
console.log(colors.bold.white(' Select an Ollama Model'));
|
|
76
|
+
models.forEach((model, index) => {
|
|
77
|
+
const active = model.name === currentModel ? colors.success(' (active)') : '';
|
|
78
|
+
console.log(` ${colors.dim(`${index + 1}.`)} ${model.name}${active}`);
|
|
79
|
+
});
|
|
80
|
+
console.log(colors.dim(' Enter a number, model name, or press Enter to cancel.'));
|
|
81
|
+
|
|
82
|
+
const answer = (await ask(colors.accent(' Select model: '))).trim();
|
|
83
|
+
if (!answer) return null;
|
|
84
|
+
|
|
85
|
+
const selectedIndex = Number.parseInt(answer, 10);
|
|
86
|
+
if (Number.isInteger(selectedIndex) && String(selectedIndex) === answer && models[selectedIndex - 1]) {
|
|
87
|
+
return models[selectedIndex - 1].name;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
const exactMatch = models.find((model) => model.name === answer);
|
|
91
|
+
if (exactMatch) return exactMatch.name;
|
|
92
|
+
|
|
93
|
+
console.log(colors.error(` '${answer}' is not a valid installed model.`));
|
|
94
|
+
return null;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
function formatBytes(bytes = 0) {
|
|
98
|
+
if (!bytes) return 'β';
|
|
99
|
+
const units = ['B', 'KB', 'MB', 'GB', 'TB'];
|
|
100
|
+
const index = Math.min(Math.floor(Math.log(bytes) / Math.log(1024)), units.length - 1);
|
|
101
|
+
return `${(bytes / (1024 ** index)).toFixed(index === 0 ? 0 : 1)} ${units[index]}`;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Handle export command
|
|
106
|
+
* @param {string} format - Export format (md, json)
|
|
107
|
+
*/
|
|
108
|
+
export async function handleExport(format = 'md') {
|
|
109
|
+
console.log();
|
|
110
|
+
console.log(colors.accent(' πΎ Export Report'));
|
|
111
|
+
console.log(colors.dim(' β'.repeat(30)));
|
|
112
|
+
console.log();
|
|
113
|
+
console.log(colors.dim(' Exporting last research report...'));
|
|
114
|
+
console.log();
|
|
115
|
+
|
|
116
|
+
// TODO: Implement export of last report
|
|
117
|
+
console.log(colors.info(' βΉ Export functionality available via Deep Research'));
|
|
118
|
+
console.log(colors.dim(' Reports are auto-saved to reports/ directory'));
|
|
119
|
+
console.log();
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* Handle list reports command
|
|
124
|
+
*/
|
|
125
|
+
export async function handleListReports() {
|
|
126
|
+
await listReports();
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* Handle stats command
|
|
131
|
+
*/
|
|
132
|
+
export async function handleStats() {
|
|
133
|
+
console.log();
|
|
134
|
+
console.log(colors.bold.white(' π Session Statistics'));
|
|
135
|
+
console.log(colors.dim(' β'.repeat(50)));
|
|
136
|
+
console.log();
|
|
137
|
+
|
|
138
|
+
// Example stats (would be tracked in real implementation)
|
|
139
|
+
const stats = [
|
|
140
|
+
{ label: 'Queries', value: 12 },
|
|
141
|
+
{ label: 'Tool Calls', value: 45 },
|
|
142
|
+
{ label: 'Deep Research', value: 3 },
|
|
143
|
+
{ label: 'Quick Response', value: 9 }
|
|
144
|
+
];
|
|
145
|
+
|
|
146
|
+
barChart(stats, { maxWidth: 30, color: 'cyan' });
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* Handle demo command (show all UI features)
|
|
151
|
+
*/
|
|
152
|
+
export async function handleDemo() {
|
|
153
|
+
console.log();
|
|
154
|
+
console.log(colors.bold.white(' π¨ UI Feature Demo'));
|
|
155
|
+
console.log(colors.dim(' β'.repeat(50)));
|
|
156
|
+
console.log();
|
|
157
|
+
|
|
158
|
+
// Demo table
|
|
159
|
+
console.log(colors.accent(' π Table Example:'));
|
|
160
|
+
console.log();
|
|
161
|
+
renderTable([
|
|
162
|
+
{ Feature: 'Deep Research', Status: 'β Available', Type: 'Core' },
|
|
163
|
+
{ Feature: 'Quick Mode', Status: 'β Available', Type: 'Core' },
|
|
164
|
+
{ Feature: 'Export', Status: 'β Available', Type: 'Utility' }
|
|
165
|
+
], { compact: true });
|
|
166
|
+
|
|
167
|
+
console.log();
|
|
168
|
+
console.log(colors.accent(' π Chart Example:'));
|
|
169
|
+
console.log();
|
|
170
|
+
barChart([
|
|
171
|
+
{ label: 'arXiv', value: 85 },
|
|
172
|
+
{ label: 'HN', value: 65 },
|
|
173
|
+
{ label: 'Wikipedia', value: 92 }
|
|
174
|
+
], { title: 'Source Reliability (%)', maxWidth: 40 });
|
|
175
|
+
|
|
176
|
+
console.log(colors.accent(' π‘ Available Commands:'));
|
|
177
|
+
console.log();
|
|
178
|
+
console.log(colors.dim(' help') + colors.white(' - Show @ trigger help'));
|
|
179
|
+
console.log(colors.dim(' stats') + colors.white(' - Show session stats'));
|
|
180
|
+
console.log(colors.dim(' reports') + colors.white(' - List saved reports'));
|
|
181
|
+
console.log(colors.dim(' demo') + colors.white(' - Show UI features'));
|
|
182
|
+
console.log(colors.dim(' clear') + colors.white(' - Clear conversation'));
|
|
183
|
+
console.log();
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
export default {
|
|
187
|
+
handleExport,
|
|
188
|
+
handleListReports,
|
|
189
|
+
handleListModels,
|
|
190
|
+
handleModelSelect,
|
|
191
|
+
handleStats,
|
|
192
|
+
handleDemo
|
|
193
|
+
};
|