nmem-cli 0.10.4__tar.gz → 0.10.6__tar.gz
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.
- {nmem_cli-0.10.4 → nmem_cli-0.10.6}/Cargo.lock +13 -13
- {nmem_cli-0.10.4 → nmem_cli-0.10.6}/Cargo.toml +1 -1
- {nmem_cli-0.10.4 → nmem_cli-0.10.6}/PKG-INFO +1 -1
- {nmem_cli-0.10.4 → nmem_cli-0.10.6}/crates/nmem-cli/src/cli.rs +19 -3
- {nmem_cli-0.10.4 → nmem_cli-0.10.6}/crates/nmem-cli/src/main.rs +180 -0
- {nmem_cli-0.10.4 → nmem_cli-0.10.6}/pyproject.toml +1 -1
- {nmem_cli-0.10.4 → nmem_cli-0.10.6}/README.md +0 -0
- {nmem_cli-0.10.4 → nmem_cli-0.10.6}/crates/nmem-cli/Cargo.toml +0 -0
- {nmem_cli-0.10.4 → nmem_cli-0.10.6}/crates/nmem-cli/README.md +0 -0
- {nmem_cli-0.10.4 → nmem_cli-0.10.6}/crates/nmem-cli/src/client.rs +0 -0
- {nmem_cli-0.10.4 → nmem_cli-0.10.6}/crates/nmem-cli/src/commands/agents.rs +0 -0
- {nmem_cli-0.10.4 → nmem_cli-0.10.6}/crates/nmem-cli/src/commands/communities.rs +0 -0
- {nmem_cli-0.10.4 → nmem_cli-0.10.6}/crates/nmem-cli/src/commands/data_transfer.rs +0 -0
- {nmem_cli-0.10.4 → nmem_cli-0.10.6}/crates/nmem-cli/src/commands/feed.rs +0 -0
- {nmem_cli-0.10.4 → nmem_cli-0.10.6}/crates/nmem-cli/src/commands/fs.rs +0 -0
- {nmem_cli-0.10.4 → nmem_cli-0.10.6}/crates/nmem-cli/src/commands/graph.rs +0 -0
- {nmem_cli-0.10.4 → nmem_cli-0.10.6}/crates/nmem-cli/src/commands/library.rs +0 -0
- {nmem_cli-0.10.4 → nmem_cli-0.10.6}/crates/nmem-cli/src/commands/license.rs +0 -0
- {nmem_cli-0.10.4 → nmem_cli-0.10.6}/crates/nmem-cli/src/commands/memories.rs +0 -0
- {nmem_cli-0.10.4 → nmem_cli-0.10.6}/crates/nmem-cli/src/commands/memory_relations.rs +0 -0
- {nmem_cli-0.10.4 → nmem_cli-0.10.6}/crates/nmem-cli/src/commands/mod.rs +0 -0
- {nmem_cli-0.10.4 → nmem_cli-0.10.6}/crates/nmem-cli/src/commands/models.rs +0 -0
- {nmem_cli-0.10.4 → nmem_cli-0.10.6}/crates/nmem-cli/src/commands/plugins.rs +0 -0
- {nmem_cli-0.10.4 → nmem_cli-0.10.6}/crates/nmem-cli/src/commands/provider.rs +0 -0
- {nmem_cli-0.10.4 → nmem_cli-0.10.6}/crates/nmem-cli/src/commands/rules.rs +0 -0
- {nmem_cli-0.10.4 → nmem_cli-0.10.6}/crates/nmem-cli/src/commands/session_import.rs +0 -0
- {nmem_cli-0.10.4 → nmem_cli-0.10.6}/crates/nmem-cli/src/commands/skills.rs +0 -0
- {nmem_cli-0.10.4 → nmem_cli-0.10.6}/crates/nmem-cli/src/commands/spaces.rs +0 -0
- {nmem_cli-0.10.4 → nmem_cli-0.10.6}/crates/nmem-cli/src/commands/stubs.rs +0 -0
- {nmem_cli-0.10.4 → nmem_cli-0.10.6}/crates/nmem-cli/src/commands/system.rs +0 -0
- {nmem_cli-0.10.4 → nmem_cli-0.10.6}/crates/nmem-cli/src/commands/threads.rs +0 -0
- {nmem_cli-0.10.4 → nmem_cli-0.10.6}/crates/nmem-cli/src/commands/wiki.rs +0 -0
- {nmem_cli-0.10.4 → nmem_cli-0.10.6}/crates/nmem-cli/src/commands/working_memory.rs +0 -0
- {nmem_cli-0.10.4 → nmem_cli-0.10.6}/crates/nmem-cli/src/config.rs +0 -0
- {nmem_cli-0.10.4 → nmem_cli-0.10.6}/crates/nmem-cli/src/format.rs +0 -0
- {nmem_cli-0.10.4 → nmem_cli-0.10.6}/crates/nmem-cli/src/style.rs +0 -0
|
@@ -5206,7 +5206,7 @@ dependencies = [
|
|
|
5206
5206
|
|
|
5207
5207
|
[[package]]
|
|
5208
5208
|
name = "nmem-cli"
|
|
5209
|
-
version = "0.10.
|
|
5209
|
+
version = "0.10.6"
|
|
5210
5210
|
dependencies = [
|
|
5211
5211
|
"anstyle",
|
|
5212
5212
|
"anyhow",
|
|
@@ -5222,7 +5222,7 @@ dependencies = [
|
|
|
5222
5222
|
|
|
5223
5223
|
[[package]]
|
|
5224
5224
|
name = "nmem-content"
|
|
5225
|
-
version = "0.10.
|
|
5225
|
+
version = "0.10.6"
|
|
5226
5226
|
dependencies = [
|
|
5227
5227
|
"anyhow",
|
|
5228
5228
|
"md5",
|
|
@@ -5234,7 +5234,7 @@ dependencies = [
|
|
|
5234
5234
|
|
|
5235
5235
|
[[package]]
|
|
5236
5236
|
name = "nmem-core"
|
|
5237
|
-
version = "0.10.
|
|
5237
|
+
version = "0.10.6"
|
|
5238
5238
|
dependencies = [
|
|
5239
5239
|
"serde",
|
|
5240
5240
|
"serde_json",
|
|
@@ -5244,7 +5244,7 @@ dependencies = [
|
|
|
5244
5244
|
|
|
5245
5245
|
[[package]]
|
|
5246
5246
|
name = "nmem-docs"
|
|
5247
|
-
version = "0.10.
|
|
5247
|
+
version = "0.10.6"
|
|
5248
5248
|
dependencies = [
|
|
5249
5249
|
"criterion",
|
|
5250
5250
|
"scraper",
|
|
@@ -5254,7 +5254,7 @@ dependencies = [
|
|
|
5254
5254
|
|
|
5255
5255
|
[[package]]
|
|
5256
5256
|
name = "nmem-embed"
|
|
5257
|
-
version = "0.10.
|
|
5257
|
+
version = "0.10.6"
|
|
5258
5258
|
dependencies = [
|
|
5259
5259
|
"anyhow",
|
|
5260
5260
|
"arrow-array",
|
|
@@ -5274,7 +5274,7 @@ dependencies = [
|
|
|
5274
5274
|
|
|
5275
5275
|
[[package]]
|
|
5276
5276
|
name = "nmem-feed"
|
|
5277
|
-
version = "0.10.
|
|
5277
|
+
version = "0.10.6"
|
|
5278
5278
|
dependencies = [
|
|
5279
5279
|
"anyhow",
|
|
5280
5280
|
"serde_json",
|
|
@@ -5283,7 +5283,7 @@ dependencies = [
|
|
|
5283
5283
|
|
|
5284
5284
|
[[package]]
|
|
5285
5285
|
name = "nmem-graph"
|
|
5286
|
-
version = "0.10.
|
|
5286
|
+
version = "0.10.6"
|
|
5287
5287
|
dependencies = [
|
|
5288
5288
|
"anyhow",
|
|
5289
5289
|
"caseless",
|
|
@@ -5302,7 +5302,7 @@ dependencies = [
|
|
|
5302
5302
|
|
|
5303
5303
|
[[package]]
|
|
5304
5304
|
name = "nmem-harness"
|
|
5305
|
-
version = "0.10.
|
|
5305
|
+
version = "0.10.6"
|
|
5306
5306
|
dependencies = [
|
|
5307
5307
|
"anyhow",
|
|
5308
5308
|
"futures",
|
|
@@ -5322,7 +5322,7 @@ dependencies = [
|
|
|
5322
5322
|
|
|
5323
5323
|
[[package]]
|
|
5324
5324
|
name = "nmem-model"
|
|
5325
|
-
version = "0.10.
|
|
5325
|
+
version = "0.10.6"
|
|
5326
5326
|
dependencies = [
|
|
5327
5327
|
"anyhow",
|
|
5328
5328
|
"encoding_rs",
|
|
@@ -5351,7 +5351,7 @@ dependencies = [
|
|
|
5351
5351
|
|
|
5352
5352
|
[[package]]
|
|
5353
5353
|
name = "nmem-search"
|
|
5354
|
-
version = "0.10.
|
|
5354
|
+
version = "0.10.6"
|
|
5355
5355
|
dependencies = [
|
|
5356
5356
|
"anyhow",
|
|
5357
5357
|
"arrow-array",
|
|
@@ -5373,7 +5373,7 @@ dependencies = [
|
|
|
5373
5373
|
|
|
5374
5374
|
[[package]]
|
|
5375
5375
|
name = "nmem-server"
|
|
5376
|
-
version = "0.10.
|
|
5376
|
+
version = "0.10.6"
|
|
5377
5377
|
dependencies = [
|
|
5378
5378
|
"aes-gcm",
|
|
5379
5379
|
"anyhow",
|
|
@@ -5429,7 +5429,7 @@ dependencies = [
|
|
|
5429
5429
|
|
|
5430
5430
|
[[package]]
|
|
5431
5431
|
name = "nmem-tui"
|
|
5432
|
-
version = "0.10.
|
|
5432
|
+
version = "0.10.6"
|
|
5433
5433
|
dependencies = [
|
|
5434
5434
|
"anyhow",
|
|
5435
5435
|
"arboard",
|
|
@@ -5443,7 +5443,7 @@ dependencies = [
|
|
|
5443
5443
|
|
|
5444
5444
|
[[package]]
|
|
5445
5445
|
name = "nmem-websearch"
|
|
5446
|
-
version = "0.10.
|
|
5446
|
+
version = "0.10.6"
|
|
5447
5447
|
dependencies = [
|
|
5448
5448
|
"anyhow",
|
|
5449
5449
|
"dom_smoothie",
|
|
@@ -178,6 +178,19 @@ pub enum Command {
|
|
|
178
178
|
#[arg(trailing_var_arg = true, allow_hyphen_values = true)]
|
|
179
179
|
passthrough: Vec<String>,
|
|
180
180
|
},
|
|
181
|
+
/// Run the backend server (headless / self-hosted). Execs the sibling
|
|
182
|
+
/// `nmem-server` binary. Compatibility shim for systemd units written by the
|
|
183
|
+
/// old Python `nmem service install` (`ExecStart=… nmem serve --host … --port …`),
|
|
184
|
+
/// which broke when the Rust CLI — a REST client with no built-in server —
|
|
185
|
+
/// took over `/usr/local/bin/nmem`.
|
|
186
|
+
Serve {
|
|
187
|
+
/// Bind host (maps to NMEM_SERVER_ADDR host).
|
|
188
|
+
#[arg(long, default_value = "127.0.0.1")]
|
|
189
|
+
host: String,
|
|
190
|
+
/// Bind port (maps to NMEM_SERVER_ADDR port).
|
|
191
|
+
#[arg(long, default_value_t = 14242)]
|
|
192
|
+
port: u16,
|
|
193
|
+
},
|
|
181
194
|
}
|
|
182
195
|
|
|
183
196
|
// ── skills ───────────────────────────────────────────────────────────────────
|
|
@@ -1403,7 +1416,8 @@ pub struct ThreadImportArgs {
|
|
|
1403
1416
|
|
|
1404
1417
|
#[derive(Args, Debug)]
|
|
1405
1418
|
pub struct ThreadSaveArgs {
|
|
1406
|
-
/// Source app: claude-code, codex, cursor, gemini-cli,
|
|
1419
|
+
/// Source app: claude-code, codex, cursor, gemini-cli, grok, hermes,
|
|
1420
|
+
/// kimi-code, kimi-work, mimo-code, omp, opencode, pi.
|
|
1407
1421
|
#[arg(long = "from", value_name = "HOST")]
|
|
1408
1422
|
pub source_app: String,
|
|
1409
1423
|
/// Project directory path (default: current dir).
|
|
@@ -1439,7 +1453,8 @@ pub struct ThreadSaveArgs {
|
|
|
1439
1453
|
|
|
1440
1454
|
#[derive(Args, Debug)]
|
|
1441
1455
|
pub struct ThreadSyncArgs {
|
|
1442
|
-
/// Source app: claude-code, codex, cursor, gemini-cli,
|
|
1456
|
+
/// Source app: claude-code, codex, cursor, gemini-cli, grok, hermes,
|
|
1457
|
+
/// kimi-code, kimi-work, mimo-code, omp, opencode, pi.
|
|
1443
1458
|
#[arg(long = "from", value_name = "HOST")]
|
|
1444
1459
|
pub source_app: String,
|
|
1445
1460
|
/// Project directory path.
|
|
@@ -1457,7 +1472,8 @@ pub struct ThreadSyncArgs {
|
|
|
1457
1472
|
/// Specific session ID.
|
|
1458
1473
|
#[arg(long = "session-id")]
|
|
1459
1474
|
pub session_id: Option<String>,
|
|
1460
|
-
///
|
|
1475
|
+
/// Host transcript directory/file to scan (repeatable). Supports sources
|
|
1476
|
+
/// such as Cursor workspaces, Pi/OMP JSONL roots, and Hermes state.db.
|
|
1461
1477
|
#[arg(long = "session-dir")]
|
|
1462
1478
|
pub session_dirs: Vec<String>,
|
|
1463
1479
|
/// Truncate large tool results where supported.
|
|
@@ -45,6 +45,11 @@ async fn run(cli: Cli) -> anyhow::Result<()> {
|
|
|
45
45
|
return launch_tui(api_flag, passthrough);
|
|
46
46
|
}
|
|
47
47
|
|
|
48
|
+
// `serve` execs the backend (`nmem-server`) and needs no live client.
|
|
49
|
+
if let Command::Serve { host, port } = &cli.command {
|
|
50
|
+
return launch_server(host, *port);
|
|
51
|
+
}
|
|
52
|
+
|
|
48
53
|
// Local-only config verbs (show/set/get-url/mcp show) need no live client.
|
|
49
54
|
// Provider verbs hit /remote-llm/* and fall through to the client path.
|
|
50
55
|
if let Command::Config { action } = &cli.command {
|
|
@@ -400,6 +405,7 @@ async fn run(cli: Cli) -> anyhow::Result<()> {
|
|
|
400
405
|
|
|
401
406
|
// Handled before the client is built.
|
|
402
407
|
Command::Tui { .. } => unreachable!("tui handled before client init"),
|
|
408
|
+
Command::Serve { .. } => unreachable!("serve handled before client init"),
|
|
403
409
|
}
|
|
404
410
|
}
|
|
405
411
|
|
|
@@ -440,11 +446,185 @@ fn launch_tui(api_flag: Option<&str>, passthrough: &[String]) -> anyhow::Result<
|
|
|
440
446
|
std::process::exit(status.code().unwrap_or(1));
|
|
441
447
|
}
|
|
442
448
|
|
|
449
|
+
/// Run the backend by exec'ing the sibling `nmem-server` binary (resolved next
|
|
450
|
+
/// to `nmem`, like the TUI). `nmem-server` takes its bind address from
|
|
451
|
+
/// `NMEM_SERVER_ADDR`, so map `--host`/`--port` onto that (only if the caller
|
|
452
|
+
/// hasn't already set it). On Unix we `exec` (replace the process) so systemd
|
|
453
|
+
/// tracks the server's own PID instead of a wrapper; on Windows we spawn and
|
|
454
|
+
/// forward the exit code. Compatibility shim for legacy `nmem serve …` units.
|
|
455
|
+
fn launch_server(host: &str, port: u16) -> anyhow::Result<()> {
|
|
456
|
+
let exe = std::env::current_exe()
|
|
457
|
+
.map_err(|e| anyhow::anyhow!("cannot resolve current executable: {e}"))?;
|
|
458
|
+
let dir = exe
|
|
459
|
+
.parent()
|
|
460
|
+
.ok_or_else(|| anyhow::anyhow!("cannot resolve executable directory"))?;
|
|
461
|
+
let bin_name = if cfg!(target_os = "windows") {
|
|
462
|
+
"nmem-server.exe"
|
|
463
|
+
} else {
|
|
464
|
+
"nmem-server"
|
|
465
|
+
};
|
|
466
|
+
let server_path = dir.join(bin_name);
|
|
467
|
+
if !server_path.exists() {
|
|
468
|
+
anyhow::bail!(
|
|
469
|
+
"the backend binary `{}` was not found next to `nmem` (looked in {}). \
|
|
470
|
+
`nmem serve` runs the bundled backend; install the full package, not just the CLI.",
|
|
471
|
+
bin_name,
|
|
472
|
+
dir.display()
|
|
473
|
+
);
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
let mut cmd = std::process::Command::new(&server_path);
|
|
477
|
+
// Respect an explicit NMEM_SERVER_ADDR; otherwise derive it from --host/--port.
|
|
478
|
+
if std::env::var_os("NMEM_SERVER_ADDR").is_none() {
|
|
479
|
+
cmd.env("NMEM_SERVER_ADDR", format!("{host}:{port}"));
|
|
480
|
+
}
|
|
481
|
+
// Derive a default NMEM_APP_DATA when unset, so a LEGACY systemd unit (the
|
|
482
|
+
// Python `nmem service install` generated `nmem.service` sets only HOME/XDG_*
|
|
483
|
+
// and calls `nmem serve`) still finds the user's existing graph. Without this
|
|
484
|
+
// the server's `apply_app_data_root` no-ops and it boots WITHOUT a writable
|
|
485
|
+
// graph/search/content surface. Explicit env always wins (docker/operators/the
|
|
486
|
+
// Tauri launcher set it). The canonical graph dir is `<platform-data>/NowledgeGraph`
|
|
487
|
+
// (identical in the Tauri launcher `setup_user_directory` and the Python
|
|
488
|
+
// `get_nowledge_graph_data_dir`); it IS the dir holding `nowledge_graph_v2.db`,
|
|
489
|
+
// matching the NMEM_APP_DATA contract in the server's `apply_app_data_root`.
|
|
490
|
+
if std::env::var_os("NMEM_APP_DATA").is_none() {
|
|
491
|
+
match default_app_data_dir() {
|
|
492
|
+
Some(dir) => {
|
|
493
|
+
cmd.env("NMEM_APP_DATA", &dir);
|
|
494
|
+
}
|
|
495
|
+
None => {
|
|
496
|
+
// Home unresolvable: never guess a path that could boot against an
|
|
497
|
+
// empty dir — tell the operator to set it explicitly instead.
|
|
498
|
+
eprintln!(
|
|
499
|
+
"nmem serve: NMEM_APP_DATA is unset and the home directory could not be \
|
|
500
|
+
resolved; set NMEM_APP_DATA to your NowledgeGraph data dir so the server \
|
|
501
|
+
serves your existing data."
|
|
502
|
+
);
|
|
503
|
+
}
|
|
504
|
+
}
|
|
505
|
+
}
|
|
506
|
+
// Config root: the legacy Python CLI stored app-settings.json / remote_llm.json
|
|
507
|
+
// / license under the platform CONFIG dir (XDG_CONFIG_HOME / %APPDATA% /
|
|
508
|
+
// Application Support). Without NMEM_APP_CONFIG_DIR the server's
|
|
509
|
+
// apply_app_data_root pins it to the GRAPH sibling instead, so an upgraded
|
|
510
|
+
// legacy headless install gets its graph back but loses provider, license, and
|
|
511
|
+
// access settings. Set the platform config dir when unset (explicit wins).
|
|
512
|
+
if std::env::var_os("NMEM_APP_CONFIG_DIR").is_none() {
|
|
513
|
+
if let Some(cfg) = default_config_dir() {
|
|
514
|
+
cmd.env("NMEM_APP_CONFIG_DIR", &cfg);
|
|
515
|
+
}
|
|
516
|
+
}
|
|
517
|
+
// Headless marker: `nmem serve` on a non-loopback bind (LAN / 0.0.0.0) is a
|
|
518
|
+
// DELIBERATE headless server (matching the Python image's `serve --host
|
|
519
|
+
// 0.0.0.0`). Mark it headless so the server's off-loopback fail-closed guard
|
|
520
|
+
// WARNS instead of REFUSING — otherwise an upgraded systemd/LAN start exits
|
|
521
|
+
// with the unauthenticated-bind error. Explicit NOWLEDGE_HEADLESS wins; a
|
|
522
|
+
// loopback bind stays unmarked (the desktop/local default).
|
|
523
|
+
if std::env::var_os("NOWLEDGE_HEADLESS").is_none() && !host_is_loopback(host) {
|
|
524
|
+
cmd.env("NOWLEDGE_HEADLESS", "1");
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
#[cfg(unix)]
|
|
528
|
+
{
|
|
529
|
+
use std::os::unix::process::CommandExt;
|
|
530
|
+
// exec replaces this process; only returns on failure.
|
|
531
|
+
let err = cmd.exec();
|
|
532
|
+
Err(anyhow::anyhow!(
|
|
533
|
+
"failed to exec {}: {err}",
|
|
534
|
+
server_path.display()
|
|
535
|
+
))
|
|
536
|
+
}
|
|
537
|
+
#[cfg(not(unix))]
|
|
538
|
+
{
|
|
539
|
+
let status = cmd
|
|
540
|
+
.status()
|
|
541
|
+
.map_err(|e| anyhow::anyhow!("failed to launch {}: {e}", server_path.display()))?;
|
|
542
|
+
std::process::exit(status.code().unwrap_or(1));
|
|
543
|
+
}
|
|
544
|
+
}
|
|
545
|
+
|
|
546
|
+
/// The canonical NowledgeGraph graph DATA dir for the current platform, or
|
|
547
|
+
/// `None` when the home directory cannot be resolved. Identical to the Tauri
|
|
548
|
+
/// launcher `setup_user_directory` + the Python `get_nowledge_graph_data_dir`,
|
|
549
|
+
/// so a default-derived `NMEM_APP_DATA` points at the SAME dir the desktop/Python
|
|
550
|
+
/// already wrote (never an empty new one):
|
|
551
|
+
/// - macOS: `~/Library/Application Support/NowledgeGraph`
|
|
552
|
+
/// - Windows: `%LOCALAPPDATA%/NowledgeGraph` (fallback `~/AppData/Local/...`)
|
|
553
|
+
/// - Linux: `$XDG_DATA_HOME/NowledgeGraph` (fallback `~/.local/share/...`)
|
|
554
|
+
fn default_app_data_dir() -> Option<std::path::PathBuf> {
|
|
555
|
+
let home = std::env::var_os("HOME")
|
|
556
|
+
.or_else(|| std::env::var_os("USERPROFILE"))
|
|
557
|
+
.map(std::path::PathBuf::from)
|
|
558
|
+
.filter(|p| !p.as_os_str().is_empty())?;
|
|
559
|
+
Some(default_app_data_dir_from(&home))
|
|
560
|
+
}
|
|
561
|
+
|
|
562
|
+
/// Pure, testable core of [`default_app_data_dir`] (home injected).
|
|
563
|
+
fn default_app_data_dir_from(home: &std::path::Path) -> std::path::PathBuf {
|
|
564
|
+
let base = if cfg!(target_os = "macos") {
|
|
565
|
+
home.join("Library").join("Application Support")
|
|
566
|
+
} else if cfg!(target_os = "windows") {
|
|
567
|
+
std::env::var_os("LOCALAPPDATA")
|
|
568
|
+
.map(std::path::PathBuf::from)
|
|
569
|
+
.filter(|p| !p.as_os_str().is_empty())
|
|
570
|
+
.unwrap_or_else(|| home.join("AppData").join("Local"))
|
|
571
|
+
} else {
|
|
572
|
+
std::env::var_os("XDG_DATA_HOME")
|
|
573
|
+
.map(std::path::PathBuf::from)
|
|
574
|
+
.filter(|p| !p.as_os_str().is_empty())
|
|
575
|
+
.unwrap_or_else(|| home.join(".local").join("share"))
|
|
576
|
+
};
|
|
577
|
+
base.join("NowledgeGraph")
|
|
578
|
+
}
|
|
579
|
+
|
|
580
|
+
/// The platform CONFIG dir `co.nowledge.mem.desktop` (app-settings / remote_llm /
|
|
581
|
+
/// license / device files), mirroring the server's `app_desktop_dir` + Python's
|
|
582
|
+
/// `get_app_config_dir`. `None` when home cannot be resolved.
|
|
583
|
+
/// - macOS: `~/Library/Application Support/co.nowledge.mem.desktop`
|
|
584
|
+
/// - Windows: `%APPDATA%/co.nowledge.mem.desktop` (Roaming; fallback `~/AppData/Roaming`)
|
|
585
|
+
/// - Linux: `$XDG_CONFIG_HOME/co.nowledge.mem.desktop` (fallback `~/.config`)
|
|
586
|
+
fn default_config_dir() -> Option<std::path::PathBuf> {
|
|
587
|
+
let home = std::env::var_os("HOME")
|
|
588
|
+
.or_else(|| std::env::var_os("USERPROFILE"))
|
|
589
|
+
.map(std::path::PathBuf::from)
|
|
590
|
+
.filter(|p| !p.as_os_str().is_empty())?;
|
|
591
|
+
let base = if cfg!(target_os = "macos") {
|
|
592
|
+
home.join("Library").join("Application Support")
|
|
593
|
+
} else if cfg!(target_os = "windows") {
|
|
594
|
+
std::env::var_os("APPDATA")
|
|
595
|
+
.map(std::path::PathBuf::from)
|
|
596
|
+
.filter(|p| !p.as_os_str().is_empty())
|
|
597
|
+
.unwrap_or_else(|| home.join("AppData").join("Roaming"))
|
|
598
|
+
} else {
|
|
599
|
+
std::env::var_os("XDG_CONFIG_HOME")
|
|
600
|
+
.map(std::path::PathBuf::from)
|
|
601
|
+
.filter(|p| !p.as_os_str().is_empty())
|
|
602
|
+
.unwrap_or_else(|| home.join(".config"))
|
|
603
|
+
};
|
|
604
|
+
Some(base.join("co.nowledge.mem.desktop"))
|
|
605
|
+
}
|
|
606
|
+
|
|
607
|
+
/// True when `host` is a loopback bind (the server only refuses an
|
|
608
|
+
/// off-loopback, unauthenticated bind — loopback never needs the headless mark).
|
|
609
|
+
fn host_is_loopback(host: &str) -> bool {
|
|
610
|
+
matches!(host, "127.0.0.1" | "localhost" | "::1" | "[::1]")
|
|
611
|
+
|| host.starts_with("127.")
|
|
612
|
+
|| host.eq_ignore_ascii_case("localhost")
|
|
613
|
+
}
|
|
614
|
+
|
|
443
615
|
#[cfg(test)]
|
|
444
616
|
mod arg_tests {
|
|
445
617
|
use super::*;
|
|
446
618
|
use clap::Parser;
|
|
447
619
|
|
|
620
|
+
#[test]
|
|
621
|
+
fn default_app_data_dir_ends_in_nowledge_graph() {
|
|
622
|
+
let home = std::path::Path::new("/home/tester");
|
|
623
|
+
let dir = default_app_data_dir_from(home);
|
|
624
|
+
assert!(dir.ends_with("NowledgeGraph"), "must be the NowledgeGraph graph dir: {dir:?}");
|
|
625
|
+
assert!(dir.starts_with(home), "must hang off the given home: {dir:?}");
|
|
626
|
+
}
|
|
627
|
+
|
|
448
628
|
#[test]
|
|
449
629
|
fn parses_global_api_and_json() {
|
|
450
630
|
let cli =
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|