fix cross-repo path deps; remove el compiler from neuron-lang/

- neuron-runtime, neuron-store, neuron-migrate: fix path deps
  (products/engram/ → foundation/engram/engrams/, products/el/ → foundation/el/engrams/)
- neuron-rs workspace: fix axon-events/axon-protocol paths
  (../../../platform/ → ../../platform/, paths were off by one level)
- neuron-lang/compiler/ removed (el self-hosting compiler now lives in foundation/el/el-compiler/)
This commit is contained in:
2026-04-29 03:27:39 -05:00
parent 78fc3a909a
commit b77f537dc6
54 changed files with 8065 additions and 2420 deletions
+19
View File
@@ -0,0 +1,19 @@
[package]
name = "neuron-cli"
version = "0.1.0"
edition = "2021"
[[bin]]
name = "neuron"
path = "src/main.rs"
[dependencies]
reqwest = { version = "0.12", features = ["json", "blocking"] }
serde = { version = "1", features = ["derive"] }
serde_json = "1"
tokio = { version = "1", features = ["full"] }
clap = { version = "4", features = ["derive"] }
rustyline = "14"
uuid = { version = "1", features = ["v4"] }
colored = "2"
chrono = { version = "0.4", features = ["serde"] }
+339
View File
@@ -0,0 +1,339 @@
// src/main.rs
use clap::{Parser, Subcommand};
use colored::Colorize;
use rustyline::DefaultEditor;
use serde_json::{json, Value};
use std::path::PathBuf;
#[derive(Parser)]
#[command(name = "neuron", about = "Neuron intelligence CLI")]
struct Cli {
#[command(subcommand)]
command: Option<Commands>,
/// Operator mode — full tool access (uses NEURON_API_KEY)
#[arg(long, global = true)]
operator: bool,
/// User mode — standard access (uses NEURON_USER_KEY)
#[arg(long, global = true)]
user: bool,
}
#[derive(Subcommand)]
enum Commands {
/// One-shot ask
Ask {
question: String,
},
/// Save a memory
Remember {
content: String,
#[arg(short, long)]
importance: Option<String>,
},
/// Search memories and knowledge
Search {
query: String,
},
/// Check server status
Status,
}
struct NeuronClient {
base_url: String,
api_key: String,
tier: Tier,
client: reqwest::Client,
}
enum Tier {
Operator,
User,
}
impl NeuronClient {
fn new(operator: bool) -> Self {
let (api_key, tier) = if operator {
(
std::env::var("NEURON_API_KEY").unwrap_or_else(|_| "neuron-dev".to_string()),
Tier::Operator,
)
} else {
(
std::env::var("NEURON_USER_KEY")
.or_else(|_| std::env::var("NEURON_API_KEY"))
.unwrap_or_else(|_| "neuron-dev".to_string()),
Tier::User,
)
};
let base_url = std::env::var("NEURON_URL")
.unwrap_or_else(|_| "http://localhost:7770".to_string());
Self {
base_url,
api_key,
tier,
client: reqwest::Client::new(),
}
}
fn prompt(&self) -> String {
match self.tier {
Tier::Operator => "neuron[op]> ".cyan().bold().to_string(),
Tier::User => "neuron> ".cyan().to_string(),
}
}
async fn axon_call(&self, tool: &str, params: Value) -> Result<Value, String> {
let id = uuid::Uuid::new_v4().to_string();
let body = json!({
"id": id,
"method": "tool_call",
"params": {
"tool": tool,
"params": params
}
});
let resp = self.client
.post(format!("{}/axon/message", self.base_url))
.header("Authorization", format!("Bearer {}", self.api_key))
.header("Content-Type", "application/json")
.json(&body)
.send()
.await
.map_err(|e| format!("Connection failed: {}. Is neuron running?", e))?;
if resp.status() == reqwest::StatusCode::UNAUTHORIZED {
return Err("Unauthorized. Check NEURON_API_KEY.".to_string());
}
resp.json::<Value>()
.await
.map_err(|e| format!("Parse error: {}", e))
}
async fn check_status(&self) -> bool {
self.client
.get(format!("{}/health", self.base_url))
.header("Authorization", format!("Bearer {}", self.api_key))
.send()
.await
.map(|r| r.status().is_success())
.unwrap_or(false)
}
async fn remember(&self, content: &str, importance: Option<&str>) -> String {
let params = json!({
"content": content,
"importance": importance.unwrap_or("normal"),
"tags": ["cli"]
});
match self.axon_call("remember", params).await {
Ok(v) => {
let id = v.get("id")
.or_else(|| v.get("result").and_then(|r| r.get("id")))
.and_then(|i| i.as_str())
.unwrap_or("saved");
format!("✓ Remembered ({})", id)
}
Err(e) => format!("{}", e),
}
}
async fn search(&self, query: &str) -> String {
let params = json!({ "query": query, "limit": 10 });
match self.axon_call("search_knowledge", params).await {
Ok(v) => format_search_results(&v),
Err(_) => {
// Try memory search
match self.axon_call("recall", json!({ "query": query })).await {
Ok(v) => format_search_results(&v),
Err(e) => format!("{}", e),
}
}
}
}
async fn chat(&self, message: &str) -> String {
let params = json!({
"message": message,
"context": "cli"
});
match self.axon_call("chat", params).await {
Ok(v) => extract_text_result(&v),
Err(e) => format!("{}", e),
}
}
}
fn extract_text_result(v: &Value) -> String {
// Try various result shapes
if let Some(r) = v.get("result") {
if let Some(s) = r.as_str() { return s.to_string(); }
if let Some(c) = r.get("content").and_then(|c| c.as_str()) { return c.to_string(); }
return serde_json::to_string_pretty(r).unwrap_or_default();
}
if let Some(e) = v.get("error").and_then(|e| e.as_str()) {
return format!("error: {}", e);
}
serde_json::to_string_pretty(v).unwrap_or_default()
}
fn format_search_results(v: &Value) -> String {
let items = v.get("result")
.or(Some(v))
.and_then(|r| r.as_array())
.cloned()
.unwrap_or_default();
if items.is_empty() {
return "No results found.".to_string();
}
let mut out = String::new();
for (i, item) in items.iter().take(10).enumerate() {
let title = item.get("title").and_then(|t| t.as_str())
.or_else(|| item.get("content").and_then(|c| c.as_str()).map(|s| &s[..s.len().min(60)]))
.unwrap_or("(untitled)");
let id = item.get("id").and_then(|i| i.as_str()).unwrap_or("");
out.push_str(&format!("{}. {} [{}]\n", i + 1, title, id));
}
out
}
fn history_path() -> PathBuf {
let home = std::env::var("HOME").unwrap_or_else(|_| ".".to_string());
PathBuf::from(home).join(".neuron").join("cli_history")
}
#[tokio::main]
async fn main() {
let cli = Cli::parse();
// Operator mode if --operator flag or neither flag (default operator for now)
let is_operator = cli.operator || !cli.user;
let nc = NeuronClient::new(is_operator);
match cli.command {
Some(Commands::Status) => {
let ok = nc.check_status().await;
if ok {
println!("{} neuron-api is running at {}", "".green(), nc.base_url);
} else {
println!("{} neuron-api is not responding at {}", "".red(), nc.base_url);
std::process::exit(1);
}
}
Some(Commands::Ask { question }) => {
let result = nc.chat(&question).await;
println!("{}", result);
}
Some(Commands::Remember { content, importance }) => {
let result = nc.remember(&content, importance.as_deref()).await;
if result.starts_with('✓') {
println!("{}", result.green());
} else {
eprintln!("{}", result.red());
std::process::exit(1);
}
}
Some(Commands::Search { query }) => {
let result = nc.search(&query).await;
println!("{}", result);
}
None => {
// Interactive REPL
if !nc.check_status().await {
eprintln!(
"{} Neuron is not running at {}.\nStart it with: neuron-api",
"".red().bold(),
nc.base_url
);
std::process::exit(1);
}
println!(
"{} {}",
"Neuron".cyan().bold(),
"intelligence CLI — type :help for commands, :quit to exit".dimmed()
);
println!();
let hist = history_path();
let mut rl = DefaultEditor::new().expect("readline init failed");
if hist.exists() {
let _ = rl.load_history(&hist);
}
let prompt = nc.prompt();
loop {
match rl.readline(&prompt) {
Ok(line) => {
let input = line.trim().to_string();
if input.is_empty() { continue; }
let _ = rl.add_history_entry(&input);
match input.as_str() {
":quit" | ":exit" | ":q" => {
let _ = rl.save_history(&hist);
println!("bye");
break;
}
":help" => {
println!("Commands:");
println!(" :status — check server status");
println!(" :quit — exit");
println!(" :help — this message");
println!(" remember <text> — save a memory");
println!(" search <query> — search knowledge");
println!(" anything else — chat");
}
":status" => {
let ok = nc.check_status().await;
if ok {
println!("{}", "● online".green());
} else {
println!("{}", "● offline".red());
}
}
_ if input.starts_with("remember ") => {
let content = &input["remember ".len()..];
let result = nc.remember(content, None).await;
println!("{}", if result.starts_with('✓') { result.green() } else { result.red() });
}
_ if input.starts_with("search ") => {
let query = &input["search ".len()..];
let result = nc.search(query).await;
println!("{}", result);
}
_ => {
let result = nc.chat(&input).await;
println!("{}", result.white());
}
}
}
Err(rustyline::error::ReadlineError::Interrupted) => {
println!();
continue;
}
Err(rustyline::error::ReadlineError::Eof) => {
let _ = rl.save_history(&hist);
break;
}
Err(e) => {
eprintln!("readline error: {}", e);
break;
}
}
}
}
}
}