nemoris 0.1.5 → 0.1.6

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/SECURITY.md CHANGED
@@ -23,7 +23,7 @@ You will receive an acknowledgement within 48 hours. We aim to provide a substan
23
23
 
24
24
  Nemoris runs locally on your machine. Key security properties:
25
25
 
26
- - **API keys** are stored in `~/.nemoris/.env` with `0600` permissions (owner read/write only)
26
+ - **API keys** are stored in `~/.nemoris-data/.env` with `0600` permissions (owner read/write only)
27
27
  - **Keys are never logged** — the runtime redacts secrets from all log output
28
28
  - **Exec approval gates** require human confirmation before shell commands execute
29
29
  - **SSRF protection** on all URL-intake surfaces
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nemoris",
3
- "version": "0.1.5",
3
+ "version": "0.1.6",
4
4
  "type": "module",
5
5
  "description": "Personal AI agent runtime — persistent memory, delivery guarantees, task contracts, self-healing. Local-first, no cloud.",
6
6
  "license": "MIT",
@@ -11,16 +11,21 @@ function dedupe(items = []) {
11
11
 
12
12
  export function resolveInstallDir({ env = process.env, cwd: _cwd = process.cwd() } = {}) {
13
13
  if (env.NEMORIS_INSTALL_DIR) {
14
- return env.NEMORIS_INSTALL_DIR;
15
- }
16
- if (_cwd) {
17
- const hasPackageJson = fs.existsSync(path.join(_cwd, "package.json"));
18
- const hasCliEntry = fs.existsSync(path.join(_cwd, "src", "cli.js"));
19
- if (hasPackageJson && hasCliEntry) {
20
- return _cwd;
14
+ const resolved = env.NEMORIS_INSTALL_DIR;
15
+ if (fs.existsSync(path.join(resolved, "package.json"))) {
16
+ throw new Error(
17
+ `NEMORIS_INSTALL_DIR "${resolved}" looks like a source repo.\n` +
18
+ `Set NEMORIS_INSTALL_DIR to a clean data directory (e.g. ~/.nemoris-data).`
19
+ );
21
20
  }
21
+ return resolved;
22
+ }
23
+ // Dev mode: when CWD is the source repo, use it as install dir
24
+ if (_cwd && fs.existsSync(path.join(_cwd, "package.json")) &&
25
+ fs.existsSync(path.join(_cwd, "src", "cli.js"))) {
26
+ return _cwd;
22
27
  }
23
- return path.join(os.homedir(), ".nemoris");
28
+ return path.join(os.homedir(), ".nemoris-data");
24
29
  }
25
30
 
26
31
  export function resolveAuthProfilesPath({ env = process.env, cwd = process.cwd() } = {}) {
package/src/cli-main.js CHANGED
@@ -53,51 +53,52 @@ import {
53
53
  import { resolveAuthProfilesPath, resolveInstallDir } from "./auth/auth-profiles.js";
54
54
 
55
55
  const __dirname = path.dirname(fileURLToPath(import.meta.url));
56
- const projectRoot = path.join(__dirname, "..");
57
- const stateRoot = path.join(projectRoot, "state", "memory");
58
- const liveRoot = resolveLiveRoot(projectRoot);
56
+ const projectRoot = path.join(__dirname, ".."); // npm package dir — only for package.json reads
57
+ const installDir = resolveRuntimeInstallDir(); // user data dir — all runtime state lives here
58
+ const stateRoot = path.join(installDir, "state", "memory");
59
+ const liveRoot = resolveLiveRoot(installDir);
59
60
  const scheduler = new Scheduler({
60
- projectRoot,
61
+ projectRoot: installDir,
61
62
  liveRoot,
62
- stateRoot: path.join(projectRoot, "state")
63
+ stateRoot: path.join(installDir, "state")
63
64
  });
64
65
  const executor = new Executor({
65
- projectRoot,
66
+ projectRoot: installDir,
66
67
  liveRoot,
67
- stateRoot: path.join(projectRoot, "state")
68
+ stateRoot: path.join(installDir, "state")
68
69
  });
69
70
  const daemon = new SchedulerDaemon({
70
- projectRoot,
71
+ projectRoot: installDir,
71
72
  liveRoot,
72
- stateRoot: path.join(projectRoot, "state")
73
+ stateRoot: path.join(installDir, "state")
73
74
  });
74
75
  const reviewer = new RunReviewer({
75
- stateRoot: path.join(projectRoot, "state")
76
+ stateRoot: path.join(installDir, "state")
76
77
  });
77
78
  const deliveryManager = new DeliveryManager({
78
- projectRoot,
79
+ projectRoot: installDir,
79
80
  liveRoot,
80
- stateRoot: path.join(projectRoot, "state")
81
+ stateRoot: path.join(installDir, "state")
81
82
  });
82
83
  const evaluator = new Evaluator({
83
- projectRoot,
84
+ projectRoot: installDir,
84
85
  liveRoot,
85
- stateRoot: path.join(projectRoot, "state")
86
+ stateRoot: path.join(installDir, "state")
86
87
  });
87
88
  const improvementHarness = new ImprovementHarness({
88
- projectRoot,
89
- stateRoot: path.join(projectRoot, "state"),
89
+ projectRoot: installDir,
90
+ stateRoot: path.join(installDir, "state"),
90
91
  executor,
91
92
  evaluator
92
93
  });
93
94
  const dependencyHealth = new DependencyHealth({
94
- projectRoot,
95
+ projectRoot: installDir,
95
96
  liveRoot
96
97
  });
97
98
  const peerReadinessProbe = new PeerReadinessProbe({ liveRoot });
98
- const embeddingService = new EmbeddingService({ projectRoot });
99
+ const embeddingService = new EmbeddingService({ projectRoot: installDir });
99
100
  const embeddingIndex = new EmbeddingIndex({
100
- rootDir: path.join(projectRoot, "state", "memory"),
101
+ rootDir: path.join(installDir, "state", "memory"),
101
102
  embeddingService
102
103
  });
103
104
 
@@ -465,7 +466,7 @@ async function deliverNotificationFile(mode = "shadow", notificationFilePath = n
465
466
  }
466
467
  const resolvedPath = path.isAbsolute(notificationFilePath)
467
468
  ? notificationFilePath
468
- : path.resolve(projectRoot, notificationFilePath);
469
+ : path.resolve(installDir, notificationFilePath);
469
470
  const result = await deliveryManager.deliverPending({
470
471
  mode,
471
472
  limit: 1,
@@ -506,7 +507,7 @@ async function chooseHandoffPeer(notificationFilePath, peerId, deliveryProfile =
506
507
  }
507
508
  const resolvedPath = path.isAbsolute(notificationFilePath)
508
509
  ? notificationFilePath
509
- : path.resolve(projectRoot, notificationFilePath);
510
+ : path.resolve(installDir, notificationFilePath);
510
511
  const result = await deliveryManager.chooseHandoffPeer(resolvedPath, peerId, {
511
512
  deliveryProfile
512
513
  });
@@ -519,7 +520,7 @@ async function chooseTopHandoffPeer(notificationFilePath, deliveryProfile = null
519
520
  }
520
521
  const resolvedPath = path.isAbsolute(notificationFilePath)
521
522
  ? notificationFilePath
522
- : path.resolve(projectRoot, notificationFilePath);
523
+ : path.resolve(installDir, notificationFilePath);
523
524
  const result = await deliveryManager.chooseTopSuggestedPeer(resolvedPath, {
524
525
  deliveryProfile
525
526
  });
@@ -530,7 +531,7 @@ async function serveTransport(port = "4318") {
530
531
  const authToken = process.env.NEMORIS_TRANSPORT_TOKEN || null;
531
532
  const runtime = await scheduler.loadRuntime();
532
533
  const server = new StandaloneTransportServer({
533
- stateRoot: path.join(projectRoot, "state"),
534
+ stateRoot: path.join(installDir, "state"),
534
535
  authToken,
535
536
  retention: runtime.runtime?.retention?.transportInbox || {},
536
537
  requestTimeoutMs: runtime.runtime?.shutdown?.transportShutdownTimeoutMs || 5000
@@ -1383,7 +1384,7 @@ async function serveDaemon(mode = "dry-run", intervalMs = "30000", installDir =
1383
1384
 
1384
1385
  async function pruneState(scope = "all") {
1385
1386
  const runtime = await scheduler.loadRuntime();
1386
- const statePath = path.join(projectRoot, "state");
1387
+ const statePath = path.join(installDir, "state");
1387
1388
  const operations = [];
1388
1389
 
1389
1390
  if (scope === "all" || scope === "runs") {
@@ -1428,7 +1429,7 @@ async function pruneState(scope = "all") {
1428
1429
 
1429
1430
  async function reviewInbox(limit = "10") {
1430
1431
  const server = new StandaloneTransportServer({
1431
- stateRoot: path.join(projectRoot, "state")
1432
+ stateRoot: path.join(installDir, "state")
1432
1433
  });
1433
1434
  const items = await server.listInbox(Number(limit) || 10);
1434
1435
  console.log(JSON.stringify(items, null, 2));
@@ -1606,7 +1607,7 @@ async function reportFallbackReadiness(jobId = "workspace-health") {
1606
1607
 
1607
1608
  async function configValidate() {
1608
1609
  const config = await scheduler.loadRuntime();
1609
- const schemaResult = validateAllConfigs(config, path.join(projectRoot, "config"));
1610
+ const schemaResult = validateAllConfigs(config, path.join(installDir, "config"));
1610
1611
  console.log(
1611
1612
  JSON.stringify(
1612
1613
  {
@@ -2246,7 +2247,7 @@ async function runtimeStatus(jsonFlag = false) {
2246
2247
 
2247
2248
  async function mcpList() {
2248
2249
  const { ConfigLoader } = await import("./config/loader.js");
2249
- const loader = new ConfigLoader({ rootDir: path.join(projectRoot, "config") });
2250
+ const loader = new ConfigLoader({ rootDir: path.join(installDir, "config") });
2250
2251
  const mcp = await loader.loadMcp();
2251
2252
 
2252
2253
  if (!mcp.servers || Object.keys(mcp.servers).length === 0) {
@@ -2283,9 +2284,9 @@ async function listTools() {
2283
2284
  const { ToolRegistry } = await import("./tools/registry.js");
2284
2285
  const { registerMicroToolHandlers } = await import("./tools/micro/index.js");
2285
2286
  const registry = new ToolRegistry();
2286
- registerMicroToolHandlers(registry, { workspaceRoot: projectRoot });
2287
+ registerMicroToolHandlers(registry, { workspaceRoot: installDir });
2287
2288
  try {
2288
- const toolsDir = path.join(projectRoot, "config", "tools");
2289
+ const toolsDir = path.join(installDir, "config", "tools");
2289
2290
  await registry.loadTools(toolsDir);
2290
2291
  } catch { /* no tools dir is fine */ }
2291
2292
  console.log("Configured tools:\n");
@@ -2306,7 +2307,7 @@ async function addTool(toolId) {
2306
2307
  process.exitCode = 1;
2307
2308
  return;
2308
2309
  }
2309
- const toolsDir = path.join(projectRoot, "config", "tools");
2310
+ const toolsDir = path.join(installDir, "config", "tools");
2310
2311
  await mkdirAsync(toolsDir, { recursive: true });
2311
2312
  const lines = [
2312
2313
  `[tool]`,
@@ -2336,7 +2337,7 @@ async function addTool(toolId) {
2336
2337
  async function listSkills() {
2337
2338
  const { SkillsLoader } = await import("./skills/loader.js");
2338
2339
  const loader = new SkillsLoader();
2339
- const skillsDir = path.join(projectRoot, "config", "skills");
2340
+ const skillsDir = path.join(installDir, "config", "skills");
2340
2341
  await loader.loadSkills(skillsDir);
2341
2342
  const all = loader.listAll();
2342
2343
  if (all.length === 0) {
@@ -2353,8 +2354,8 @@ async function listSkills() {
2353
2354
  async function listImprovements() {
2354
2355
  const { readdir: readdirAsync } = await import("node:fs/promises");
2355
2356
  const { ImprovementEngine } = await import("./runtime/improvement-engine.js");
2356
- const engine = new ImprovementEngine({ stateRoot: path.join(projectRoot, "state") });
2357
- const tuningsRoot = path.join(projectRoot, "state", "tunings");
2357
+ const engine = new ImprovementEngine({ stateRoot: path.join(installDir, "state") });
2358
+ const tuningsRoot = path.join(installDir, "state", "tunings");
2358
2359
  try {
2359
2360
  const jobDirs = await readdirAsync(tuningsRoot);
2360
2361
  let found = false;
@@ -2557,7 +2558,7 @@ export async function main(argv = process.argv) {
2557
2558
  const { runMigration } = await import("./runtime/migration.js");
2558
2559
  const dryRun = rest.includes("--dry-run");
2559
2560
  const liveRoot = process.env.NEMORIS_LIVE_ROOT || path.join(process.env.HOME || os.homedir(), ".openclaw");
2560
- const result = await runMigration({ installDir: projectRoot, liveRoot, dryRun });
2561
+ const result = await runMigration({ installDir, liveRoot, dryRun });
2561
2562
  console.log(JSON.stringify(result, null, 2));
2562
2563
  return 0;
2563
2564
  } else if (command === "review-inbox") {
@@ -20,7 +20,7 @@ function defaultSearchPaths() {
20
20
  const installDir = resolveInstallDir();
21
21
  return [
22
22
  path.join(installDir, ".env"),
23
- path.join(os.homedir(), ".nemoris", ".env"),
23
+ path.join(os.homedir(), ".nemoris-data", ".env"),
24
24
  path.join(os.homedir(), ".openclaw", ".env"),
25
25
  ];
26
26
  }
@@ -7,7 +7,7 @@ export function resolveSocketPath({ platform = process.platform, stateDir, homed
7
7
  if (platform === "win32") {
8
8
  return "\\\\.\\pipe\\nemoris-daemon";
9
9
  }
10
- const root = stateDir || path.join(homedir(), ".nemoris", "state");
10
+ const root = stateDir || path.join(homedir(), ".nemoris-data", "state");
11
11
  return path.join(root, "daemon.sock");
12
12
  }
13
13