nemoris 0.1.7 → 0.1.9

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nemoris",
3
- "version": "0.1.7",
3
+ "version": "0.1.9",
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",
@@ -487,24 +487,49 @@ export async function cleanupOrphanedDaemonProcesses({
487
487
 
488
488
  export async function startDirectDaemon({
489
489
  projectRoot,
490
- cliEntryPath = path.join(projectRoot, "src", "cli.js"),
490
+ cliEntryPath,
491
491
  fsImpl = fs,
492
492
  spawnImpl = spawn,
493
493
  processImpl = process,
494
494
  } = {}) {
495
+ // Resolve CLI entry: explicit path > source repo > which nemoris > package __dirname
496
+ if (!cliEntryPath) {
497
+ const srcCliPath = path.join(projectRoot, "src", "cli.js");
498
+ if (fsImpl.existsSync(srcCliPath)) {
499
+ cliEntryPath = srcCliPath;
500
+ } else {
501
+ try {
502
+ const { execFileSync } = await import("node:child_process");
503
+ const whichOut = execFileSync("which", ["nemoris"], { encoding: "utf8", timeout: 3000 }).trim();
504
+ if (whichOut) {
505
+ cliEntryPath = whichOut;
506
+ }
507
+ } catch { /* which not available */ }
508
+ }
509
+ if (!cliEntryPath) {
510
+ // Fallback: resolve from this module's own package
511
+ const __dirname = path.dirname(new URL(import.meta.url).pathname);
512
+ cliEntryPath = path.join(__dirname, "..", "cli.js");
513
+ }
514
+ }
495
515
  const { stateDir, pidFile, stdoutLog, stderrLog } = getDaemonPaths(projectRoot);
496
516
  fsImpl.mkdirSync(stateDir, { recursive: true });
497
517
  const stdoutFd = fsImpl.openSync(stdoutLog, "a");
498
518
  const stderrFd = fsImpl.openSync(stderrLog, "a");
499
519
 
500
520
  try {
521
+ // Only set NEMORIS_INSTALL_DIR for the child when the path is a clean
522
+ // data directory. If it's the source repo (has package.json), the child
523
+ // will detect it via CWD detection instead — avoids the source repo guard.
524
+ const childEnv = { ...processImpl.env };
525
+ const looksLikeSourceRepo = fsImpl.existsSync(path.join(projectRoot, "package.json"));
526
+ if (!looksLikeSourceRepo) {
527
+ childEnv.NEMORIS_INSTALL_DIR = projectRoot;
528
+ }
501
529
  const child = spawnImpl(processImpl.execPath, [cliEntryPath, "serve-daemon", "live"], {
502
530
  cwd: projectRoot,
503
531
  detached: true,
504
- env: {
505
- ...processImpl.env,
506
- NEMORIS_INSTALL_DIR: projectRoot,
507
- },
532
+ env: childEnv,
508
533
  stdio: ["ignore", stdoutFd, stderrFd],
509
534
  });
510
535
  child.unref?.();
package/src/cli-main.js CHANGED
@@ -2745,13 +2745,27 @@ export async function main(argv = process.argv) {
2745
2745
  });
2746
2746
  } else if (command === "doctor") {
2747
2747
  const { runDoctor, formatDoctorReport } = await import("./onboarding/doctor.js");
2748
- const results = await runDoctor(resolveRuntimeInstallDir());
2748
+ const doctorInstallDir = resolveRuntimeInstallDir();
2749
+ const results = await runDoctor(doctorInstallDir);
2749
2750
  const json = rest.includes("--json");
2751
+ const fix = rest.includes("--fix");
2750
2752
  if (json) {
2751
2753
  console.log(JSON.stringify(results, null, 2));
2752
2754
  } else {
2753
2755
  console.log(formatDoctorReport(results));
2754
2756
  }
2757
+ // Auto-repair: clean stale PID file
2758
+ if (fix && results.daemon?.stalePidFile) {
2759
+ const { pidFile } = getDaemonPaths(doctorInstallDir);
2760
+ try {
2761
+ fs.unlinkSync(pidFile);
2762
+ console.log("\n \u2705 Cleaned stale PID file. Run: nemoris start");
2763
+ } catch {
2764
+ console.log("\n \u274c Could not remove stale PID file.");
2765
+ }
2766
+ } else if (!fix && results.daemon?.stalePidFile) {
2767
+ console.log("\n Tip: run nemoris doctor --fix to clean the stale PID file.");
2768
+ }
2755
2769
  return results.exitCode;
2756
2770
  } else if (command === "battle") {
2757
2771
  const { runBattle, parseBattleFlags } = await import("./battle.js");
@@ -41,7 +41,18 @@ function detectProvider(installDir) {
41
41
  const files = fs.readdirSync(providersDir).filter((f) => f.endsWith(".toml"));
42
42
  if (files.length === 0) return { configured: false };
43
43
  const name = files[0].replace(/\.toml$/, "");
44
- return { configured: true, name, count: files.length };
44
+ // Read primary model from the first provider config
45
+ let primaryModel = "";
46
+ try {
47
+ const content = fs.readFileSync(path.join(providersDir, files[0]), "utf8");
48
+ const config = parseToml(content);
49
+ const models = config.models || {};
50
+ const firstModelKey = Object.keys(models)[0];
51
+ if (firstModelKey && models[firstModelKey]?.id) {
52
+ primaryModel = models[firstModelKey].id;
53
+ }
54
+ } catch { /* non-fatal */ }
55
+ return { configured: true, name, count: files.length, primaryModel };
45
56
  } catch {
46
57
  return { configured: false };
47
58
  }
@@ -96,9 +107,12 @@ export function formatSetupChecklist(checklist) {
96
107
  : "Identity";
97
108
  item(checklist.identity, idLabel, "nemoris setup");
98
109
 
99
- const provLabel = checklist.provider.configured
100
- ? `Provider ${checklist.provider.name}${checklist.provider.count > 1 ? ` (+${checklist.provider.count - 1})` : ""}`
101
- : "Provider";
110
+ let provLabel = "Provider";
111
+ if (checklist.provider.configured) {
112
+ const model = checklist.provider.primaryModel || "";
113
+ const modelSuffix = model ? ` \u00b7 ${model}` : "";
114
+ provLabel = `Provider ${checklist.provider.name}${modelSuffix}`;
115
+ }
102
116
  item(checklist.provider, provLabel, "nemoris setup");
103
117
 
104
118
  item(checklist.telegram, "Telegram", "nemoris setup telegram");
@@ -336,16 +336,36 @@ async function runFastPathWizard({ installDir }) {
336
336
  "Setup Complete"
337
337
  );
338
338
 
339
+ // Offer Telegram setup inline
340
+ if (!checklist.telegram.configured) {
341
+ const wantTelegram = await prompter.confirm({
342
+ message: "Set up Telegram now?",
343
+ initialValue: false,
344
+ });
345
+ if (wantTelegram) {
346
+ await runTelegramPhase({
347
+ installDir,
348
+ agentId,
349
+ });
350
+ }
351
+ }
352
+
339
353
  await prompter.outro("Run: nemoris start");
340
354
 
341
355
  try {
342
356
  const pkg = JSON.parse(fs.readFileSync(new URL("../../package.json", import.meta.url), "utf8"));
357
+ const caps = { identity: true, provider: true };
358
+ // Re-check telegram after possible inline setup
359
+ const updatedChecklist = buildSetupChecklist(installDir);
360
+ if (updatedChecklist.telegram.configured || updatedChecklist.telegram.pending) {
361
+ caps.telegram = true;
362
+ }
343
363
  writeWizardMetadata(installDir, {
344
364
  lastRunVersion: pkg.version || "",
345
365
  lastRunCommand: "setup",
346
366
  lastRunMode: "fast",
347
367
  lastRunFlow: "interactive",
348
- capabilities: { identity: true, provider: true },
368
+ capabilities: caps,
349
369
  });
350
370
  } catch {
351
371
  // Non-fatal
@@ -1,5 +1,33 @@
1
+ import fs from "node:fs";
1
2
  import path from "node:path";
3
+ import os from "node:os";
2
4
 
5
+ /**
6
+ * Resolve the .env file path. Priority:
7
+ * 1. NEMORIS_INSTALL_DIR/.env (explicit data dir)
8
+ * 2. CWD/.env (dev mode — running from source repo)
9
+ * 3. ~/.nemoris-data/.env (default install dir)
10
+ * 4. package dir/.env (legacy fallback)
11
+ */
3
12
  export function resolveEnvPath(srcDir) {
13
+ // 1. Explicit install dir
14
+ if (process.env.NEMORIS_INSTALL_DIR) {
15
+ return path.join(process.env.NEMORIS_INSTALL_DIR, ".env");
16
+ }
17
+
18
+ // 2. Dev mode — CWD is source repo
19
+ const cwd = process.cwd();
20
+ if (fs.existsSync(path.join(cwd, "package.json")) &&
21
+ fs.existsSync(path.join(cwd, "src", "cli.js"))) {
22
+ return path.join(cwd, ".env");
23
+ }
24
+
25
+ // 3. Default data dir
26
+ const defaultEnv = path.join(os.homedir(), ".nemoris-data", ".env");
27
+ if (fs.existsSync(defaultEnv)) {
28
+ return defaultEnv;
29
+ }
30
+
31
+ // 4. Legacy: package dir
4
32
  return path.resolve(srcDir, "..", ".env");
5
33
  }