nika_engine/core/
paths.rs1use std::path::PathBuf;
38
39pub const NIKA_HOME_ENV: &str = "NIKA_HOME";
44
45pub const NIKA_DIR_NAME: &str = ".nika";
47
48pub const NIKA_PROJECT_DIR: &str = ".nika";
50
51pub const NIKA_MANIFEST: &str = "nika.yaml";
53
54pub const NIKA_LOCKFILE: &str = "nika.lock";
56
57pub const MCP_CONFIG: &str = "mcp.yaml";
59
60pub const GLOBAL_CONFIG: &str = "config.toml";
62
63pub const REGISTRY_INDEX: &str = "registry.yaml";
65
66pub const DAEMON_SOCKET: &str = "nika.sock";
68
69pub const DAEMON_PID: &str = "nika.pid";
71
72pub fn nika_home() -> PathBuf {
95 if let Ok(custom_home) = std::env::var(NIKA_HOME_ENV) {
97 return PathBuf::from(custom_home);
98 }
99
100 match dirs::home_dir() {
102 Some(h) => h.join(NIKA_DIR_NAME),
103 None => {
104 let fallback = std::env::temp_dir().join(NIKA_DIR_NAME);
107 tracing::error!(
108 path = %fallback.display(),
109 "Could not determine home directory. Using temporary fallback. Set NIKA_HOME environment variable for a secure persistent location."
110 );
111 fallback
112 }
113 }
114}
115
116pub fn nika_home_opt() -> Option<PathBuf> {
120 if let Ok(custom_home) = std::env::var(NIKA_HOME_ENV) {
121 return Some(PathBuf::from(custom_home));
122 }
123 dirs::home_dir().map(|h| h.join(NIKA_DIR_NAME))
124}
125
126pub fn nika_home_result() -> Result<PathBuf, crate::NikaError> {
135 nika_home_opt().ok_or(crate::NikaError::HomeDirectoryNotFound)
136}
137
138pub fn user_home_result() -> Result<PathBuf, crate::NikaError> {
144 dirs::home_dir().ok_or(crate::NikaError::HomeDirectoryNotFound)
145}
146
147pub fn packages_dir() -> PathBuf {
153 nika_home().join("packages")
154}
155
156pub fn models_dir() -> PathBuf {
158 nika_home().join("models")
159}
160
161pub fn backups_dir() -> PathBuf {
163 nika_home().join("backups")
164}
165
166pub fn cache_dir() -> PathBuf {
168 nika_home().join("cache")
169}
170
171pub fn daemon_dir() -> PathBuf {
174 nika_home().join("daemon")
175}
176
177pub fn global_config_path() -> PathBuf {
183 nika_home().join(GLOBAL_CONFIG)
184}
185
186pub fn global_mcp_config_path() -> PathBuf {
188 nika_home().join(MCP_CONFIG)
189}
190
191pub fn registry_index_path() -> PathBuf {
193 packages_dir().join(REGISTRY_INDEX)
194}
195
196pub fn daemon_socket_path() -> PathBuf {
199 daemon_dir().join(DAEMON_SOCKET)
200}
201
202pub fn daemon_pid_path() -> PathBuf {
205 daemon_dir().join(DAEMON_PID)
206}
207
208pub fn project_nika_dir(project_root: &std::path::Path) -> PathBuf {
214 project_root.join(NIKA_PROJECT_DIR)
215}
216
217pub fn project_mcp_config_path(project_root: &std::path::Path) -> PathBuf {
219 project_nika_dir(project_root).join(MCP_CONFIG)
220}
221
222pub fn project_manifest_path(project_root: &std::path::Path) -> PathBuf {
224 project_root.join(NIKA_MANIFEST)
225}
226
227pub fn project_lockfile_path(project_root: &std::path::Path) -> PathBuf {
229 project_root.join(NIKA_LOCKFILE)
230}
231
232pub fn project_sessions_dir(project_root: &std::path::Path) -> PathBuf {
234 project_nika_dir(project_root).join("sessions")
235}
236
237pub fn package_dir(scope: &str, name: &str, version: &str) -> PathBuf {
252 packages_dir().join(scope).join(name).join(version)
253}
254
255pub fn package_manifest_path(scope: &str, name: &str, version: &str) -> PathBuf {
257 package_dir(scope, name, version).join("manifest.yaml")
258}
259
260pub fn ensure_nika_home() -> std::io::Result<()> {
278 let home = nika_home();
279 std::fs::create_dir_all(&home)?;
280 std::fs::create_dir_all(home.join("packages"))?;
281 std::fs::create_dir_all(home.join("models"))?;
282 std::fs::create_dir_all(home.join("backups"))?;
283 std::fs::create_dir_all(home.join("cache"))?;
284 std::fs::create_dir_all(home.join("daemon"))?;
285 Ok(())
286}
287
288pub fn ensure_project_nika_dir(project_root: &std::path::Path) -> std::io::Result<()> {
294 let nika_dir = project_nika_dir(project_root);
295 std::fs::create_dir_all(&nika_dir)?;
296 std::fs::create_dir_all(nika_dir.join("sessions"))?;
297 Ok(())
298}
299
300#[cfg(test)]
305mod tests {
306 use super::*;
307 use serial_test::serial;
308 use std::env;
309
310 fn with_temp_nika_home<F, R>(f: F) -> R
312 where
313 F: FnOnce(&std::path::Path) -> R,
314 {
315 let temp_dir = tempfile::tempdir().unwrap();
316 let old_val = env::var(NIKA_HOME_ENV).ok();
317
318 env::set_var(NIKA_HOME_ENV, temp_dir.path());
319 let result = f(temp_dir.path());
320
321 if let Some(val) = old_val {
323 env::set_var(NIKA_HOME_ENV, val);
324 } else {
325 env::remove_var(NIKA_HOME_ENV);
326 }
327
328 result
329 }
330
331 #[test]
332 #[serial]
333 fn test_nika_home_default() {
334 let old_val = env::var(NIKA_HOME_ENV).ok();
336 env::remove_var(NIKA_HOME_ENV);
337
338 let home = nika_home();
339 assert!(home.ends_with(".nika"));
340
341 if let Some(val) = old_val {
343 env::set_var(NIKA_HOME_ENV, val);
344 }
345 }
346
347 #[test]
348 #[serial]
349 fn test_nika_home_env_override() {
350 with_temp_nika_home(|temp_path| {
351 let home = nika_home();
352 assert_eq!(home, temp_path);
353 });
354 }
355
356 #[test]
357 #[serial]
358 fn test_nika_home_opt_returns_some() {
359 with_temp_nika_home(|temp_path| {
360 let home = nika_home_opt();
361 assert_eq!(home, Some(temp_path.to_path_buf()));
362 });
363 }
364
365 #[test]
366 #[serial]
367 fn test_packages_dir() {
368 with_temp_nika_home(|temp_path| {
369 let packages = packages_dir();
370 assert_eq!(packages, temp_path.join("packages"));
371 });
372 }
373
374 #[test]
375 #[serial]
376 fn test_models_dir() {
377 with_temp_nika_home(|temp_path| {
378 let models = models_dir();
379 assert_eq!(models, temp_path.join("models"));
380 });
381 }
382
383 #[test]
384 #[serial]
385 fn test_backups_dir() {
386 with_temp_nika_home(|temp_path| {
387 let backups = backups_dir();
388 assert_eq!(backups, temp_path.join("backups"));
389 });
390 }
391
392 #[test]
393 #[serial]
394 fn test_cache_dir() {
395 with_temp_nika_home(|temp_path| {
396 let cache = cache_dir();
397 assert_eq!(cache, temp_path.join("cache"));
398 });
399 }
400
401 #[test]
402 #[serial]
403 fn test_daemon_dir() {
404 with_temp_nika_home(|temp_path| {
405 let daemon = daemon_dir();
406 assert_eq!(daemon, temp_path.join("daemon"));
407 });
408 }
409
410 #[test]
411 #[serial]
412 fn test_global_config_path() {
413 with_temp_nika_home(|temp_path| {
414 let config = global_config_path();
415 assert_eq!(config, temp_path.join("config.toml"));
416 });
417 }
418
419 #[test]
420 #[serial]
421 fn test_global_mcp_config_path() {
422 with_temp_nika_home(|temp_path| {
423 let mcp = global_mcp_config_path();
424 assert_eq!(mcp, temp_path.join("mcp.yaml"));
425 });
426 }
427
428 #[test]
429 #[serial]
430 fn test_registry_index_path() {
431 with_temp_nika_home(|temp_path| {
432 let registry = registry_index_path();
433 assert_eq!(registry, temp_path.join("packages").join("registry.yaml"));
434 });
435 }
436
437 #[test]
438 #[serial]
439 fn test_daemon_socket_path() {
440 with_temp_nika_home(|temp_path| {
441 let socket = daemon_socket_path();
442 assert_eq!(socket, temp_path.join("daemon").join("nika.sock"));
443 });
444 }
445
446 #[test]
447 #[serial]
448 fn test_daemon_pid_path() {
449 with_temp_nika_home(|temp_path| {
450 let pid = daemon_pid_path();
451 assert_eq!(pid, temp_path.join("daemon").join("nika.pid"));
452 });
453 }
454
455 #[test]
456 fn test_project_paths() {
457 let project_root = std::path::Path::new("/tmp/my-project");
458
459 assert_eq!(
460 project_nika_dir(project_root),
461 PathBuf::from("/tmp/my-project/.nika")
462 );
463
464 assert_eq!(
465 project_mcp_config_path(project_root),
466 PathBuf::from("/tmp/my-project/.nika/mcp.yaml")
467 );
468
469 assert_eq!(
470 project_manifest_path(project_root),
471 PathBuf::from("/tmp/my-project/nika.yaml")
472 );
473
474 assert_eq!(
475 project_lockfile_path(project_root),
476 PathBuf::from("/tmp/my-project/nika.lock")
477 );
478
479 assert_eq!(
480 project_sessions_dir(project_root),
481 PathBuf::from("/tmp/my-project/.nika/sessions")
482 );
483 }
484
485 #[test]
486 #[serial]
487 fn test_package_dir() {
488 with_temp_nika_home(|temp_path| {
489 let pkg = package_dir("@nika", "seo-audit", "1.0.0");
490 assert_eq!(pkg, temp_path.join("packages/@nika/seo-audit/1.0.0"));
491 });
492 }
493
494 #[test]
495 #[serial]
496 fn test_package_manifest_path() {
497 with_temp_nika_home(|temp_path| {
498 let manifest = package_manifest_path("@workflows", "code-review", "2.1.0");
499 assert_eq!(
500 manifest,
501 temp_path.join("packages/@workflows/code-review/2.1.0/manifest.yaml")
502 );
503 });
504 }
505
506 #[test]
507 #[serial]
508 fn test_ensure_nika_home_creates_directories() {
509 with_temp_nika_home(|temp_path| {
510 let _ = std::fs::remove_dir_all(temp_path);
512
513 ensure_nika_home().unwrap();
515
516 assert!(temp_path.exists());
517 assert!(temp_path.join("packages").exists());
518 assert!(temp_path.join("models").exists());
519 assert!(temp_path.join("backups").exists());
520 assert!(temp_path.join("cache").exists());
521 assert!(temp_path.join("daemon").exists());
522 });
523 }
524
525 #[test]
526 fn test_ensure_project_nika_dir() {
527 let temp_dir = tempfile::tempdir().unwrap();
528 let project_root = temp_dir.path();
529
530 ensure_project_nika_dir(project_root).unwrap();
531
532 assert!(project_root.join(".nika").exists());
533 assert!(project_root.join(".nika/sessions").exists());
534 }
535
536 #[test]
537 fn test_constants() {
538 assert_eq!(NIKA_HOME_ENV, "NIKA_HOME");
539 assert_eq!(NIKA_DIR_NAME, ".nika");
540 assert_eq!(NIKA_PROJECT_DIR, ".nika");
541 assert_eq!(NIKA_MANIFEST, "nika.yaml");
542 assert_eq!(NIKA_LOCKFILE, "nika.lock");
543 assert_eq!(MCP_CONFIG, "mcp.yaml");
544 assert_eq!(GLOBAL_CONFIG, "config.toml");
545 assert_eq!(REGISTRY_INDEX, "registry.yaml");
546 assert_eq!(DAEMON_SOCKET, "nika.sock");
547 assert_eq!(DAEMON_PID, "nika.pid");
548 }
549}