nemoris 0.1.10 → 0.1.12

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.10",
3
+ "version": "0.1.12",
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",
@@ -498,16 +498,25 @@ export async function startDirectDaemon({
498
498
  if (fsImpl.existsSync(srcCliPath)) {
499
499
  cliEntryPath = srcCliPath;
500
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 */ }
501
+ // Resolve cli.js from this module's own package — the `which nemoris`
502
+ // path is a shell wrapper, not a Node.js file, so we can't pass it
503
+ // to `node` directly.
504
+ const __dirname = path.dirname(new URL(import.meta.url).pathname);
505
+ const packageCliPath = path.join(__dirname, "..", "cli.js");
506
+ if (fsImpl.existsSync(packageCliPath)) {
507
+ cliEntryPath = packageCliPath;
508
+ } else {
509
+ // Last resort: try which nemoris and hope it's a node script
510
+ try {
511
+ const { execFileSync } = await import("node:child_process");
512
+ const whichOut = execFileSync("which", ["nemoris"], { encoding: "utf8", timeout: 3000 }).trim();
513
+ if (whichOut) {
514
+ cliEntryPath = whichOut;
515
+ }
516
+ } catch { /* which not available */ }
517
+ }
508
518
  }
509
519
  if (!cliEntryPath) {
510
- // Fallback: resolve from this module's own package
511
520
  const __dirname = path.dirname(new URL(import.meta.url).pathname);
512
521
  cliEntryPath = path.join(__dirname, "..", "cli.js");
513
522
  }
@@ -246,7 +246,10 @@ export async function runTelegramPhase({ installDir, agentId, nonInteractive = f
246
246
  let token = null;
247
247
  let botUsername = null;
248
248
 
249
- console.log(`\n ${dimImpl("Need a bot token? Open Telegram → search @BotFather send /newbot → follow the steps.")}`);
249
+ console.log(`\n ${yellowImpl("!")} Create a new bot in BotFather for Nemoris.`);
250
+ console.log(` ${dimImpl("If you're migrating from OpenClaw, don't reuse your existing bot tokens —")}`);
251
+ console.log(` ${dimImpl("each agent needs its own. Only one process can poll a bot at a time.")}`);
252
+ console.log(`\n ${dimImpl("Open Telegram → search @BotFather → send /newbot → follow the steps.")}`);
250
253
  console.log(` ${dimImpl("After creating your bot, open it in Telegram and tap Start before coming back here.")}`);
251
254
  console.log(` ${dimImpl("Your input will be hidden as you type — just paste and press Enter.")}\n`);
252
255
 
@@ -329,6 +332,21 @@ export async function runTelegramPhase({ installDir, agentId, nonInteractive = f
329
332
  chatIdPending: !chatId,
330
333
  });
331
334
 
335
+ // Persist bot_username to runtime.toml for finish screen / status
336
+ if (botUsername) {
337
+ const runtimePath = path.join(installDir, "config", "runtime.toml");
338
+ try {
339
+ let runtime = fs.readFileSync(runtimePath, "utf8");
340
+ if (runtime.includes("[telegram]") && !runtime.includes("bot_username")) {
341
+ runtime = runtime.replace(
342
+ /(\[telegram\][^\[]*)/s,
343
+ (section) => section.trimEnd() + `\nbot_username = "${botUsername}"\n`
344
+ );
345
+ fs.writeFileSync(runtimePath, runtime);
346
+ }
347
+ } catch { /* non-fatal */ }
348
+ }
349
+
332
350
  // Store chat_id in state for later phases (e.g. hatch)
333
351
  const result = { configured: true, verified: false, botUsername, botToken: token, operatorChatId: chatId || "" };
334
352
 
@@ -56,6 +56,21 @@ function createLegacyPromptAdapter(prompter) {
56
56
  };
57
57
  }
58
58
 
59
+ function readTelegramBotName(installDir) {
60
+ try {
61
+ const runtime = readRuntimeConfig(installDir);
62
+ const tokenEnv = runtime?.telegram?.bot_token_env;
63
+ if (!tokenEnv) return null;
64
+ const token = process.env[tokenEnv];
65
+ if (!token) return null;
66
+ // Bot username is cached in runtime.toml if available
67
+ if (runtime?.telegram?.bot_username) return runtime.telegram.bot_username;
68
+ return null;
69
+ } catch {
70
+ return null;
71
+ }
72
+ }
73
+
59
74
  function readRuntimeConfig(installDir) {
60
75
  const runtimePath = path.join(installDir, "config", "runtime.toml");
61
76
  try {
@@ -418,43 +433,62 @@ async function runFastPathWizard({ installDir }) {
418
433
  });
419
434
  }
420
435
 
421
- // Post-setup guidance
422
- const { buildSetupChecklist, formatSetupChecklist } = await import("./setup-checklist.js");
423
- const checklist = buildSetupChecklist(installDir);
424
- const allConfigured = Object.values(checklist).every((c) => c.configured);
425
-
426
- await prompter.note(
427
- [
428
- `Agent "${agentName}" is ready.`,
429
- "",
430
- "Next steps:",
431
- " nemoris start Start the daemon",
432
- " nemoris chat Open interactive chat",
433
- ...(allConfigured ? [] : [
434
- "",
435
- "Optional:",
436
- ...(!checklist.telegram.configured ? [" nemoris setup telegram Connect Telegram"] : []),
437
- ...(!checklist.ollama.configured ? [" nemoris setup ollama Add local models"] : []),
438
- ]),
439
- ].join("\n"),
440
- "Setup Complete"
441
- );
442
-
443
436
  // Offer Telegram setup inline
437
+ const { buildSetupChecklist } = await import("./setup-checklist.js");
438
+ let checklist = buildSetupChecklist(installDir);
439
+
440
+ let telegramBotUsername = null;
444
441
  if (!checklist.telegram.configured) {
445
442
  const wantTelegram = await prompter.confirm({
446
443
  message: "Set up Telegram now?",
447
444
  initialValue: false,
448
445
  });
449
446
  if (wantTelegram) {
450
- await runTelegramPhase({
447
+ const tgResult = await runTelegramPhase({
451
448
  installDir,
452
449
  agentId,
453
450
  });
451
+ telegramBotUsername = tgResult?.botUsername || null;
452
+ checklist = buildSetupChecklist(installDir);
454
453
  }
455
454
  }
456
455
 
457
- await prompter.outro("Run: nemoris start");
456
+ // Auto-start daemon
457
+ let daemonPid = null;
458
+ try {
459
+ const { startDaemonCommand } = await import("../cli/runtime-control.js");
460
+ const result = await startDaemonCommand({ projectRoot: installDir });
461
+ const pidMatch = String(result.message || "").match(/pid\s+(\d+)/);
462
+ daemonPid = pidMatch ? pidMatch[1] : "started";
463
+ } catch {
464
+ // Non-fatal — user can start manually
465
+ }
466
+
467
+ // Strong finish screen
468
+ const telegramConfigured = checklist.telegram.configured || checklist.telegram.pending;
469
+ const botName = telegramBotUsername || readTelegramBotName(installDir);
470
+ const finishLines = [
471
+ `Your agent is live`,
472
+ "",
473
+ ...(daemonPid
474
+ ? [` \u2713 Daemon running (pid ${daemonPid})`]
475
+ : [" \u26a0 Daemon not started \u2014 run: nemoris start"]),
476
+ "",
477
+ ...(telegramConfigured && botName
478
+ ? [
479
+ ` \u2192 Open Telegram and message @${botName}`,
480
+ " Your agent will introduce itself",
481
+ ]
482
+ : [
483
+ " \u2192 Run: nemoris chat Start talking now",
484
+ ]),
485
+ "",
486
+ " Or: nemoris chat Open terminal chat",
487
+ " nemoris status Check agent health",
488
+ ];
489
+
490
+ await prompter.note(finishLines.join("\n"), "");
491
+ await prompter.outro("");
458
492
 
459
493
  try {
460
494
  const pkg = JSON.parse(fs.readFileSync(new URL("../../package.json", import.meta.url), "utf8"));