Skip to content

Commit dc94aa0

Browse files
committed
fix: Fix npm package
chore: add run parameters to provide data directory and config
1 parent e37e234 commit dc94aa0

File tree

7 files changed

+84
-47
lines changed

7 files changed

+84
-47
lines changed

npm/huly-coder/src/index.ts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ function getExePath() {
2121

2222
try {
2323
// Since the binary will be located inside `node_modules`, we can simply call `require.resolve`
24-
return require.resolve(`app-${os}-${arch}/bin/app${extension}`);
24+
return require.resolve(`huly-coder-${os}-${arch}/bin/huly-coder${extension}`);
2525
} catch (e) {
2626
throw new Error(
2727
`Couldn't find application binary inside node_modules for ${os}-${arch}`
@@ -34,7 +34,10 @@ function getExePath() {
3434
*/
3535
function run() {
3636
const args = process.argv.slice(2);
37-
const processResult = spawnSync(getExePath(), args, { stdio: "inherit" });
37+
const path = getExePath();
38+
const pathSeparator = ["win32", "cygwin"].includes(process.platform) ? "\\" : "/";
39+
const parentDir = path.split(pathSeparator).slice(0, -2).join(pathSeparator);
40+
const processResult = spawnSync(path, args, { cwd: parentDir, stdio: "inherit" });
3841
process.exit(processResult.status ?? 0);
3942
}
4043

src/agent/mod.rs

Lines changed: 19 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ use std::collections::HashSet;
22
use std::fmt::Display;
33
use std::fs;
44
use std::path::Path;
5+
use std::path::PathBuf;
56
// Copyright © 2025 Huly Labs. Use of this source code is governed by the MIT license.
67
use std::sync::Arc;
78

@@ -87,12 +88,11 @@ struct AgentConfigState {
8788
}
8889

8990
impl AgentConfigState {
90-
pub fn new() -> Self {
91-
if Path::new(CONFIG_STATE_FILE_PATH).exists() {
92-
serde_yaml::from_str(
93-
&std::fs::read_to_string(CONFIG_STATE_FILE_PATH).unwrap_or_default(),
94-
)
95-
.unwrap_or_default()
91+
pub fn new(data_dir: &str) -> Self {
92+
let path = Path::new(data_dir).join(CONFIG_STATE_FILE_PATH);
93+
if path.exists() {
94+
serde_yaml::from_str(&std::fs::read_to_string(path).unwrap_or_default())
95+
.unwrap_or_default()
9696
} else {
9797
Self {
9898
approved_tools: HashSet::default(),
@@ -132,6 +132,7 @@ fn pending_tool_id<'a>(messages: RwLockReadGuard<'a, Vec<Message>>) -> Option<St
132132

133133
struct AgentContext {
134134
config: Config,
135+
data_dir: PathBuf,
135136
config_state: Arc<RwLock<AgentConfigState>>,
136137
messages: Arc<RwLock<Vec<Message>>>,
137138
state: Arc<RwLock<AgentState>>,
@@ -144,11 +145,15 @@ struct AgentContext {
144145
}
145146

146147
impl Agent {
147-
pub fn new(config: Config, sender: mpsc::UnboundedSender<AgentOutputEvent>) -> Self {
148+
pub fn new(
149+
data_dir: &str,
150+
config: Config,
151+
sender: mpsc::UnboundedSender<AgentOutputEvent>,
152+
) -> Self {
148153
Self {
149154
config,
150155
sender,
151-
memory: Arc::new(RwLock::new(MemoryManager::new(false))),
156+
memory: Arc::new(RwLock::new(MemoryManager::new(data_dir, false))),
152157
process_registry: Arc::new(RwLock::new(ProcessRegistry::default())),
153158
}
154159
}
@@ -407,6 +412,7 @@ impl Agent {
407412

408413
pub async fn run(
409414
&mut self,
415+
data_dir: &str,
410416
receiver: mpsc::UnboundedReceiver<AgentControlEvent>,
411417
messages: Vec<Message>,
412418
memory_index: InMemoryVectorIndex<rig_fastembed::EmbeddingModel, Entity>,
@@ -463,10 +469,11 @@ impl Agent {
463469
let memory_index = Arc::new(RwLock::new(memory_index));
464470
let sender = self.sender.clone();
465471
let state = Arc::new(RwLock::new(state));
466-
let config_state = Arc::new(RwLock::new(AgentConfigState::new()));
472+
let config_state = Arc::new(RwLock::new(AgentConfigState::new(data_dir)));
467473

468474
let events_context = AgentContext {
469475
config: self.config.clone(),
476+
data_dir: PathBuf::from(data_dir),
470477
config_state: config_state.clone(),
471478
messages: messages.clone(),
472479
state: state.clone(),
@@ -480,6 +487,7 @@ impl Agent {
480487

481488
let process_context = AgentContext {
482489
config: self.config.clone(),
490+
data_dir: PathBuf::from(data_dir),
483491
config_state: config_state.clone(),
484492
messages: messages.clone(),
485493
state: state.clone(),
@@ -584,14 +592,14 @@ impl AgentContext {
584592
async fn persist_history(&self) {
585593
tracing::debug!("persist_history");
586594
let messages = self.messages.read().await;
587-
persist_history(&messages);
595+
persist_history(&self.data_dir, &messages);
588596
}
589597

590598
async fn persist_config_state(&self) {
591599
tracing::debug!("persist_config_state");
592600
let state = self.config_state.read().await;
593601
fs::write(
594-
CONFIG_STATE_FILE_PATH,
602+
self.data_dir.join(CONFIG_STATE_FILE_PATH),
595603
serde_yaml::to_string(&*state).unwrap(),
596604
)
597605
.unwrap();

src/agent/utils.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -132,9 +132,9 @@ pub async fn add_env_message<'a>(
132132
}
133133
}
134134

135-
pub fn persist_history(messages: &[Message]) {
135+
pub fn persist_history(data_dir: &Path, messages: &[Message]) {
136136
fs::write(
137-
HISTORY_PATH,
137+
data_dir.join(HISTORY_PATH),
138138
serde_json::to_string_pretty(messages).unwrap(),
139139
)
140140
.unwrap();

src/config.rs

Lines changed: 5 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@ use serde::Deserialize;
88

99
const CONFIG_FILE: &str = "huly-coder.yaml";
1010
const LOCAL_CONFIG_FILE: &str = "huly-coder-local.yaml";
11-
const DOCKER_LOCAL_CONFIG_FILE: &str = "data/huly-coder-local.yaml";
1211

1312
#[derive(Debug, Deserialize, Clone)]
1413
pub enum ProviderKind {
@@ -98,7 +97,7 @@ pub struct Config {
9897
}
9998

10099
impl Config {
101-
pub fn new() -> color_eyre::Result<Self> {
100+
pub fn new(custom_config: &str) -> color_eyre::Result<Self> {
102101
let mut builder = config::Config::builder()
103102
.add_source(config::File::with_name(CONFIG_FILE))
104103
.add_source(config::Environment::with_prefix("HULY_CODER"));
@@ -108,12 +107,6 @@ impl Config {
108107
builder = builder.add_source(config::File::with_name(LOCAL_CONFIG_FILE));
109108
}
110109

111-
// Docker related local config file that stored in /data directory
112-
if Path::new(DOCKER_LOCAL_CONFIG_FILE).exists() {
113-
tracing::info!("Found local config at {}", DOCKER_LOCAL_CONFIG_FILE);
114-
builder = builder.add_source(config::File::with_name(DOCKER_LOCAL_CONFIG_FILE));
115-
}
116-
117110
let user_config = format!(
118111
"{}/{}",
119112
dirs::home_dir().unwrap().to_str().unwrap(),
@@ -123,6 +116,10 @@ impl Config {
123116
tracing::info!("Found user config at {}", user_config);
124117
builder = builder.add_source(config::File::with_name(&user_config));
125118
}
119+
if Path::new(custom_config).exists() {
120+
tracing::info!("Found custom config at {}", custom_config);
121+
builder = builder.add_source(config::File::with_name(custom_config));
122+
}
126123
if env::var("DOCKER_RUN").is_ok() {
127124
builder = builder.set_override("permission_mode", "full_autonomous")?;
128125
}

src/main.rs

Lines changed: 35 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
1+
use std::fs;
12
// Copyright © 2025 Huly Labs. Use of this source code is governed by the MIT license.
23
use std::io;
34
use std::io::stdout;
45
use std::panic::set_hook;
56
use std::panic::take_hook;
7+
use std::path::Path;
68

79
use crossterm::execute;
810
use crossterm::terminal::disable_raw_mode;
@@ -29,19 +31,26 @@ pub mod templates;
2931
pub mod tools;
3032
mod tui;
3133

32-
const HISTORY_PATH: &str = "data/history.json";
33-
const CONFIG_STATE_FILE_PATH: &str = "data/config_state.yaml";
34+
const HISTORY_PATH: &str = "history.json";
35+
const CONFIG_STATE_FILE_PATH: &str = "config_state.yaml";
3436

3537
#[derive(Parser, Debug)]
3638
#[command(version, about, long_about = None)]
3739
struct Args {
3840
/// Skip loading previous session from history.json file
3941
#[arg(short, long)]
4042
skip_load_messages: bool,
43+
/// Path to data directory
44+
#[arg(short, long, default_value = "data")]
45+
data: String,
46+
/// Path to config file
47+
#[arg(short, long, default_value = "huly-coder-local.yaml")]
48+
config: String,
4149
}
4250

43-
fn init_logger() {
44-
let writer = tracing_appender::rolling::daily("data/logs", "huly-coder.log");
51+
fn init_logger(data_dir: &str) {
52+
let log_dir = Path::new(data_dir).join("logs");
53+
let writer = tracing_appender::rolling::daily(log_dir, "huly-coder.log");
4554
tracing_subscriber::registry()
4655
.with(
4756
tracing_subscriber::fmt::layer()
@@ -96,31 +105,46 @@ pub fn restore_tui() -> io::Result<()> {
96105
async fn main() -> color_eyre::Result<()> {
97106
color_eyre::install()?;
98107
init_panic_hook();
99-
init_logger();
100108
let args = Args::parse();
101109

110+
init_logger(&args.data);
111+
102112
tracing::info!("Start");
103-
let config = Config::new()?;
113+
let config = match Config::new(&args.config) {
114+
Ok(config) => config,
115+
Err(e) => {
116+
ratatui::restore();
117+
println!("Error: Failed to load config");
118+
return Err(e);
119+
}
120+
};
121+
let data_dir = Path::new(&args.data);
122+
if !data_dir.exists() {
123+
fs::create_dir_all(data_dir)?;
124+
}
125+
let history_path = data_dir.join(HISTORY_PATH);
104126
// start agent
105127
let (output_sender, output_receiver) =
106128
tokio::sync::mpsc::unbounded_channel::<AgentOutputEvent>();
107129
let (control_sender, control_receiver) =
108130
tokio::sync::mpsc::unbounded_channel::<AgentControlEvent>();
109-
let history = if !args.skip_load_messages && std::path::Path::new(HISTORY_PATH).exists() {
110-
serde_json::from_str(&std::fs::read_to_string(HISTORY_PATH).unwrap()).unwrap()
131+
let history = if !args.skip_load_messages && history_path.exists() {
132+
serde_json::from_str(&std::fs::read_to_string(history_path).unwrap()).unwrap()
111133
} else {
112134
Vec::new()
113135
};
114136

115-
let model_info = model_info(&config).await?;
137+
let model_info = model_info(&args.data, &config).await?;
116138
tracing::info!("Model info: {:?}", model_info);
117139

118-
let mut agent = agent::Agent::new(config.clone(), output_sender);
140+
let mut agent = agent::Agent::new(&args.data, config.clone(), output_sender);
119141
let memory_index = agent.init_memory_index().await;
120142

121143
let messages = history.clone();
122144
let agent_handler = tokio::spawn(async move {
123-
agent.run(control_receiver, messages, memory_index).await;
145+
agent
146+
.run(&args.data, control_receiver, messages, memory_index)
147+
.await;
124148
});
125149

126150
let terminal = init_tui().unwrap();

src/providers/model_info.rs

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ use serde::Deserialize;
66

77
use crate::config::Config;
88

9-
const OPENROUTER_MODELS_FILE: &str = "data/openrouter_models.json";
9+
const OPENROUTER_MODELS_FILE: &str = "openrouter_models.json";
1010
const ANTHROPIC_MODELS: &str = include_str!("anthropic_models.json");
1111
const OPENAI_MODELS: &str = include_str!("openai_models.json");
1212

@@ -53,7 +53,8 @@ struct OpenAIModelInfo {
5353
pub max_context_tokens: u32,
5454
}
5555

56-
pub async fn model_info(config: &Config) -> color_eyre::Result<ModelInfo> {
56+
pub async fn model_info(data_dir: &str, config: &Config) -> color_eyre::Result<ModelInfo> {
57+
let openrouter_models_file = Path::new(data_dir).join(OPENROUTER_MODELS_FILE);
5758
match config.provider {
5859
crate::config::ProviderKind::OpenAI => {
5960
let models: Vec<OpenAIModelInfo> = serde_json::from_str(OPENAI_MODELS)?;
@@ -69,16 +70,16 @@ pub async fn model_info(config: &Config) -> color_eyre::Result<ModelInfo> {
6970
}
7071
crate::config::ProviderKind::OpenRouter => {
7172
let models: Vec<OpenRouterModelInfo> =
72-
serde_json::from_value(if Path::new(OPENROUTER_MODELS_FILE).exists() {
73-
let data = fs::read_to_string(OPENROUTER_MODELS_FILE)?;
73+
serde_json::from_value(if openrouter_models_file.exists() {
74+
let data = fs::read_to_string(openrouter_models_file)?;
7475
serde_json::from_str(&data)?
7576
} else {
7677
let mut data = reqwest::get("https://openrouter.ai/api/v1/models")
7778
.await?
7879
.json::<serde_json::Value>()
7980
.await?;
8081
let data = data["data"].take();
81-
fs::write(OPENROUTER_MODELS_FILE, data.to_string())?;
82+
fs::write(openrouter_models_file, data.to_string())?;
8283
data
8384
})?;
8485
models

src/tools/memory/mod.rs

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
// Copyright © 2025 Huly Labs. Use of this source code is governed by the MIT license.
55
use std::collections::HashSet;
66
use std::fs;
7+
use std::path::{Path, PathBuf};
78
use std::sync::Arc;
89

910
use indicium::simple::{Indexable, SearchIndex};
@@ -19,7 +20,7 @@ use super::AgentToolError;
1920
mod tests;
2021

2122
const TOOLS_STR: &str = include_str!("tools.json");
22-
const MEMORY_PATH: &str = "data/memory.yaml";
23+
const MEMORY_PATH: &str = "memory.yaml";
2324

2425
#[derive(Clone, Deserialize)]
2526
struct JsonToolDefinition {
@@ -115,6 +116,7 @@ pub struct MemoryManager {
115116
memory_only: bool,
116117
knowledge_graph: KnowledgeGraph,
117118
search_index: SearchIndex<usize>,
119+
data_dir: PathBuf,
118120
}
119121

120122
impl Embed for Entity {
@@ -139,14 +141,15 @@ impl Indexable for Entity {
139141
}
140142

141143
impl MemoryManager {
142-
pub fn new(memory_only: bool) -> Self {
144+
pub fn new(data_dir: &str, memory_only: bool) -> Self {
143145
let knowledge_graph = if !memory_only {
144-
serde_yaml::from_str(&fs::read_to_string(MEMORY_PATH).unwrap_or_default()).unwrap_or(
145-
KnowledgeGraph {
146-
entities: Vec::new(),
147-
relations: Vec::new(),
148-
},
146+
serde_yaml::from_str(
147+
&fs::read_to_string(Path::new(data_dir).join(MEMORY_PATH)).unwrap_or_default(),
149148
)
149+
.unwrap_or(KnowledgeGraph {
150+
entities: Vec::new(),
151+
relations: Vec::new(),
152+
})
150153
} else {
151154
KnowledgeGraph {
152155
entities: Vec::new(),
@@ -167,6 +170,7 @@ impl MemoryManager {
167170
memory_only,
168171
knowledge_graph,
169172
search_index,
173+
data_dir: PathBuf::from(data_dir),
170174
}
171175
}
172176

@@ -357,7 +361,7 @@ impl MemoryManager {
357361
});
358362
if !self.memory_only {
359363
fs::write(
360-
MEMORY_PATH,
364+
self.data_dir.join(MEMORY_PATH),
361365
serde_yaml::to_string(&self.knowledge_graph).unwrap(),
362366
)
363367
.unwrap();

0 commit comments

Comments
 (0)