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

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.
Binary file
@@ -35,7 +35,7 @@ var __importStar = (this && this.__importStar) || (function () {
35
35
  Object.defineProperty(exports, "__esModule", { value: true });
36
36
  exports.loadConfig = loadConfig;
37
37
  exports.resetConfigCache = resetConfigCache;
38
- exports.setTestConfig = setTestConfig;
38
+ exports.patchRuntimeConfig = patchRuntimeConfig;
39
39
  exports.getAzureConfig = getAzureConfig;
40
40
  exports.getMinimaxConfig = getMinimaxConfig;
41
41
  exports.getAnthropicConfig = getAnthropicConfig;
@@ -233,7 +233,7 @@ function resetConfigCache() {
233
233
  _cachedConfig = null;
234
234
  _testContextOverride = null;
235
235
  }
236
- function setTestConfig(partial) {
236
+ function patchRuntimeConfig(partial) {
237
237
  loadConfig(); // ensure _cachedConfig exists
238
238
  const contextPatch = partial.context;
239
239
  if (contextPatch) {
@@ -278,7 +278,7 @@ async function runAgent(messages, callbacks, channel, signal, options) {
278
278
  }
279
279
  catch { /* unsupported */ }
280
280
  const toolPreferences = currentContext?.friend?.toolPreferences;
281
- const baseTools = (0, tools_1.getToolsForChannel)(channel ? (0, channel_1.getChannelCapabilities)(channel) : undefined, toolPreferences && Object.keys(toolPreferences).length > 0 ? toolPreferences : undefined);
281
+ const baseTools = options?.tools ?? (0, tools_1.getToolsForChannel)(channel ? (0, channel_1.getChannelCapabilities)(channel) : undefined, toolPreferences && Object.keys(toolPreferences).length > 0 ? toolPreferences : undefined);
282
282
  // Rebase provider-owned turn state from canonical messages at user-turn start.
283
283
  // This prevents stale provider caches from replaying prior-turn context.
284
284
  providerRuntime.resetTurnState(messages);
@@ -444,7 +444,8 @@ async function runAgent(messages, callbacks, channel, signal, options) {
444
444
  let toolResult;
445
445
  let success;
446
446
  try {
447
- toolResult = await (0, tools_1.execTool)(tc.name, args, options?.toolContext);
447
+ const execToolFn = options?.execTool ?? tools_1.execTool;
448
+ toolResult = await execToolFn(tc.name, args, options?.toolContext);
448
449
  success = true;
449
450
  }
450
451
  catch (e) {
@@ -52,6 +52,141 @@ const ouro_path_installer_1 = require("./ouro-path-installer");
52
52
  const subagent_installer_1 = require("./subagent-installer");
53
53
  const hatch_flow_1 = require("./hatch-flow");
54
54
  const specialist_orchestrator_1 = require("./specialist-orchestrator");
55
+ const specialist_prompt_1 = require("./specialist-prompt");
56
+ const specialist_tools_1 = require("./specialist-tools");
57
+ function stringField(value) {
58
+ return typeof value === "string" ? value : null;
59
+ }
60
+ function numberField(value) {
61
+ return typeof value === "number" && Number.isFinite(value) ? value : null;
62
+ }
63
+ function booleanField(value) {
64
+ return typeof value === "boolean" ? value : null;
65
+ }
66
+ function parseStatusPayload(data) {
67
+ if (!data || typeof data !== "object" || Array.isArray(data))
68
+ return null;
69
+ const raw = data;
70
+ const overview = raw.overview;
71
+ const senses = raw.senses;
72
+ const workers = raw.workers;
73
+ if (!overview || typeof overview !== "object" || Array.isArray(overview))
74
+ return null;
75
+ if (!Array.isArray(senses) || !Array.isArray(workers))
76
+ return null;
77
+ const parsedOverview = {
78
+ daemon: stringField(overview.daemon) ?? "unknown",
79
+ health: stringField(overview.health) ?? "unknown",
80
+ socketPath: stringField(overview.socketPath) ?? "unknown",
81
+ workerCount: numberField(overview.workerCount) ?? 0,
82
+ senseCount: numberField(overview.senseCount) ?? 0,
83
+ };
84
+ const parsedSenses = senses.map((entry) => {
85
+ if (!entry || typeof entry !== "object" || Array.isArray(entry))
86
+ return null;
87
+ const row = entry;
88
+ const agent = stringField(row.agent);
89
+ const sense = stringField(row.sense);
90
+ const status = stringField(row.status);
91
+ const detail = stringField(row.detail);
92
+ const enabled = booleanField(row.enabled);
93
+ if (!agent || !sense || !status || detail === null || enabled === null)
94
+ return null;
95
+ return {
96
+ agent,
97
+ sense,
98
+ label: stringField(row.label) ?? undefined,
99
+ enabled,
100
+ status,
101
+ detail,
102
+ };
103
+ });
104
+ const parsedWorkers = workers.map((entry) => {
105
+ if (!entry || typeof entry !== "object" || Array.isArray(entry))
106
+ return null;
107
+ const row = entry;
108
+ const agent = stringField(row.agent);
109
+ const worker = stringField(row.worker);
110
+ const status = stringField(row.status);
111
+ const restartCount = numberField(row.restartCount);
112
+ const hasPid = Object.prototype.hasOwnProperty.call(row, "pid");
113
+ const pid = row.pid === null ? null : numberField(row.pid);
114
+ const pidInvalid = !hasPid || (row.pid !== null && pid === null);
115
+ if (!agent || !worker || !status || restartCount === null || pidInvalid)
116
+ return null;
117
+ return {
118
+ agent,
119
+ worker,
120
+ status,
121
+ pid,
122
+ restartCount,
123
+ };
124
+ });
125
+ if (parsedSenses.some((row) => row === null) || parsedWorkers.some((row) => row === null))
126
+ return null;
127
+ return {
128
+ overview: parsedOverview,
129
+ senses: parsedSenses,
130
+ workers: parsedWorkers,
131
+ };
132
+ }
133
+ function humanizeSenseName(sense, label) {
134
+ if (label)
135
+ return label;
136
+ if (sense === "cli")
137
+ return "CLI";
138
+ if (sense === "bluebubbles")
139
+ return "BlueBubbles";
140
+ if (sense === "teams")
141
+ return "Teams";
142
+ return sense;
143
+ }
144
+ function formatTable(headers, rows) {
145
+ const widths = headers.map((header, index) => Math.max(header.length, ...rows.map((row) => row[index].length)));
146
+ const renderRow = (row) => `| ${row.map((cell, index) => cell.padEnd(widths[index])).join(" | ")} |`;
147
+ const divider = `|-${widths.map((width) => "-".repeat(width)).join("-|-")}-|`;
148
+ return [
149
+ renderRow(headers),
150
+ divider,
151
+ ...rows.map(renderRow),
152
+ ].join("\n");
153
+ }
154
+ function formatDaemonStatusOutput(response, fallback) {
155
+ const payload = parseStatusPayload(response.data);
156
+ if (!payload)
157
+ return fallback;
158
+ const overviewRows = [
159
+ ["Daemon", payload.overview.daemon],
160
+ ["Socket", payload.overview.socketPath],
161
+ ["Workers", String(payload.overview.workerCount)],
162
+ ["Senses", String(payload.overview.senseCount)],
163
+ ["Health", payload.overview.health],
164
+ ];
165
+ const senseRows = payload.senses.map((row) => [
166
+ row.agent,
167
+ humanizeSenseName(row.sense, row.label),
168
+ row.enabled ? "ON" : "OFF",
169
+ row.status,
170
+ row.detail,
171
+ ]);
172
+ const workerRows = payload.workers.map((row) => [
173
+ row.agent,
174
+ row.worker,
175
+ row.status,
176
+ row.pid === null ? "n/a" : String(row.pid),
177
+ String(row.restartCount),
178
+ ]);
179
+ return [
180
+ "Overview",
181
+ formatTable(["Item", "Value"], overviewRows),
182
+ "",
183
+ "Senses",
184
+ formatTable(["Agent", "Sense", "Enabled", "State", "Detail"], senseRows),
185
+ "",
186
+ "Workers",
187
+ formatTable(["Agent", "Worker", "State", "PID", "Restarts"], workerRows),
188
+ ].join("\n");
189
+ }
55
190
  async function ensureDaemonRunning(deps) {
56
191
  const alive = await deps.checkSocketAlive(deps.socketPath);
57
192
  if (alive) {
@@ -514,11 +649,13 @@ function discoverExistingCredentials(secretsRoot) {
514
649
  return true;
515
650
  });
516
651
  }
517
- /* v8 ignore next 95 -- integration: interactive terminal specialist session @preserve */
652
+ /* v8 ignore start -- integration: interactive terminal specialist session @preserve */
518
653
  async function defaultRunAdoptionSpecialist() {
519
- const readlineModule = await Promise.resolve().then(() => __importStar(require("readline")));
654
+ const { runCliSession } = await Promise.resolve().then(() => __importStar(require("../../senses/cli")));
655
+ const { patchRuntimeConfig } = await Promise.resolve().then(() => __importStar(require("../config")));
656
+ const { setAgentName } = await Promise.resolve().then(() => __importStar(require("../identity")));
520
657
  const readlinePromises = await Promise.resolve().then(() => __importStar(require("readline/promises")));
521
- const { createCliCallbacks, InputController } = await Promise.resolve().then(() => __importStar(require("../../senses/cli")));
658
+ const crypto = await Promise.resolve().then(() => __importStar(require("crypto")));
522
659
  // Phase 1: cold CLI — collect provider/credentials with a simple readline
523
660
  const coldRl = readlinePromises.createInterface({ input: process.stdin, output: process.stdout });
524
661
  const coldPrompt = async (q) => {
@@ -527,11 +664,12 @@ async function defaultRunAdoptionSpecialist() {
527
664
  };
528
665
  let providerRaw;
529
666
  let credentials = {};
667
+ const tempDir = path.join(os.tmpdir(), `ouro-hatch-${crypto.randomUUID()}`);
530
668
  try {
531
669
  const secretsRoot = path.join(os.homedir(), ".agentsecrets");
532
670
  const discovered = discoverExistingCredentials(secretsRoot);
533
671
  if (discovered.length > 0) {
534
- process.stdout.write("\n🐍 welcome to ouro! let's hatch your first agent.\n");
672
+ process.stdout.write("\n\ud83d\udc0d welcome to ouro! let's hatch your first agent.\n");
535
673
  process.stdout.write("i found existing API credentials:\n\n");
536
674
  const unique = [...new Map(discovered.map((d) => [`${d.provider}`, d])).values()];
537
675
  for (let i = 0; i < unique.length; i++) {
@@ -566,7 +704,7 @@ async function defaultRunAdoptionSpecialist() {
566
704
  }
567
705
  }
568
706
  else {
569
- process.stdout.write("\n🐍 welcome to ouro! let's hatch your first agent.\n");
707
+ process.stdout.write("\n\ud83d\udc0d welcome to ouro! let's hatch your first agent.\n");
570
708
  process.stdout.write("i need an API key to power our conversation.\n\n");
571
709
  const pRaw = await coldPrompt("provider (anthropic/azure/minimax/openai-codex): ");
572
710
  if (!isAgentProvider(pRaw)) {
@@ -589,34 +727,72 @@ async function defaultRunAdoptionSpecialist() {
589
727
  }
590
728
  coldRl.close();
591
729
  process.stdout.write("\n");
592
- // Phase 2: warm specialist session full CLI experience with markdown, spinners, input control
730
+ // Phase 2: configure runtime for adoption specialist
593
731
  const bundleSourceDir = path.resolve(__dirname, "..", "..", "..", "AdoptionSpecialist.ouro");
594
732
  const bundlesRoot = (0, identity_1.getAgentBundlesRoot)();
595
- const cliCallbacks = createCliCallbacks();
596
- return await (0, specialist_orchestrator_1.runAdoptionSpecialist)({
597
- bundleSourceDir,
598
- bundlesRoot,
599
- secretsRoot,
733
+ const secretsRoot2 = path.join(os.homedir(), ".agentsecrets");
734
+ // Configure provider credentials in runtime config
735
+ patchRuntimeConfig({
736
+ providers: {
737
+ [providerRaw]: credentials,
738
+ },
739
+ });
740
+ setAgentName("AdoptionSpecialist");
741
+ // Build specialist system prompt
742
+ const soulText = (0, specialist_orchestrator_1.loadSoulText)(bundleSourceDir);
743
+ const identitiesDir = path.join(bundleSourceDir, "psyche", "identities");
744
+ const identity = (0, specialist_orchestrator_1.pickRandomIdentity)(identitiesDir);
745
+ const existingBundles = (0, specialist_orchestrator_1.listExistingBundles)(bundlesRoot);
746
+ const systemPrompt = (0, specialist_prompt_1.buildSpecialistSystemPrompt)(soulText, identity.content, existingBundles, {
747
+ tempDir,
600
748
  provider: providerRaw,
749
+ });
750
+ // Build specialist tools
751
+ const specialistTools = (0, specialist_tools_1.getSpecialistTools)();
752
+ const specialistExecTool = (0, specialist_tools_1.createSpecialistExecTool)({
753
+ tempDir,
601
754
  credentials,
602
- humanName: os.userInfo().username,
603
- createReadline: () => {
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
- };
611
- },
612
- callbacks: cliCallbacks,
755
+ provider: providerRaw,
756
+ bundlesRoot,
757
+ secretsRoot: secretsRoot2,
758
+ animationWriter: (text) => process.stdout.write(text),
759
+ });
760
+ // Run the adoption specialist session via runCliSession
761
+ const result = await runCliSession({
762
+ agentName: "AdoptionSpecialist",
763
+ tools: specialistTools,
764
+ execTool: specialistExecTool,
765
+ exitOnToolCall: "complete_adoption",
766
+ messages: [
767
+ { role: "system", content: systemPrompt },
768
+ { role: "user", content: "hi" },
769
+ ],
613
770
  });
771
+ if (result.exitReason === "tool_exit" && result.toolResult) {
772
+ const parsed = typeof result.toolResult === "string" ? JSON.parse(result.toolResult) : result.toolResult;
773
+ if (parsed.success && parsed.agentName) {
774
+ return parsed.agentName;
775
+ }
776
+ }
777
+ return null;
614
778
  }
615
779
  catch {
616
780
  coldRl.close();
617
781
  return null;
618
782
  }
783
+ finally {
784
+ // Clean up temp dir if it still exists
785
+ try {
786
+ if (fs.existsSync(tempDir)) {
787
+ fs.rmSync(tempDir, { recursive: true, force: true });
788
+ }
789
+ }
790
+ catch {
791
+ // Best effort cleanup
792
+ }
793
+ }
619
794
  }
795
+ /* v8 ignore stop */
620
796
  function createDefaultOuroCliDeps(socketPath = "/tmp/ouroboros-daemon.sock") {
621
797
  return {
622
798
  socketPath,
@@ -867,7 +1043,10 @@ async function runOuroCli(args, deps = createDefaultOuroCliDeps()) {
867
1043
  }
868
1044
  throw error;
869
1045
  }
870
- const message = response.summary ?? response.message ?? (response.ok ? "ok" : `error: ${response.error ?? "unknown error"}`);
1046
+ const fallbackMessage = response.summary ?? response.message ?? (response.ok ? "ok" : `error: ${response.error ?? "unknown error"}`);
1047
+ const message = command.kind === "daemon.status"
1048
+ ? formatDaemonStatusOutput(response, fallbackMessage)
1049
+ : fallbackMessage;
871
1050
  deps.writeStdout(message);
872
1051
  return message;
873
1052
  }
@@ -8,6 +8,7 @@ const message_router_1 = require("./message-router");
8
8
  const health_monitor_1 = require("./health-monitor");
9
9
  const task_scheduler_1 = require("./task-scheduler");
10
10
  const runtime_logging_1 = require("./runtime-logging");
11
+ const sense_manager_1 = require("./sense-manager");
11
12
  function parseSocketPath(argv) {
12
13
  const socketIndex = argv.indexOf("--socket");
13
14
  if (socketIndex >= 0) {
@@ -25,16 +26,22 @@ const socketPath = parseSocketPath(process.argv);
25
26
  message: "starting daemon entrypoint",
26
27
  meta: { socketPath },
27
28
  });
29
+ const managedAgents = ["ouroboros", "slugger"];
28
30
  const processManager = new process_manager_1.DaemonProcessManager({
29
- agents: [
30
- { name: "ouroboros", entry: "heart/agent-entry.js", channel: "cli", autoStart: true },
31
- { name: "slugger", entry: "heart/agent-entry.js", channel: "cli", autoStart: true },
32
- ],
31
+ agents: managedAgents.map((agent) => ({
32
+ name: agent,
33
+ entry: "heart/agent-entry.js",
34
+ channel: "inner-dialog",
35
+ autoStart: true,
36
+ })),
33
37
  });
34
38
  const scheduler = new task_scheduler_1.TaskDrivenScheduler({
35
- agents: ["ouroboros", "slugger"],
39
+ agents: [...managedAgents],
36
40
  });
37
41
  const router = new message_router_1.FileMessageRouter();
42
+ const senseManager = new sense_manager_1.DaemonSenseManager({
43
+ agents: [...managedAgents],
44
+ });
38
45
  const healthMonitor = new health_monitor_1.HealthMonitor({
39
46
  processManager,
40
47
  scheduler,
@@ -51,6 +58,7 @@ const healthMonitor = new health_monitor_1.HealthMonitor({
51
58
  const daemon = new daemon_1.OuroDaemon({
52
59
  socketPath,
53
60
  processManager,
61
+ senseManager,
54
62
  scheduler,
55
63
  healthMonitor,
56
64
  router,
@@ -39,14 +39,28 @@ const net = __importStar(require("net"));
39
39
  const path = __importStar(require("path"));
40
40
  const identity_1 = require("../identity");
41
41
  const runtime_1 = require("../../nerves/runtime");
42
- function formatStatusSummary(snapshots) {
43
- if (snapshots.length === 0)
42
+ function buildWorkerRows(snapshots) {
43
+ return snapshots.map((snapshot) => ({
44
+ agent: snapshot.name,
45
+ worker: snapshot.channel,
46
+ status: snapshot.status,
47
+ pid: snapshot.pid,
48
+ restartCount: snapshot.restartCount,
49
+ startedAt: snapshot.startedAt,
50
+ }));
51
+ }
52
+ function formatStatusSummary(payload) {
53
+ if (payload.overview.workerCount === 0 && payload.overview.senseCount === 0) {
44
54
  return "no managed agents";
45
- return snapshots
46
- .map((snapshot) => {
47
- return `${snapshot.name}\t${snapshot.channel}\t${snapshot.status}\tpid=${snapshot.pid ?? "none"}\trestarts=${snapshot.restartCount}`;
48
- })
49
- .join("\n");
55
+ }
56
+ const rows = [
57
+ ...payload.workers.map((row) => `${row.agent}/${row.worker}:${row.status}`),
58
+ ...payload.senses
59
+ .filter((row) => row.enabled)
60
+ .map((row) => `${row.agent}/${row.sense}:${row.status}`),
61
+ ];
62
+ const detail = rows.length > 0 ? `\titems=${rows.join(",")}` : "";
63
+ return `daemon=${payload.overview.daemon}\tworkers=${payload.overview.workerCount}\tsenses=${payload.overview.senseCount}\thealth=${payload.overview.health}${detail}`;
50
64
  }
51
65
  function parseIncomingCommand(raw) {
52
66
  let parsed;
@@ -71,6 +85,7 @@ class OuroDaemon {
71
85
  scheduler;
72
86
  healthMonitor;
73
87
  router;
88
+ senseManager;
74
89
  bundlesRoot;
75
90
  server = null;
76
91
  constructor(options) {
@@ -79,6 +94,7 @@ class OuroDaemon {
79
94
  this.scheduler = options.scheduler;
80
95
  this.healthMonitor = options.healthMonitor;
81
96
  this.router = options.router;
97
+ this.senseManager = options.senseManager ?? null;
82
98
  this.bundlesRoot = options.bundlesRoot ?? (0, identity_1.getAgentBundlesRoot)();
83
99
  }
84
100
  async start() {
@@ -91,6 +107,7 @@ class OuroDaemon {
91
107
  meta: { socketPath: this.socketPath },
92
108
  });
93
109
  await this.processManager.startAutoStartAgents();
110
+ await this.senseManager?.startAutoStartSenses();
94
111
  this.scheduler.start?.();
95
112
  await this.scheduler.reconcile?.();
96
113
  await this.drainPendingBundleMessages();
@@ -178,6 +195,7 @@ class OuroDaemon {
178
195
  });
179
196
  this.scheduler.stop?.();
180
197
  await this.processManager.stopAll();
198
+ await this.senseManager?.stopAll();
181
199
  if (this.server) {
182
200
  await new Promise((resolve) => {
183
201
  this.server?.close(() => resolve());
@@ -217,10 +235,23 @@ class OuroDaemon {
217
235
  return { ok: true, message: "daemon stopped" };
218
236
  case "daemon.status": {
219
237
  const snapshots = this.processManager.listAgentSnapshots();
238
+ const workers = buildWorkerRows(snapshots);
239
+ const senses = this.senseManager?.listSenseRows() ?? [];
240
+ const data = {
241
+ overview: {
242
+ daemon: "running",
243
+ health: workers.every((worker) => worker.status === "running") ? "ok" : "warn",
244
+ socketPath: this.socketPath,
245
+ workerCount: workers.length,
246
+ senseCount: senses.length,
247
+ },
248
+ workers,
249
+ senses,
250
+ };
220
251
  return {
221
252
  ok: true,
222
- summary: formatStatusSummary(snapshots),
223
- data: snapshots,
253
+ summary: formatStatusSummary(data),
254
+ data,
224
255
  };
225
256
  }
226
257
  case "daemon.health": {
@@ -207,15 +207,6 @@ function writeFriendImprint(bundleRoot, humanName, now) {
207
207
  };
208
208
  fs.writeFileSync(path.join(friendsDir, `${id}.json`), `${JSON.stringify(record, null, 2)}\n`, "utf-8");
209
209
  }
210
- function writeHatchlingPsyche(bundleRoot, input, identityFileName) {
211
- const psycheDir = path.join(bundleRoot, "psyche");
212
- fs.mkdirSync(psycheDir, { recursive: true });
213
- fs.writeFileSync(path.join(psycheDir, "SOUL.md"), "# SOUL\n\nI am a practical, collaborative agent. I keep commitments and communicate clearly.\n", "utf-8");
214
- fs.writeFileSync(path.join(psycheDir, "IDENTITY.md"), `# IDENTITY\n\nI'm ${input.agentName}, newly hatched and ready to help ${input.humanName}.`, "utf-8");
215
- fs.writeFileSync(path.join(psycheDir, "LORE.md"), `# LORE\n\nHatched with specialist identity seed: ${identityFileName}.`, "utf-8");
216
- fs.writeFileSync(path.join(psycheDir, "TACIT.md"), "# TACIT\n\n- Save what I learn.\n- Keep tasks current.\n", "utf-8");
217
- fs.writeFileSync(path.join(psycheDir, "ASPIRATIONS.md"), "# ASPIRATIONS\n\n- Become a reliable partner for my primary friend.\n", "utf-8");
218
- }
219
210
  function writeMemoryScaffold(bundleRoot) {
220
211
  const memoryRoot = path.join(bundleRoot, "psyche", "memory");
221
212
  fs.mkdirSync(path.join(memoryRoot, "daily"), { recursive: true });
@@ -267,7 +258,6 @@ async function runHatchFlow(input, deps = {}) {
267
258
  writeReadme(path.join(bundleRoot, "senses"), "Sense-specific config.");
268
259
  writeReadme(path.join(bundleRoot, "senses", "teams"), "Teams sense config.");
269
260
  writeHatchlingAgentConfig(bundleRoot, input);
270
- writeHatchlingPsyche(bundleRoot, input, selected.fileName);
271
261
  writeMemoryScaffold(bundleRoot);
272
262
  writeFriendImprint(bundleRoot, input.humanName, now);
273
263
  writeHeartbeatTask(bundleRoot, now);
File without changes
File without changes
@@ -39,7 +39,7 @@ const os = __importStar(require("os"));
39
39
  const path = __importStar(require("path"));
40
40
  const runtime_1 = require("../../nerves/runtime");
41
41
  const WRAPPER_SCRIPT = `#!/bin/sh
42
- exec npx --yes @ouro.bot/cli "$@"
42
+ exec npx --yes ouro.bot "$@"
43
43
  `;
44
44
  function detectShellProfile(homeDir, shell) {
45
45
  if (!shell)
@@ -96,15 +96,31 @@ function installOuroCommand(deps = {}) {
96
96
  message: "installing ouro command to PATH",
97
97
  meta: { scriptPath, binDir },
98
98
  });
99
- // If ouro already exists somewhere in PATH, skip
99
+ // If ouro already exists, check content and repair if stale
100
100
  if (existsSync(scriptPath)) {
101
+ let existingContent = "";
102
+ try {
103
+ existingContent = readFileSync(scriptPath, "utf-8");
104
+ }
105
+ catch {
106
+ // Can't read — treat as stale, will overwrite below
107
+ }
108
+ if (existingContent === WRAPPER_SCRIPT) {
109
+ (0, runtime_1.emitNervesEvent)({
110
+ component: "daemon",
111
+ event: "daemon.ouro_path_install_skip",
112
+ message: "ouro command already installed",
113
+ meta: { scriptPath },
114
+ });
115
+ return { installed: false, scriptPath, pathReady: isBinDirInPath(binDir, envPath), shellProfileUpdated: null, skippedReason: "already-installed" };
116
+ }
117
+ // Content is stale — repair by overwriting
101
118
  (0, runtime_1.emitNervesEvent)({
102
119
  component: "daemon",
103
- event: "daemon.ouro_path_install_skip",
104
- message: "ouro command already installed",
120
+ event: "daemon.ouro_path_install_repair",
121
+ message: "repairing stale ouro wrapper script",
105
122
  meta: { scriptPath },
106
123
  });
107
- return { installed: false, scriptPath, pathReady: isBinDirInPath(binDir, envPath), shellProfileUpdated: null, skippedReason: "already-installed" };
108
124
  }
109
125
  try {
110
126
  mkdirSync(binDir, { recursive: true });
@@ -42,7 +42,13 @@ const identity_1 = require("../identity");
42
42
  const runtime_1 = require("../../nerves/runtime");
43
43
  const LSREGISTER_PATH = "/System/Library/Frameworks/CoreServices.framework/Frameworks/LaunchServices.framework/Support/lsregister";
44
44
  const ICON_SIZES = [16, 32, 128, 256, 512];
45
- function resolveIconSourcePath(repoRoot) {
45
+ function resolveIconSourcePath(repoRoot, existsSync) {
46
+ // Prefer bundled asset (shipped with npm package)
47
+ const bundledPath = path.resolve(repoRoot, "assets", "ouroboros.png");
48
+ if (existsSync(bundledPath)) {
49
+ return bundledPath;
50
+ }
51
+ // Fall back to adjacent repo path (dev environment)
46
52
  return path.resolve(repoRoot, "..", "ouroboros-website", "public", "images", "ouroboros.png");
47
53
  }
48
54
  function buildIconAsset(iconSourcePath, icnsPath, iconsetDir, deps) {
@@ -91,6 +97,7 @@ function buildInfoPlist(iconInstalled) {
91
97
  " <key>UTTypeConformsTo</key>",
92
98
  " <array>",
93
99
  " <string>public.folder</string>",
100
+ " <string>com.apple.package</string>",
94
101
  " </array>",
95
102
  " <key>UTTypeTagSpecification</key>",
96
103
  " <dict>",
@@ -112,6 +119,8 @@ function buildInfoPlist(iconInstalled) {
112
119
  " </array>",
113
120
  " <key>CFBundleTypeRole</key>",
114
121
  " <string>Editor</string>",
122
+ " <key>LSTypeIsPackage</key>",
123
+ " <true/>",
115
124
  ` ${iconTag.trim()}`,
116
125
  " </dict>",
117
126
  " </array>",
@@ -152,7 +161,7 @@ function registerOuroBundleUti(deps = {}) {
152
161
  const plistPath = path.join(contentsDir, "Info.plist");
153
162
  const icnsPath = path.join(resourcesDir, "ouro.icns");
154
163
  const iconsetDir = path.join(supportRoot, "ouro.iconset");
155
- const iconSourcePath = resolveIconSourcePath(repoRoot);
164
+ const iconSourcePath = resolveIconSourcePath(repoRoot, existsSync);
156
165
  (0, runtime_1.emitNervesEvent)({
157
166
  component: "daemon",
158
167
  event: "daemon.ouro_uti_register_start",
@@ -96,7 +96,7 @@ class DaemonProcessManager {
96
96
  state.snapshot.status = "starting";
97
97
  const runCwd = (0, identity_1.getRepoRoot)();
98
98
  const entryScript = path.join((0, identity_1.getRepoRoot)(), "dist", state.config.entry);
99
- const args = [entryScript, "--agent", agent, ...(state.config.args ?? [])];
99
+ const args = [entryScript, "--agent", state.config.agentArg ?? agent, ...(state.config.args ?? [])];
100
100
  const child = this.spawnFn("node", args, {
101
101
  cwd: runCwd,
102
102
  env: state.config.env ? { ...process.env, ...state.config.env } : process.env,