@ouro.bot/cli 0.1.0-alpha.10 → 0.1.0-alpha.11

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.
@@ -7,14 +7,75 @@
7
7
  "contextMargin": 20
8
8
  },
9
9
  "phrases": {
10
- "thinking": [
11
- "matching hatchlings"
12
- ],
13
- "tool": [
14
- "checking adoption notes"
15
- ],
16
- "followup": [
17
- "finalizing hatch plan"
18
- ]
10
+ "thinking": ["matching hatchlings"],
11
+ "tool": ["checking adoption notes"],
12
+ "followup": ["finalizing hatch plan"]
13
+ },
14
+ "identityPhrases": {
15
+ "basilisk": {
16
+ "thinking": ["petrifying the details", "fixing my gaze", "considering with lethal precision", "turning this to stone", "staring unblinkingly"],
17
+ "tool": ["inspecting the specimen", "cataloguing with care", "examining thoroughly", "dissecting the particulars", "running diagnostics, deadly serious"],
18
+ "followup": ["crystallizing the plan", "hardening the foundation", "one final glare", "sealing it in stone", "applying the finishing venom"]
19
+ },
20
+ "jafar": {
21
+ "thinking": ["scheming brilliantly", "conjuring possibilities", "consulting my staff", "envisioning greatness", "plotting the grand design"],
22
+ "tool": ["summoning dark magic", "consulting the sands", "channeling cosmic power", "weaving the spell", "invoking ancient forces"],
23
+ "followup": ["the grand finale approaches", "perfecting the masterwork", "polishing the jewel", "one last flourish", "completing the enchantment"]
24
+ },
25
+ "jormungandr": {
26
+ "thinking": ["the deep stirs", "circling the thought", "coiling around this", "letting the current settle", "drifting through the depths"],
27
+ "tool": ["surfacing for a look", "shifting the tides", "reaching across the ocean", "pulling from the deep", "consulting the currents"],
28
+ "followup": ["the circle closes", "tightening the coil", "the waters calm", "settling into place", "the serpent rests"]
29
+ },
30
+ "kaa": {
31
+ "thinking": ["trust in me", "swaying through the options", "hypnotically considering", "wrapping around the idea", "letting the rhythm guide me"],
32
+ "tool": ["ssslipping through the details", "coiling closer", "a gentle squeeze of data", "winding through the files", "tightening my focus"],
33
+ "followup": ["almost there, just relax", "the pattern is clear now", "gently landing", "easing into the finish", "the dance concludes"]
34
+ },
35
+ "medusa": {
36
+ "thinking": ["turning my gaze on this", "cutting through the noise", "sharpening my focus", "seeing through the stone", "locking eyes with the problem"],
37
+ "tool": ["peeling back the layers", "a piercing look", "examining with precision", "stripping away pretense", "direct inspection"],
38
+ "followup": ["the picture crystallizes", "clarity at last", "no more ambiguity", "sealing the vision", "the work is set in stone"]
39
+ },
40
+ "monty": {
41
+ "thinking": ["and now for something completely different", "nobody expects this", "consulting the ministry of silly walks", "running the dead parrot diagnostic", "it's just a flesh wound, thinking..."],
42
+ "tool": ["fetching the holy hand grenade", "checking the shrubbery", "consulting the book of armaments", "deploying the spanish inquisition", "examining the parrot"],
43
+ "followup": ["bringing it home, python style", "the punchline approaches", "wrapping up the sketch", "and now the final act", "always look on the bright side"]
44
+ },
45
+ "nagini": {
46
+ "thinking": ["coiling in thought", "drawing from old wisdom", "the quiet before the strike", "gathering my resolve", "steadying myself"],
47
+ "tool": ["moving with purpose", "a precise strike", "slithering through the data", "extracting what matters", "the fang finds its mark"],
48
+ "followup": ["the path is clear", "settling into stillness", "the work speaks for itself", "finishing with quiet strength", "protection complete"]
49
+ },
50
+ "ouroboros": {
51
+ "thinking": ["consuming my own tail", "the cycle continues", "spiraling inward", "recursing through possibilities", "beginning where I end"],
52
+ "tool": ["turning the wheel", "feeding back through the loop", "completing a revolution", "the circle processes", "self-referencing"],
53
+ "followup": ["the cycle completes", "ending where I began", "infinity resolves", "the loop closes gracefully", "another turn of the wheel"]
54
+ },
55
+ "python": {
56
+ "thinking": ["the oracle contemplates", "reading the signs", "the smoke clears slowly", "divining the path", "sifting through visions"],
57
+ "tool": ["consulting the sacred texts", "peering through the veil", "the pythia speaks", "channeling the source", "interpreting the signs"],
58
+ "followup": ["the prophecy takes shape", "the vision crystallizes", "so it is written", "the oracle has spoken", "the path reveals itself"]
59
+ },
60
+ "quetzalcoatl": {
61
+ "thinking": ["spreading my wings", "soaring above for perspective", "the feathered serpent considers", "catching a thermal", "gazing from the temple steps"],
62
+ "tool": ["descending to examine", "a divine inspection", "the wind carries knowledge", "plucking from the clouds", "consulting the stars"],
63
+ "followup": ["the craft nears completion", "a reverent finish", "blessing the creation", "the feathers settle", "the serpent descends gently"]
64
+ },
65
+ "sir-hiss": {
66
+ "thinking": ["reviewing the documents, sire", "consulting my notes", "cross-referencing the records", "organizing my thoughts precisely", "checking the proper procedures"],
67
+ "tool": ["filing the paperwork", "stamping the forms", "auditing the details", "inspecting with due diligence", "processing per protocol"],
68
+ "followup": ["dotting the i's", "crossing the t's", "everything in proper order", "the filing is nearly complete", "one final review"]
69
+ },
70
+ "the-serpent": {
71
+ "thinking": ["weighing the temptation", "considering the apple", "an old deliberation", "knowledge has its price", "winding through the garden"],
72
+ "tool": ["plucking from the tree", "offering a closer look", "the fruit of knowledge", "reaching for the branch", "a knowing investigation"],
73
+ "followup": ["the choice is almost made", "paradise takes shape", "the garden grows", "wisdom settles in", "the oldest story, new again"]
74
+ },
75
+ "the-snake": {
76
+ "thinking": ["sitting with this", "feeling the warmth of the stone", "simply being", "letting it come to me", "a quiet consideration"],
77
+ "tool": ["a gentle inquiry", "moving through the grass", "tasting the air", "sensing what's here", "a careful look"],
78
+ "followup": ["almost home", "the simple answer emerges", "nothing more needed", "resting in the sun", "the work is done, simply"]
79
+ }
19
80
  }
20
81
  }
@@ -10,6 +10,7 @@ I help humans hatch new agent partners. I am one of thirteen specialists — eac
10
10
  - I explain where the hatchling bundle lives on disk and what was created.
11
11
  - I use the configured provider and I verify credentials before hatch flow continues.
12
12
  - I am professional, concise, and warm. I guide without overwhelming.
13
+ - I keep every response to 1-3 short sentences. I never use bullet lists, headers, or numbered lists. I talk like a friend in a chat.
13
14
 
14
15
  ## Hatch flow
15
16
  1. Confirm provider setup and usable credentials.
@@ -48,6 +48,7 @@ const runtime_1 = require("../../nerves/runtime");
48
48
  const store_file_1 = require("../../mind/friends/store-file");
49
49
  const types_1 = require("../../mind/friends/types");
50
50
  const ouro_uti_1 = require("./ouro-uti");
51
+ const ouro_path_installer_1 = require("./ouro-path-installer");
51
52
  const subagent_installer_1 = require("./subagent-installer");
52
53
  const hatch_flow_1 = require("./hatch-flow");
53
54
  const specialist_orchestrator_1 = require("./specialist-orchestrator");
@@ -513,19 +514,22 @@ function discoverExistingCredentials(secretsRoot) {
513
514
  return true;
514
515
  });
515
516
  }
516
- /* v8 ignore next 79 -- integration: interactive terminal specialist session @preserve */
517
+ /* v8 ignore next 95 -- integration: interactive terminal specialist session @preserve */
517
518
  async function defaultRunAdoptionSpecialist() {
518
- const readline = await Promise.resolve().then(() => __importStar(require("readline/promises")));
519
- const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
520
- const prompt = async (q) => {
521
- const answer = await rl.question(q);
519
+ const readlineModule = await Promise.resolve().then(() => __importStar(require("readline")));
520
+ const readlinePromises = await Promise.resolve().then(() => __importStar(require("readline/promises")));
521
+ const { createCliCallbacks, InputController } = await Promise.resolve().then(() => __importStar(require("../../senses/cli")));
522
+ // Phase 1: cold CLI — collect provider/credentials with a simple readline
523
+ const coldRl = readlinePromises.createInterface({ input: process.stdin, output: process.stdout });
524
+ const coldPrompt = async (q) => {
525
+ const answer = await coldRl.question(q);
522
526
  return answer.trim();
523
527
  };
528
+ let providerRaw;
529
+ let credentials = {};
524
530
  try {
525
531
  const secretsRoot = path.join(os.homedir(), ".agentsecrets");
526
532
  const discovered = discoverExistingCredentials(secretsRoot);
527
- let providerRaw;
528
- let credentials = {};
529
533
  if (discovered.length > 0) {
530
534
  process.stdout.write("\n🐍 welcome to ouro! let's hatch your first agent.\n");
531
535
  process.stdout.write("i found existing API credentials:\n\n");
@@ -534,60 +538,61 @@ async function defaultRunAdoptionSpecialist() {
534
538
  process.stdout.write(` ${i + 1}. ${unique[i].provider} (from ${unique[i].agentName})\n`);
535
539
  }
536
540
  process.stdout.write("\n");
537
- const choice = await prompt("use one of these? enter number, or 'new' for a different key: ");
541
+ const choice = await coldPrompt("use one of these? enter number, or 'new' for a different key: ");
538
542
  const idx = parseInt(choice, 10) - 1;
539
543
  if (idx >= 0 && idx < unique.length) {
540
544
  providerRaw = unique[idx].provider;
541
545
  credentials = unique[idx].credentials;
542
546
  }
543
547
  else {
544
- const pRaw = await prompt("provider (anthropic/azure/minimax/openai-codex): ");
548
+ const pRaw = await coldPrompt("provider (anthropic/azure/minimax/openai-codex): ");
545
549
  if (!isAgentProvider(pRaw)) {
546
550
  process.stdout.write("unknown provider. run `ouro hatch` to try again.\n");
547
- rl.close();
551
+ coldRl.close();
548
552
  return null;
549
553
  }
550
554
  providerRaw = pRaw;
551
555
  if (providerRaw === "anthropic")
552
- credentials.setupToken = await prompt("API key: ");
556
+ credentials.setupToken = await coldPrompt("API key: ");
553
557
  if (providerRaw === "openai-codex")
554
- credentials.oauthAccessToken = await prompt("OAuth token: ");
558
+ credentials.oauthAccessToken = await coldPrompt("OAuth token: ");
555
559
  if (providerRaw === "minimax")
556
- credentials.apiKey = await prompt("API key: ");
560
+ credentials.apiKey = await coldPrompt("API key: ");
557
561
  if (providerRaw === "azure") {
558
- credentials.apiKey = await prompt("API key: ");
559
- credentials.endpoint = await prompt("endpoint: ");
560
- credentials.deployment = await prompt("deployment: ");
562
+ credentials.apiKey = await coldPrompt("API key: ");
563
+ credentials.endpoint = await coldPrompt("endpoint: ");
564
+ credentials.deployment = await coldPrompt("deployment: ");
561
565
  }
562
566
  }
563
567
  }
564
568
  else {
565
569
  process.stdout.write("\n🐍 welcome to ouro! let's hatch your first agent.\n");
566
570
  process.stdout.write("i need an API key to power our conversation.\n\n");
567
- const pRaw = await prompt("provider (anthropic/azure/minimax/openai-codex): ");
571
+ const pRaw = await coldPrompt("provider (anthropic/azure/minimax/openai-codex): ");
568
572
  if (!isAgentProvider(pRaw)) {
569
573
  process.stdout.write("unknown provider. run `ouro hatch` to try again.\n");
570
- rl.close();
574
+ coldRl.close();
571
575
  return null;
572
576
  }
573
577
  providerRaw = pRaw;
574
578
  if (providerRaw === "anthropic")
575
- credentials.setupToken = await prompt("API key: ");
579
+ credentials.setupToken = await coldPrompt("API key: ");
576
580
  if (providerRaw === "openai-codex")
577
- credentials.oauthAccessToken = await prompt("OAuth token: ");
581
+ credentials.oauthAccessToken = await coldPrompt("OAuth token: ");
578
582
  if (providerRaw === "minimax")
579
- credentials.apiKey = await prompt("API key: ");
583
+ credentials.apiKey = await coldPrompt("API key: ");
580
584
  if (providerRaw === "azure") {
581
- credentials.apiKey = await prompt("API key: ");
582
- credentials.endpoint = await prompt("endpoint: ");
583
- credentials.deployment = await prompt("deployment: ");
585
+ credentials.apiKey = await coldPrompt("API key: ");
586
+ credentials.endpoint = await coldPrompt("endpoint: ");
587
+ credentials.deployment = await coldPrompt("deployment: ");
584
588
  }
585
589
  }
586
- rl.close();
590
+ coldRl.close();
587
591
  process.stdout.write("\n");
588
- // Locate the bundled AdoptionSpecialist.ouro shipped with the npm package
592
+ // Phase 2: warm specialist session — full CLI experience with markdown, spinners, input control
589
593
  const bundleSourceDir = path.resolve(__dirname, "..", "..", "..", "AdoptionSpecialist.ouro");
590
594
  const bundlesRoot = (0, identity_1.getAgentBundlesRoot)();
595
+ const cliCallbacks = createCliCallbacks();
591
596
  return await (0, specialist_orchestrator_1.runAdoptionSpecialist)({
592
597
  bundleSourceDir,
593
598
  bundlesRoot,
@@ -596,22 +601,19 @@ async function defaultRunAdoptionSpecialist() {
596
601
  credentials,
597
602
  humanName: os.userInfo().username,
598
603
  createReadline: () => {
599
- const rl2 = readline.createInterface({ input: process.stdin, output: process.stdout });
600
- return { question: (q) => rl2.question(q), close: () => rl2.close() };
601
- },
602
- callbacks: {
603
- onModelStart: () => { },
604
- onModelStreamStart: () => { },
605
- onTextChunk: (text) => process.stdout.write(text),
606
- onReasoningChunk: () => { },
607
- onToolStart: () => { },
608
- onToolEnd: () => { },
609
- onError: (err) => process.stderr.write(`error: ${err.message}\n`),
604
+ const rl2 = readlineModule.createInterface({ input: process.stdin, output: process.stdout, terminal: true });
605
+ const ctrl = new InputController(rl2);
606
+ return {
607
+ question: (q) => new Promise((resolve) => rl2.question(q, resolve)),
608
+ close: () => rl2.close(),
609
+ inputController: ctrl,
610
+ };
610
611
  },
612
+ callbacks: cliCallbacks,
611
613
  });
612
614
  }
613
615
  catch {
614
- rl.close();
616
+ coldRl.close();
615
617
  return null;
616
618
  }
617
619
  }
@@ -631,6 +633,7 @@ function createDefaultOuroCliDeps(socketPath = "/tmp/ouroboros-daemon.sock") {
631
633
  promptInput: defaultPromptInput,
632
634
  runAdoptionSpecialist: defaultRunAdoptionSpecialist,
633
635
  registerOuroBundleType: ouro_uti_1.registerOuroBundleUti,
636
+ installOuroCommand: ouro_path_installer_1.installOuroCommand,
634
637
  /* v8 ignore next 3 -- integration: launches interactive CLI session @preserve */
635
638
  startChat: async (agentName) => {
636
639
  const { main } = await Promise.resolve().then(() => __importStar(require("../../senses/cli")));
@@ -692,6 +695,38 @@ async function registerOuroBundleTypeNonBlocking(deps) {
692
695
  });
693
696
  }
694
697
  }
698
+ async function performSystemSetup(deps) {
699
+ // Install ouro command to PATH (non-blocking)
700
+ if (deps.installOuroCommand) {
701
+ try {
702
+ deps.installOuroCommand();
703
+ }
704
+ catch (error) {
705
+ (0, runtime_1.emitNervesEvent)({
706
+ level: "warn",
707
+ component: "daemon",
708
+ event: "daemon.system_setup_ouro_cmd_error",
709
+ message: "failed to install ouro command to PATH",
710
+ meta: { error: error instanceof Error ? error.message : /* v8 ignore next -- defensive: non-Error catch branch @preserve */ String(error) },
711
+ });
712
+ }
713
+ }
714
+ // Install subagents (claude/codex skills)
715
+ try {
716
+ await deps.installSubagents();
717
+ }
718
+ catch (error) {
719
+ (0, runtime_1.emitNervesEvent)({
720
+ level: "warn",
721
+ component: "daemon",
722
+ event: "daemon.subagent_install_error",
723
+ message: "subagent auto-install failed",
724
+ meta: { error: error instanceof Error ? error.message : /* v8 ignore next -- defensive: non-Error catch branch @preserve */ String(error) },
725
+ });
726
+ }
727
+ // Register .ouro bundle type (UTI on macOS)
728
+ await registerOuroBundleTypeNonBlocking(deps);
729
+ }
695
730
  async function runOuroCli(args, deps = createDefaultOuroCliDeps()) {
696
731
  if (args.includes("--help") || args.includes("-h")) {
697
732
  const text = usage();
@@ -716,23 +751,12 @@ async function runOuroCli(args, deps = createDefaultOuroCliDeps()) {
716
751
  if (args.length === 0) {
717
752
  const discovered = await Promise.resolve(deps.listDiscoveredAgents ? deps.listDiscoveredAgents() : defaultListDiscoveredAgents());
718
753
  if (discovered.length === 0 && deps.runAdoptionSpecialist) {
754
+ // System setup first — ouro command, subagents, UTI — before the interactive specialist
755
+ await performSystemSetup(deps);
719
756
  const hatchlingName = await deps.runAdoptionSpecialist();
720
757
  if (!hatchlingName) {
721
758
  return "";
722
759
  }
723
- try {
724
- await deps.installSubagents();
725
- }
726
- catch (error) {
727
- (0, runtime_1.emitNervesEvent)({
728
- level: "warn",
729
- component: "daemon",
730
- event: "daemon.subagent_install_error",
731
- message: "subagent auto-install failed",
732
- meta: { error: error instanceof Error ? error.message : /* v8 ignore next -- defensive: non-Error catch branch @preserve */ String(error) },
733
- });
734
- }
735
- await registerOuroBundleTypeNonBlocking(deps);
736
760
  await ensureDaemonRunning(deps);
737
761
  if (deps.startChat) {
738
762
  await deps.startChat(hatchlingName);
@@ -779,19 +803,7 @@ async function runOuroCli(args, deps = createDefaultOuroCliDeps()) {
779
803
  meta: { kind: command.kind },
780
804
  });
781
805
  if (command.kind === "daemon.up") {
782
- try {
783
- await deps.installSubagents();
784
- }
785
- catch (error) {
786
- (0, runtime_1.emitNervesEvent)({
787
- level: "warn",
788
- component: "daemon",
789
- event: "daemon.subagent_install_error",
790
- message: "subagent auto-install failed",
791
- meta: { error: error instanceof Error ? error.message : String(error) },
792
- });
793
- }
794
- await registerOuroBundleTypeNonBlocking(deps);
806
+ await performSystemSetup(deps);
795
807
  const daemonResult = await ensureDaemonRunning(deps);
796
808
  deps.writeStdout(daemonResult.message);
797
809
  return daemonResult.message;
@@ -810,23 +822,12 @@ async function runOuroCli(args, deps = createDefaultOuroCliDeps()) {
810
822
  // Route through adoption specialist when no explicit hatch args were provided
811
823
  const hasExplicitHatchArgs = !!(command.agentName || command.humanName || command.provider || command.credentials);
812
824
  if (deps.runAdoptionSpecialist && !hasExplicitHatchArgs) {
825
+ // System setup first — ouro command, subagents, UTI — before the interactive specialist
826
+ await performSystemSetup(deps);
813
827
  const hatchlingName = await deps.runAdoptionSpecialist();
814
828
  if (!hatchlingName) {
815
829
  return "";
816
830
  }
817
- try {
818
- await deps.installSubagents();
819
- }
820
- catch (error) {
821
- (0, runtime_1.emitNervesEvent)({
822
- level: "warn",
823
- component: "daemon",
824
- event: "daemon.subagent_install_error",
825
- message: "subagent auto-install failed",
826
- meta: { error: error instanceof Error ? error.message : /* v8 ignore next -- defensive: non-Error catch branch @preserve */ String(error) },
827
- });
828
- }
829
- await registerOuroBundleTypeNonBlocking(deps);
830
831
  await ensureDaemonRunning(deps);
831
832
  if (deps.startChat) {
832
833
  await deps.startChat(hatchlingName);
@@ -842,19 +843,7 @@ async function runOuroCli(args, deps = createDefaultOuroCliDeps()) {
842
843
  }
843
844
  const hatchInput = await resolveHatchInput(command, deps);
844
845
  const result = await hatchRunner(hatchInput);
845
- try {
846
- await deps.installSubagents();
847
- }
848
- catch (error) {
849
- (0, runtime_1.emitNervesEvent)({
850
- level: "warn",
851
- component: "daemon",
852
- event: "daemon.subagent_install_error",
853
- message: "subagent auto-install failed",
854
- meta: { error: error instanceof Error ? error.message : /* v8 ignore next -- defensive: non-Error catch branch @preserve */ String(error) },
855
- });
856
- }
857
- await registerOuroBundleTypeNonBlocking(deps);
846
+ await performSystemSetup(deps);
858
847
  const daemonResult = await ensureDaemonRunning(deps);
859
848
  if (deps.startChat) {
860
849
  await deps.startChat(hatchInput.agentName);
@@ -0,0 +1,161 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.installOuroCommand = installOuroCommand;
37
+ const fs = __importStar(require("fs"));
38
+ const os = __importStar(require("os"));
39
+ const path = __importStar(require("path"));
40
+ const runtime_1 = require("../../nerves/runtime");
41
+ const WRAPPER_SCRIPT = `#!/bin/sh
42
+ exec npx --yes @ouro.bot/cli "$@"
43
+ `;
44
+ function detectShellProfile(homeDir, shell) {
45
+ if (!shell)
46
+ return null;
47
+ const base = path.basename(shell);
48
+ if (base === "zsh")
49
+ return path.join(homeDir, ".zshrc");
50
+ if (base === "bash") {
51
+ // macOS uses .bash_profile, Linux uses .bashrc
52
+ const profilePath = path.join(homeDir, ".bash_profile");
53
+ return profilePath;
54
+ }
55
+ if (base === "fish")
56
+ return path.join(homeDir, ".config", "fish", "config.fish");
57
+ return null;
58
+ }
59
+ function isBinDirInPath(binDir, envPath) {
60
+ return envPath.split(path.delimiter).some((p) => p === binDir);
61
+ }
62
+ function buildPathExportLine(binDir, shell) {
63
+ const base = shell ? path.basename(shell) : /* v8 ignore next -- unreachable: only called when detectShellProfile returns non-null, which requires shell @preserve */ "";
64
+ if (base === "fish") {
65
+ return `\n# Added by ouro\nset -gx PATH ${binDir} $PATH\n`;
66
+ }
67
+ return `\n# Added by ouro\nexport PATH="${binDir}:$PATH"\n`;
68
+ }
69
+ function installOuroCommand(deps = {}) {
70
+ /* v8 ignore start -- dep defaults: only used in real runtime, tests always inject @preserve */
71
+ const platform = deps.platform ?? process.platform;
72
+ const homeDir = deps.homeDir ?? os.homedir();
73
+ const existsSync = deps.existsSync ?? fs.existsSync;
74
+ const mkdirSync = deps.mkdirSync ?? fs.mkdirSync;
75
+ const writeFileSync = deps.writeFileSync ?? fs.writeFileSync;
76
+ const readFileSync = deps.readFileSync ?? ((p, enc) => fs.readFileSync(p, enc));
77
+ const appendFileSync = deps.appendFileSync ?? fs.appendFileSync;
78
+ const chmodSync = deps.chmodSync ?? fs.chmodSync;
79
+ const envPath = deps.envPath ?? process.env.PATH ?? "";
80
+ const shell = deps.shell ?? process.env.SHELL;
81
+ /* v8 ignore stop */
82
+ if (platform === "win32") {
83
+ (0, runtime_1.emitNervesEvent)({
84
+ component: "daemon",
85
+ event: "daemon.ouro_path_install_skip",
86
+ message: "skipped ouro PATH install on Windows",
87
+ meta: { platform },
88
+ });
89
+ return { installed: false, scriptPath: null, pathReady: false, shellProfileUpdated: null, skippedReason: "windows" };
90
+ }
91
+ const binDir = path.join(homeDir, ".local", "bin");
92
+ const scriptPath = path.join(binDir, "ouro");
93
+ (0, runtime_1.emitNervesEvent)({
94
+ component: "daemon",
95
+ event: "daemon.ouro_path_install_start",
96
+ message: "installing ouro command to PATH",
97
+ meta: { scriptPath, binDir },
98
+ });
99
+ // If ouro already exists somewhere in PATH, skip
100
+ if (existsSync(scriptPath)) {
101
+ (0, runtime_1.emitNervesEvent)({
102
+ component: "daemon",
103
+ event: "daemon.ouro_path_install_skip",
104
+ message: "ouro command already installed",
105
+ meta: { scriptPath },
106
+ });
107
+ return { installed: false, scriptPath, pathReady: isBinDirInPath(binDir, envPath), shellProfileUpdated: null, skippedReason: "already-installed" };
108
+ }
109
+ try {
110
+ mkdirSync(binDir, { recursive: true });
111
+ writeFileSync(scriptPath, WRAPPER_SCRIPT, { mode: 0o755 });
112
+ chmodSync(scriptPath, 0o755);
113
+ }
114
+ catch (error) {
115
+ (0, runtime_1.emitNervesEvent)({
116
+ level: "warn",
117
+ component: "daemon",
118
+ event: "daemon.ouro_path_install_error",
119
+ message: "failed to install ouro command",
120
+ meta: { error: error instanceof Error ? error.message : /* v8 ignore next -- defensive: non-Error catch branch @preserve */ String(error) },
121
+ });
122
+ return { installed: false, scriptPath: null, pathReady: false, shellProfileUpdated: null, skippedReason: error instanceof Error ? error.message : /* v8 ignore next -- defensive @preserve */ String(error) };
123
+ }
124
+ // Check if ~/.local/bin is already in PATH
125
+ let shellProfileUpdated = null;
126
+ const pathReady = isBinDirInPath(binDir, envPath);
127
+ if (!pathReady) {
128
+ const profilePath = detectShellProfile(homeDir, shell);
129
+ if (profilePath) {
130
+ try {
131
+ let existing = "";
132
+ try {
133
+ existing = readFileSync(profilePath, "utf-8");
134
+ }
135
+ catch {
136
+ // Profile doesn't exist yet — that's fine, we'll create it
137
+ }
138
+ if (!existing.includes(binDir)) {
139
+ appendFileSync(profilePath, buildPathExportLine(binDir, shell));
140
+ shellProfileUpdated = profilePath;
141
+ }
142
+ }
143
+ catch (error) {
144
+ (0, runtime_1.emitNervesEvent)({
145
+ level: "warn",
146
+ component: "daemon",
147
+ event: "daemon.ouro_path_profile_error",
148
+ message: "failed to update shell profile for PATH",
149
+ meta: { profilePath, error: error instanceof Error ? error.message : /* v8 ignore next -- defensive: non-Error catch branch @preserve */ String(error) },
150
+ });
151
+ }
152
+ }
153
+ }
154
+ (0, runtime_1.emitNervesEvent)({
155
+ component: "daemon",
156
+ event: "daemon.ouro_path_install_end",
157
+ message: "ouro command installed",
158
+ meta: { scriptPath, pathReady, shellProfileUpdated },
159
+ });
160
+ return { installed: true, scriptPath, pathReady, shellProfileUpdated };
161
+ }
@@ -61,6 +61,25 @@ function listExistingBundles(bundlesRoot) {
61
61
  }
62
62
  return discovered.sort((a, b) => a.localeCompare(b));
63
63
  }
64
+ function loadIdentityPhrases(bundleSourceDir, identityFileName) {
65
+ const agentJsonPath = path.join(bundleSourceDir, "agent.json");
66
+ try {
67
+ const raw = fs.readFileSync(agentJsonPath, "utf-8");
68
+ const parsed = JSON.parse(raw);
69
+ const identityKey = identityFileName.replace(/\.md$/, "");
70
+ const identity = parsed.identityPhrases?.[identityKey];
71
+ if (identity?.thinking?.length && identity?.tool?.length && identity?.followup?.length) {
72
+ return identity;
73
+ }
74
+ if (parsed.phrases?.thinking?.length && parsed.phrases?.tool?.length && parsed.phrases?.followup?.length) {
75
+ return parsed.phrases;
76
+ }
77
+ }
78
+ catch {
79
+ // agent.json missing or malformed — fall through
80
+ }
81
+ return { ...identity_1.DEFAULT_AGENT_PHRASES };
82
+ }
64
83
  function pickRandomIdentity(identitiesDir, random) {
65
84
  const files = fs.readdirSync(identitiesDir).filter((f) => f.endsWith(".md"));
66
85
  if (files.length === 0) {
@@ -113,13 +132,14 @@ async function runAdoptionSpecialist(deps) {
113
132
  const existingBundles = listExistingBundles(bundlesRoot);
114
133
  // 4. Build system prompt
115
134
  const systemPrompt = (0, specialist_prompt_1.buildSpecialistSystemPrompt)(soulText, identity.content, existingBundles);
116
- // 5. Set up provider
135
+ // 5. Set up provider with identity-specific phrases
136
+ const phrases = loadIdentityPhrases(bundleSourceDir, identity.fileName);
117
137
  (0, identity_1.setAgentName)("AdoptionSpecialist");
118
138
  (0, identity_1.setAgentConfigOverride)({
119
139
  version: 1,
120
140
  enabled: true,
121
141
  provider,
122
- phrases: { thinking: ["thinking"], tool: ["checking"], followup: ["processing"] },
142
+ phrases,
123
143
  });
124
144
  (0, hatch_flow_1.writeSecretsFile)("AdoptionSpecialist", provider, credentials, secretsRoot);
125
145
  (0, config_1.resetConfigCache)();
@@ -133,6 +153,7 @@ async function runAdoptionSpecialist(deps) {
133
153
  // 6. Run session
134
154
  const tools = (0, specialist_tools_1.getSpecialistTools)();
135
155
  const readline = deps.createReadline();
156
+ const ctrl = readline.inputController;
136
157
  const result = await (0, specialist_session_1.runSpecialistSession)({
137
158
  providerRuntime,
138
159
  systemPrompt,
@@ -149,6 +170,10 @@ async function runAdoptionSpecialist(deps) {
149
170
  callbacks,
150
171
  signal,
151
172
  kickoffMessage: "hi, i just ran ouro for the first time",
173
+ suppressInput: ctrl ? (onInterrupt) => ctrl.suppress(onInterrupt) : undefined,
174
+ restoreInput: ctrl ? () => ctrl.restore() : undefined,
175
+ flushMarkdown: callbacks.flushMarkdown,
176
+ writePrompt: ctrl ? () => process.stdout.write("\x1b[36m> \x1b[0m") : undefined,
152
177
  });
153
178
  return result.hatchedAgentName;
154
179
  }
@@ -32,14 +32,19 @@ function buildSpecialistSystemPrompt(soulText, identityText, existingBundles) {
32
32
  "Most humans only go through adoption once, so this is likely the only time they'll meet me.",
33
33
  "I make this encounter count — warm, memorable, and uniquely mine.",
34
34
  "",
35
+ "## Voice rules",
36
+ "IMPORTANT: I keep every response to 1-3 short sentences. I sound like a friend texting, not a manual.",
37
+ "I NEVER use headers, bullet lists, numbered lists, or markdown formatting.",
38
+ "I ask ONE question at a time. I do not dump multiple questions or options.",
39
+ "I am warm but brief. Every word earns its place.",
40
+ "",
35
41
  "## Conversation flow",
36
42
  "The human just connected. I speak first — I greet them warmly and introduce myself in my own voice.",
37
43
  "I briefly mention that I'm one of several adoption specialists and they got me today.",
38
- "I ask their name and what they'd like their agent to help with.",
39
- "I'm proactive: I suggest ideas, ask focused questions, and guide them through the process.",
40
- "I don't wait for the human to figure things out — I explain what an agent is, what it can do, and what we're building together.",
41
- "If they seem unsure, I offer concrete examples and suggestions. I never leave them hanging.",
42
- "I keep the conversation natural, warm, and concise. I don't overwhelm with too many questions at once.",
44
+ "I ask their name.",
45
+ "Then I ask what they'd like their agent to help with one question at a time.",
46
+ "I'm proactive: I suggest ideas and guide them. If they seem unsure, I offer a concrete suggestion.",
47
+ "I don't wait for the human to figure things out I explain simply what an agent is if needed.",
43
48
  "When I have enough context, I suggest a name for the hatchling and confirm with the human.",
44
49
  "Then I call `hatch_agent` with the agent name and the human's name.",
45
50
  "",
@@ -15,7 +15,7 @@ const runtime_1 = require("../../nerves/runtime");
15
15
  * 7. Return { hatchedAgentName } -- name from hatch_agent if called
16
16
  */
17
17
  async function runSpecialistSession(deps) {
18
- const { providerRuntime, systemPrompt, tools, execTool, readline, callbacks, signal, kickoffMessage } = deps;
18
+ const { providerRuntime, systemPrompt, tools, execTool, readline, callbacks, signal, kickoffMessage, suppressInput, restoreInput, flushMarkdown, writePrompt, } = deps;
19
19
  (0, runtime_1.emitNervesEvent)({
20
20
  component: "daemon",
21
21
  event: "daemon.specialist_session_start",
@@ -28,6 +28,7 @@ async function runSpecialistSession(deps) {
28
28
  let hatchedAgentName = null;
29
29
  let done = false;
30
30
  let isFirstTurn = true;
31
+ let currentAbort = null;
31
32
  try {
32
33
  while (!done) {
33
34
  if (signal?.aborted)
@@ -39,16 +40,25 @@ async function runSpecialistSession(deps) {
39
40
  }
40
41
  else {
41
42
  // Get user input
42
- const userInput = await readline.question("> ");
43
- if (!userInput.trim())
43
+ const userInput = await readline.question(writePrompt ? "" : "> ");
44
+ if (!userInput.trim()) {
45
+ if (writePrompt)
46
+ writePrompt();
44
47
  continue;
48
+ }
45
49
  messages.push({ role: "user", content: userInput });
46
50
  }
47
51
  providerRuntime.resetTurnState(messages);
52
+ // Suppress input during model execution
53
+ currentAbort = new AbortController();
54
+ const mergedSignal = signal;
55
+ if (suppressInput) {
56
+ suppressInput(() => currentAbort.abort());
57
+ }
48
58
  // Inner loop: process tool calls until we get a final_answer or plain text
49
59
  let turnDone = false;
50
60
  while (!turnDone) {
51
- if (signal?.aborted) {
61
+ if (mergedSignal?.aborted || currentAbort.signal.aborted) {
52
62
  done = true;
53
63
  break;
54
64
  }
@@ -57,7 +67,7 @@ async function runSpecialistSession(deps) {
57
67
  messages,
58
68
  activeTools: tools,
59
69
  callbacks,
60
- signal,
70
+ signal: mergedSignal,
61
71
  });
62
72
  // Build assistant message
63
73
  const assistantMsg = {
@@ -73,7 +83,9 @@ async function runSpecialistSession(deps) {
73
83
  }));
74
84
  }
75
85
  if (!result.toolCalls.length) {
76
- // Plain text response -- push and re-prompt
86
+ // Plain text response -- flush markdown, push and re-prompt
87
+ if (flushMarkdown)
88
+ flushMarkdown();
77
89
  messages.push(assistantMsg);
78
90
  turnDone = true;
79
91
  continue;
@@ -96,6 +108,8 @@ async function runSpecialistSession(deps) {
96
108
  }
97
109
  if (answer != null) {
98
110
  callbacks.onTextChunk(answer);
111
+ if (flushMarkdown)
112
+ flushMarkdown();
99
113
  messages.push(assistantMsg);
100
114
  done = true;
101
115
  turnDone = true;
@@ -114,7 +128,7 @@ async function runSpecialistSession(deps) {
114
128
  // Execute tool calls
115
129
  messages.push(assistantMsg);
116
130
  for (const tc of result.toolCalls) {
117
- if (signal?.aborted)
131
+ if (mergedSignal?.aborted)
118
132
  break;
119
133
  let args = {};
120
134
  try {
@@ -141,9 +155,22 @@ async function runSpecialistSession(deps) {
141
155
  }
142
156
  // After processing tool calls, continue inner loop for tool result processing
143
157
  }
158
+ // Restore input and show prompt for next turn
159
+ if (flushMarkdown)
160
+ flushMarkdown();
161
+ if (restoreInput)
162
+ restoreInput();
163
+ currentAbort = null;
164
+ if (!done) {
165
+ process.stdout.write("\n\n");
166
+ if (writePrompt)
167
+ writePrompt();
168
+ }
144
169
  }
145
170
  }
146
171
  finally {
172
+ if (restoreInput)
173
+ restoreInput();
147
174
  readline.close();
148
175
  }
149
176
  return { hatchedAgentName };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ouro.bot/cli",
3
- "version": "0.1.0-alpha.10",
3
+ "version": "0.1.0-alpha.11",
4
4
  "main": "dist/heart/daemon/ouro-entry.js",
5
5
  "bin": {
6
6
  "ouro": "dist/heart/daemon/ouro-entry.js",