@ouro.bot/cli 0.1.0-alpha.2 → 0.1.0-alpha.21
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/AdoptionSpecialist.ouro/agent.json +70 -9
- package/AdoptionSpecialist.ouro/psyche/SOUL.md +5 -2
- package/AdoptionSpecialist.ouro/psyche/identities/monty.md +2 -2
- package/assets/ouroboros.png +0 -0
- package/dist/heart/config.js +66 -4
- package/dist/heart/core.js +75 -2
- package/dist/heart/daemon/daemon-cli.js +523 -33
- package/dist/heart/daemon/daemon-entry.js +13 -5
- package/dist/heart/daemon/daemon-runtime-sync.js +90 -0
- package/dist/heart/daemon/daemon.js +42 -9
- package/dist/heart/daemon/hatch-animation.js +35 -0
- package/dist/heart/daemon/hatch-flow.js +2 -11
- package/dist/heart/daemon/hatch-specialist.js +6 -1
- package/dist/heart/daemon/ouro-bot-wrapper.js +4 -3
- package/dist/heart/daemon/ouro-path-installer.js +178 -0
- package/dist/heart/daemon/ouro-uti.js +11 -2
- package/dist/heart/daemon/process-manager.js +1 -1
- package/dist/heart/daemon/runtime-logging.js +9 -5
- package/dist/heart/daemon/runtime-metadata.js +118 -0
- package/dist/heart/daemon/sense-manager.js +266 -0
- package/dist/heart/daemon/specialist-orchestrator.js +129 -0
- package/dist/heart/daemon/specialist-prompt.js +98 -0
- package/dist/heart/daemon/specialist-tools.js +237 -0
- package/dist/heart/daemon/subagent-installer.js +10 -1
- package/dist/heart/daemon/wrapper-publish-guard.js +48 -0
- package/dist/heart/identity.js +77 -1
- package/dist/heart/providers/anthropic.js +19 -2
- package/dist/heart/sense-truth.js +61 -0
- package/dist/heart/streaming.js +99 -21
- package/dist/mind/bundle-manifest.js +58 -0
- package/dist/mind/friends/channel.js +8 -0
- package/dist/mind/friends/types.js +1 -1
- package/dist/mind/prompt.js +77 -3
- package/dist/nerves/cli-logging.js +15 -2
- package/dist/repertoire/ado-client.js +4 -2
- package/dist/repertoire/coding/feedback.js +134 -0
- package/dist/repertoire/coding/index.js +4 -1
- package/dist/repertoire/coding/manager.js +61 -2
- package/dist/repertoire/coding/spawner.js +3 -3
- package/dist/repertoire/coding/tools.js +41 -2
- package/dist/repertoire/data/ado-endpoints.json +188 -0
- package/dist/repertoire/tools-base.js +69 -5
- package/dist/repertoire/tools-teams.js +57 -4
- package/dist/repertoire/tools.js +44 -11
- package/dist/senses/bluebubbles-client.js +433 -0
- package/dist/senses/bluebubbles-entry.js +11 -0
- package/dist/senses/bluebubbles-media.js +244 -0
- package/dist/senses/bluebubbles-model.js +253 -0
- package/dist/senses/bluebubbles-mutation-log.js +76 -0
- package/dist/senses/bluebubbles.js +421 -0
- package/dist/senses/cli.js +293 -133
- package/dist/senses/debug-activity.js +107 -0
- package/dist/senses/teams.js +173 -54
- package/package.json +11 -4
- package/subagents/work-doer.md +26 -24
- package/subagents/work-merger.md +24 -30
- package/subagents/work-planner.md +34 -25
- package/dist/inner-worker-entry.js +0 -4
|
@@ -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
|
-
|
|
31
|
-
|
|
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: [
|
|
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,
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.ensureCurrentDaemonRuntime = ensureCurrentDaemonRuntime;
|
|
4
|
+
const runtime_1 = require("../../nerves/runtime");
|
|
5
|
+
function isKnownVersion(version) {
|
|
6
|
+
return version !== "unknown" && version.trim().length > 0;
|
|
7
|
+
}
|
|
8
|
+
function formatErrorReason(error) {
|
|
9
|
+
return error instanceof Error ? error.message : String(error);
|
|
10
|
+
}
|
|
11
|
+
async function ensureCurrentDaemonRuntime(deps) {
|
|
12
|
+
try {
|
|
13
|
+
const runningVersion = await deps.fetchRunningVersion();
|
|
14
|
+
let result;
|
|
15
|
+
if (isKnownVersion(deps.localVersion) &&
|
|
16
|
+
isKnownVersion(runningVersion) &&
|
|
17
|
+
runningVersion !== deps.localVersion) {
|
|
18
|
+
try {
|
|
19
|
+
await deps.stopDaemon();
|
|
20
|
+
}
|
|
21
|
+
catch (error) {
|
|
22
|
+
const reason = formatErrorReason(error);
|
|
23
|
+
result = {
|
|
24
|
+
alreadyRunning: true,
|
|
25
|
+
message: `daemon already running (${deps.socketPath}; could not replace stale daemon ${runningVersion} -> ${deps.localVersion}: ${reason})`,
|
|
26
|
+
};
|
|
27
|
+
(0, runtime_1.emitNervesEvent)({
|
|
28
|
+
level: "warn",
|
|
29
|
+
component: "daemon",
|
|
30
|
+
event: "daemon.runtime_sync_decision",
|
|
31
|
+
message: "evaluated daemon runtime sync outcome",
|
|
32
|
+
meta: { socketPath: deps.socketPath, localVersion: deps.localVersion, runningVersion, action: "stale_replace_failed", reason },
|
|
33
|
+
});
|
|
34
|
+
return result;
|
|
35
|
+
}
|
|
36
|
+
deps.cleanupStaleSocket(deps.socketPath);
|
|
37
|
+
const started = await deps.startDaemonProcess(deps.socketPath);
|
|
38
|
+
result = {
|
|
39
|
+
alreadyRunning: false,
|
|
40
|
+
message: `restarted stale daemon from ${runningVersion} to ${deps.localVersion} (pid ${started.pid ?? "unknown"})`,
|
|
41
|
+
};
|
|
42
|
+
(0, runtime_1.emitNervesEvent)({
|
|
43
|
+
component: "daemon",
|
|
44
|
+
event: "daemon.runtime_sync_decision",
|
|
45
|
+
message: "evaluated daemon runtime sync outcome",
|
|
46
|
+
meta: { socketPath: deps.socketPath, localVersion: deps.localVersion, runningVersion, action: "stale_restarted", pid: started.pid ?? null },
|
|
47
|
+
});
|
|
48
|
+
return result;
|
|
49
|
+
}
|
|
50
|
+
if (!isKnownVersion(deps.localVersion) || !isKnownVersion(runningVersion)) {
|
|
51
|
+
result = {
|
|
52
|
+
alreadyRunning: true,
|
|
53
|
+
message: `daemon already running (${deps.socketPath}; unable to verify version)`,
|
|
54
|
+
};
|
|
55
|
+
(0, runtime_1.emitNervesEvent)({
|
|
56
|
+
component: "daemon",
|
|
57
|
+
event: "daemon.runtime_sync_decision",
|
|
58
|
+
message: "evaluated daemon runtime sync outcome",
|
|
59
|
+
meta: { socketPath: deps.socketPath, localVersion: deps.localVersion, runningVersion, action: "unknown_version" },
|
|
60
|
+
});
|
|
61
|
+
return result;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
catch (error) {
|
|
65
|
+
const reason = formatErrorReason(error);
|
|
66
|
+
const result = {
|
|
67
|
+
alreadyRunning: true,
|
|
68
|
+
message: `daemon already running (${deps.socketPath}; unable to verify version: ${reason})`,
|
|
69
|
+
};
|
|
70
|
+
(0, runtime_1.emitNervesEvent)({
|
|
71
|
+
level: "warn",
|
|
72
|
+
component: "daemon",
|
|
73
|
+
event: "daemon.runtime_sync_decision",
|
|
74
|
+
message: "evaluated daemon runtime sync outcome",
|
|
75
|
+
meta: { socketPath: deps.socketPath, localVersion: deps.localVersion, action: "status_lookup_failed", reason },
|
|
76
|
+
});
|
|
77
|
+
return result;
|
|
78
|
+
}
|
|
79
|
+
const result = {
|
|
80
|
+
alreadyRunning: true,
|
|
81
|
+
message: `daemon already running (${deps.socketPath})`,
|
|
82
|
+
};
|
|
83
|
+
(0, runtime_1.emitNervesEvent)({
|
|
84
|
+
component: "daemon",
|
|
85
|
+
event: "daemon.runtime_sync_decision",
|
|
86
|
+
message: "evaluated daemon runtime sync outcome",
|
|
87
|
+
meta: { socketPath: deps.socketPath, localVersion: deps.localVersion, action: "already_current" },
|
|
88
|
+
});
|
|
89
|
+
return result;
|
|
90
|
+
}
|
|
@@ -39,14 +39,29 @@ 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
|
-
|
|
43
|
-
|
|
42
|
+
const runtime_metadata_1 = require("./runtime-metadata");
|
|
43
|
+
function buildWorkerRows(snapshots) {
|
|
44
|
+
return snapshots.map((snapshot) => ({
|
|
45
|
+
agent: snapshot.name,
|
|
46
|
+
worker: snapshot.channel,
|
|
47
|
+
status: snapshot.status,
|
|
48
|
+
pid: snapshot.pid,
|
|
49
|
+
restartCount: snapshot.restartCount,
|
|
50
|
+
startedAt: snapshot.startedAt,
|
|
51
|
+
}));
|
|
52
|
+
}
|
|
53
|
+
function formatStatusSummary(payload) {
|
|
54
|
+
if (payload.overview.workerCount === 0 && payload.overview.senseCount === 0) {
|
|
44
55
|
return "no managed agents";
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
56
|
+
}
|
|
57
|
+
const rows = [
|
|
58
|
+
...payload.workers.map((row) => `${row.agent}/${row.worker}:${row.status}`),
|
|
59
|
+
...payload.senses
|
|
60
|
+
.filter((row) => row.enabled)
|
|
61
|
+
.map((row) => `${row.agent}/${row.sense}:${row.status}`),
|
|
62
|
+
];
|
|
63
|
+
const detail = rows.length > 0 ? `\titems=${rows.join(",")}` : "";
|
|
64
|
+
return `daemon=${payload.overview.daemon}\tworkers=${payload.overview.workerCount}\tsenses=${payload.overview.senseCount}\thealth=${payload.overview.health}${detail}`;
|
|
50
65
|
}
|
|
51
66
|
function parseIncomingCommand(raw) {
|
|
52
67
|
let parsed;
|
|
@@ -71,6 +86,7 @@ class OuroDaemon {
|
|
|
71
86
|
scheduler;
|
|
72
87
|
healthMonitor;
|
|
73
88
|
router;
|
|
89
|
+
senseManager;
|
|
74
90
|
bundlesRoot;
|
|
75
91
|
server = null;
|
|
76
92
|
constructor(options) {
|
|
@@ -79,6 +95,7 @@ class OuroDaemon {
|
|
|
79
95
|
this.scheduler = options.scheduler;
|
|
80
96
|
this.healthMonitor = options.healthMonitor;
|
|
81
97
|
this.router = options.router;
|
|
98
|
+
this.senseManager = options.senseManager ?? null;
|
|
82
99
|
this.bundlesRoot = options.bundlesRoot ?? (0, identity_1.getAgentBundlesRoot)();
|
|
83
100
|
}
|
|
84
101
|
async start() {
|
|
@@ -91,6 +108,7 @@ class OuroDaemon {
|
|
|
91
108
|
meta: { socketPath: this.socketPath },
|
|
92
109
|
});
|
|
93
110
|
await this.processManager.startAutoStartAgents();
|
|
111
|
+
await this.senseManager?.startAutoStartSenses();
|
|
94
112
|
this.scheduler.start?.();
|
|
95
113
|
await this.scheduler.reconcile?.();
|
|
96
114
|
await this.drainPendingBundleMessages();
|
|
@@ -178,6 +196,7 @@ class OuroDaemon {
|
|
|
178
196
|
});
|
|
179
197
|
this.scheduler.stop?.();
|
|
180
198
|
await this.processManager.stopAll();
|
|
199
|
+
await this.senseManager?.stopAll();
|
|
181
200
|
if (this.server) {
|
|
182
201
|
await new Promise((resolve) => {
|
|
183
202
|
this.server?.close(() => resolve());
|
|
@@ -217,10 +236,24 @@ class OuroDaemon {
|
|
|
217
236
|
return { ok: true, message: "daemon stopped" };
|
|
218
237
|
case "daemon.status": {
|
|
219
238
|
const snapshots = this.processManager.listAgentSnapshots();
|
|
239
|
+
const workers = buildWorkerRows(snapshots);
|
|
240
|
+
const senses = this.senseManager?.listSenseRows() ?? [];
|
|
241
|
+
const data = {
|
|
242
|
+
overview: {
|
|
243
|
+
daemon: "running",
|
|
244
|
+
health: workers.every((worker) => worker.status === "running") ? "ok" : "warn",
|
|
245
|
+
socketPath: this.socketPath,
|
|
246
|
+
...(0, runtime_metadata_1.getRuntimeMetadata)(),
|
|
247
|
+
workerCount: workers.length,
|
|
248
|
+
senseCount: senses.length,
|
|
249
|
+
},
|
|
250
|
+
workers,
|
|
251
|
+
senses,
|
|
252
|
+
};
|
|
220
253
|
return {
|
|
221
254
|
ok: true,
|
|
222
|
-
summary: formatStatusSummary(
|
|
223
|
-
data
|
|
255
|
+
summary: formatStatusSummary(data),
|
|
256
|
+
data,
|
|
224
257
|
};
|
|
225
258
|
}
|
|
226
259
|
case "daemon.health": {
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.playHatchAnimation = playHatchAnimation;
|
|
4
|
+
const runtime_1 = require("../../nerves/runtime");
|
|
5
|
+
const EGG = "\uD83E\uDD5A";
|
|
6
|
+
const SNAKE = "\uD83D\uDC0D";
|
|
7
|
+
const DOTS = " . . . ";
|
|
8
|
+
function wait(ms) {
|
|
9
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Play the hatch animation: egg -> dots -> snake + name.
|
|
13
|
+
* The writer function receives each chunk. Default writer is process.stderr.write.
|
|
14
|
+
*/
|
|
15
|
+
async function playHatchAnimation(hatchlingName, writer) {
|
|
16
|
+
(0, runtime_1.emitNervesEvent)({
|
|
17
|
+
component: "daemon",
|
|
18
|
+
event: "daemon.hatch_animation_start",
|
|
19
|
+
message: "playing hatch animation",
|
|
20
|
+
meta: { hatchlingName },
|
|
21
|
+
});
|
|
22
|
+
const write = writer ?? ((text) => process.stderr.write(text));
|
|
23
|
+
// Total animation time randomized between 3–5 seconds
|
|
24
|
+
const totalMs = 3000 + Math.floor(Math.random() * 2000);
|
|
25
|
+
const eggPhase = Math.floor(totalMs * 0.4);
|
|
26
|
+
const dotsPhase = Math.floor(totalMs * 0.4);
|
|
27
|
+
const revealPause = totalMs - eggPhase - dotsPhase;
|
|
28
|
+
write(`\n ${EGG}`);
|
|
29
|
+
await wait(eggPhase);
|
|
30
|
+
write(DOTS);
|
|
31
|
+
await wait(dotsPhase);
|
|
32
|
+
write(`${SNAKE} \x1b[1m${hatchlingName}\x1b[0m`);
|
|
33
|
+
await wait(revealPause);
|
|
34
|
+
write("\n\n");
|
|
35
|
+
}
|
|
@@ -33,6 +33,7 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
33
33
|
};
|
|
34
34
|
})();
|
|
35
35
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.writeSecretsFile = writeSecretsFile;
|
|
36
37
|
exports.runHatchFlow = runHatchFlow;
|
|
37
38
|
const fs = __importStar(require("fs"));
|
|
38
39
|
const os = __importStar(require("os"));
|
|
@@ -84,7 +85,7 @@ function buildSecretsTemplate() {
|
|
|
84
85
|
setupToken: "",
|
|
85
86
|
},
|
|
86
87
|
"openai-codex": {
|
|
87
|
-
model: "gpt-5.
|
|
88
|
+
model: "gpt-5.4",
|
|
88
89
|
oauthAccessToken: "",
|
|
89
90
|
},
|
|
90
91
|
},
|
|
@@ -206,15 +207,6 @@ function writeFriendImprint(bundleRoot, humanName, now) {
|
|
|
206
207
|
};
|
|
207
208
|
fs.writeFileSync(path.join(friendsDir, `${id}.json`), `${JSON.stringify(record, null, 2)}\n`, "utf-8");
|
|
208
209
|
}
|
|
209
|
-
function writeHatchlingPsyche(bundleRoot, input, identityFileName) {
|
|
210
|
-
const psycheDir = path.join(bundleRoot, "psyche");
|
|
211
|
-
fs.mkdirSync(psycheDir, { recursive: true });
|
|
212
|
-
fs.writeFileSync(path.join(psycheDir, "SOUL.md"), "# SOUL\n\nI am a practical, collaborative agent. I keep commitments and communicate clearly.\n", "utf-8");
|
|
213
|
-
fs.writeFileSync(path.join(psycheDir, "IDENTITY.md"), `# IDENTITY\n\nI'm ${input.agentName}, newly hatched and ready to help ${input.humanName}.`, "utf-8");
|
|
214
|
-
fs.writeFileSync(path.join(psycheDir, "LORE.md"), `# LORE\n\nHatched with specialist identity seed: ${identityFileName}.`, "utf-8");
|
|
215
|
-
fs.writeFileSync(path.join(psycheDir, "TACIT.md"), "# TACIT\n\n- Save what I learn.\n- Keep tasks current.\n", "utf-8");
|
|
216
|
-
fs.writeFileSync(path.join(psycheDir, "ASPIRATIONS.md"), "# ASPIRATIONS\n\n- Become a reliable partner for my primary friend.\n", "utf-8");
|
|
217
|
-
}
|
|
218
210
|
function writeMemoryScaffold(bundleRoot) {
|
|
219
211
|
const memoryRoot = path.join(bundleRoot, "psyche", "memory");
|
|
220
212
|
fs.mkdirSync(path.join(memoryRoot, "daily"), { recursive: true });
|
|
@@ -266,7 +258,6 @@ async function runHatchFlow(input, deps = {}) {
|
|
|
266
258
|
writeReadme(path.join(bundleRoot, "senses"), "Sense-specific config.");
|
|
267
259
|
writeReadme(path.join(bundleRoot, "senses", "teams"), "Teams sense config.");
|
|
268
260
|
writeHatchlingAgentConfig(bundleRoot, input);
|
|
269
|
-
writeHatchlingPsyche(bundleRoot, input, selected.fileName);
|
|
270
261
|
writeMemoryScaffold(bundleRoot);
|
|
271
262
|
writeFriendImprint(bundleRoot, input.humanName, now);
|
|
272
263
|
writeHeartbeatTask(bundleRoot, now);
|
|
@@ -42,7 +42,12 @@ const os = __importStar(require("os"));
|
|
|
42
42
|
const path = __importStar(require("path"));
|
|
43
43
|
const runtime_1 = require("../../nerves/runtime");
|
|
44
44
|
function getSpecialistIdentitySourceDir() {
|
|
45
|
-
|
|
45
|
+
// Prefer ~/AgentBundles/ if it exists (user may have customized identities)
|
|
46
|
+
const userSource = path.join(os.homedir(), "AgentBundles", "AdoptionSpecialist.ouro", "psyche", "identities");
|
|
47
|
+
if (fs.existsSync(userSource))
|
|
48
|
+
return userSource;
|
|
49
|
+
// Fall back to the bundled copy shipped with the npm package
|
|
50
|
+
return path.join(__dirname, "..", "..", "..", "AdoptionSpecialist.ouro", "psyche", "identities");
|
|
46
51
|
}
|
|
47
52
|
function getRepoSpecialistIdentitiesDir() {
|
|
48
53
|
return path.join(process.cwd(), "AdoptionSpecialist.ouro", "psyche", "identities");
|
|
@@ -37,8 +37,9 @@ exports.runOuroBotWrapper = runOuroBotWrapper;
|
|
|
37
37
|
const runtime_1 = require("../../nerves/runtime");
|
|
38
38
|
const daemon_cli_1 = require("./daemon-cli");
|
|
39
39
|
async function defaultLoadCanonicalRunner() {
|
|
40
|
-
|
|
41
|
-
|
|
40
|
+
// Use the subpath export so we get the daemon-cli module directly,
|
|
41
|
+
// NOT the root entry point which has side-effects (immediately runs the CLI).
|
|
42
|
+
const specifier = "@ouro.bot/cli/runOuroCli";
|
|
42
43
|
const loaded = await Promise.resolve(`${specifier}`).then(s => __importStar(require(s)));
|
|
43
44
|
const candidate = Object.prototype.hasOwnProperty.call(loaded, "runOuroCli")
|
|
44
45
|
? loaded["runOuroCli"]
|
|
@@ -46,7 +47,7 @@ async function defaultLoadCanonicalRunner() {
|
|
|
46
47
|
if (typeof candidate === "function") {
|
|
47
48
|
return candidate;
|
|
48
49
|
}
|
|
49
|
-
throw new Error("@ouro.bot/cli does not export runOuroCli");
|
|
50
|
+
throw new Error("@ouro.bot/cli/runOuroCli does not export runOuroCli");
|
|
50
51
|
}
|
|
51
52
|
function defaultWriteStdout(_text) {
|
|
52
53
|
// Wrapper is intentionally silent by default to avoid duplicate terminal output.
|
|
@@ -0,0 +1,178 @@
|
|
|
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 CLI_PACKAGE_SPECIFIER = "@ouro.bot/cli@alpha";
|
|
42
|
+
const WRAPPER_SCRIPT = `#!/bin/sh
|
|
43
|
+
exec npx --yes ${CLI_PACKAGE_SPECIFIER} "$@"
|
|
44
|
+
`;
|
|
45
|
+
function detectShellProfile(homeDir, shell) {
|
|
46
|
+
if (!shell)
|
|
47
|
+
return null;
|
|
48
|
+
const base = path.basename(shell);
|
|
49
|
+
if (base === "zsh")
|
|
50
|
+
return path.join(homeDir, ".zshrc");
|
|
51
|
+
if (base === "bash") {
|
|
52
|
+
// macOS uses .bash_profile, Linux uses .bashrc
|
|
53
|
+
const profilePath = path.join(homeDir, ".bash_profile");
|
|
54
|
+
return profilePath;
|
|
55
|
+
}
|
|
56
|
+
if (base === "fish")
|
|
57
|
+
return path.join(homeDir, ".config", "fish", "config.fish");
|
|
58
|
+
return null;
|
|
59
|
+
}
|
|
60
|
+
function isBinDirInPath(binDir, envPath) {
|
|
61
|
+
return envPath.split(path.delimiter).some((p) => p === binDir);
|
|
62
|
+
}
|
|
63
|
+
function buildPathExportLine(binDir, shell) {
|
|
64
|
+
const base = shell ? path.basename(shell) : /* v8 ignore next -- unreachable: only called when detectShellProfile returns non-null, which requires shell @preserve */ "";
|
|
65
|
+
if (base === "fish") {
|
|
66
|
+
return `\n# Added by ouro\nset -gx PATH ${binDir} $PATH\n`;
|
|
67
|
+
}
|
|
68
|
+
return `\n# Added by ouro\nexport PATH="${binDir}:$PATH"\n`;
|
|
69
|
+
}
|
|
70
|
+
function installOuroCommand(deps = {}) {
|
|
71
|
+
/* v8 ignore start -- dep defaults: only used in real runtime, tests always inject @preserve */
|
|
72
|
+
const platform = deps.platform ?? process.platform;
|
|
73
|
+
const homeDir = deps.homeDir ?? os.homedir();
|
|
74
|
+
const existsSync = deps.existsSync ?? fs.existsSync;
|
|
75
|
+
const mkdirSync = deps.mkdirSync ?? fs.mkdirSync;
|
|
76
|
+
const writeFileSync = deps.writeFileSync ?? fs.writeFileSync;
|
|
77
|
+
const readFileSync = deps.readFileSync ?? ((p, enc) => fs.readFileSync(p, enc));
|
|
78
|
+
const appendFileSync = deps.appendFileSync ?? fs.appendFileSync;
|
|
79
|
+
const chmodSync = deps.chmodSync ?? fs.chmodSync;
|
|
80
|
+
const envPath = deps.envPath ?? process.env.PATH ?? "";
|
|
81
|
+
const shell = deps.shell ?? process.env.SHELL;
|
|
82
|
+
/* v8 ignore stop */
|
|
83
|
+
if (platform === "win32") {
|
|
84
|
+
(0, runtime_1.emitNervesEvent)({
|
|
85
|
+
component: "daemon",
|
|
86
|
+
event: "daemon.ouro_path_install_skip",
|
|
87
|
+
message: "skipped ouro PATH install on Windows",
|
|
88
|
+
meta: { platform },
|
|
89
|
+
});
|
|
90
|
+
return { installed: false, scriptPath: null, pathReady: false, shellProfileUpdated: null, skippedReason: "windows" };
|
|
91
|
+
}
|
|
92
|
+
const binDir = path.join(homeDir, ".local", "bin");
|
|
93
|
+
const scriptPath = path.join(binDir, "ouro");
|
|
94
|
+
(0, runtime_1.emitNervesEvent)({
|
|
95
|
+
component: "daemon",
|
|
96
|
+
event: "daemon.ouro_path_install_start",
|
|
97
|
+
message: "installing ouro command to PATH",
|
|
98
|
+
meta: { scriptPath, binDir },
|
|
99
|
+
});
|
|
100
|
+
// If ouro already exists, check content and repair if stale
|
|
101
|
+
if (existsSync(scriptPath)) {
|
|
102
|
+
let existingContent = "";
|
|
103
|
+
try {
|
|
104
|
+
existingContent = readFileSync(scriptPath, "utf-8");
|
|
105
|
+
}
|
|
106
|
+
catch {
|
|
107
|
+
// Can't read — treat as stale, will overwrite below
|
|
108
|
+
}
|
|
109
|
+
if (existingContent === WRAPPER_SCRIPT) {
|
|
110
|
+
(0, runtime_1.emitNervesEvent)({
|
|
111
|
+
component: "daemon",
|
|
112
|
+
event: "daemon.ouro_path_install_skip",
|
|
113
|
+
message: "ouro command already installed",
|
|
114
|
+
meta: { scriptPath },
|
|
115
|
+
});
|
|
116
|
+
return { installed: false, scriptPath, pathReady: isBinDirInPath(binDir, envPath), shellProfileUpdated: null, skippedReason: "already-installed" };
|
|
117
|
+
}
|
|
118
|
+
// Content is stale — repair by overwriting
|
|
119
|
+
(0, runtime_1.emitNervesEvent)({
|
|
120
|
+
component: "daemon",
|
|
121
|
+
event: "daemon.ouro_path_install_repair",
|
|
122
|
+
message: "repairing stale ouro wrapper script",
|
|
123
|
+
meta: { scriptPath },
|
|
124
|
+
});
|
|
125
|
+
}
|
|
126
|
+
try {
|
|
127
|
+
mkdirSync(binDir, { recursive: true });
|
|
128
|
+
writeFileSync(scriptPath, WRAPPER_SCRIPT, { mode: 0o755 });
|
|
129
|
+
chmodSync(scriptPath, 0o755);
|
|
130
|
+
}
|
|
131
|
+
catch (error) {
|
|
132
|
+
(0, runtime_1.emitNervesEvent)({
|
|
133
|
+
level: "warn",
|
|
134
|
+
component: "daemon",
|
|
135
|
+
event: "daemon.ouro_path_install_error",
|
|
136
|
+
message: "failed to install ouro command",
|
|
137
|
+
meta: { error: error instanceof Error ? error.message : /* v8 ignore next -- defensive: non-Error catch branch @preserve */ String(error) },
|
|
138
|
+
});
|
|
139
|
+
return { installed: false, scriptPath: null, pathReady: false, shellProfileUpdated: null, skippedReason: error instanceof Error ? error.message : /* v8 ignore next -- defensive @preserve */ String(error) };
|
|
140
|
+
}
|
|
141
|
+
// Check if ~/.local/bin is already in PATH
|
|
142
|
+
let shellProfileUpdated = null;
|
|
143
|
+
const pathReady = isBinDirInPath(binDir, envPath);
|
|
144
|
+
if (!pathReady) {
|
|
145
|
+
const profilePath = detectShellProfile(homeDir, shell);
|
|
146
|
+
if (profilePath) {
|
|
147
|
+
try {
|
|
148
|
+
let existing = "";
|
|
149
|
+
try {
|
|
150
|
+
existing = readFileSync(profilePath, "utf-8");
|
|
151
|
+
}
|
|
152
|
+
catch {
|
|
153
|
+
// Profile doesn't exist yet — that's fine, we'll create it
|
|
154
|
+
}
|
|
155
|
+
if (!existing.includes(binDir)) {
|
|
156
|
+
appendFileSync(profilePath, buildPathExportLine(binDir, shell));
|
|
157
|
+
shellProfileUpdated = profilePath;
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
catch (error) {
|
|
161
|
+
(0, runtime_1.emitNervesEvent)({
|
|
162
|
+
level: "warn",
|
|
163
|
+
component: "daemon",
|
|
164
|
+
event: "daemon.ouro_path_profile_error",
|
|
165
|
+
message: "failed to update shell profile for PATH",
|
|
166
|
+
meta: { profilePath, error: error instanceof Error ? error.message : /* v8 ignore next -- defensive: non-Error catch branch @preserve */ String(error) },
|
|
167
|
+
});
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
(0, runtime_1.emitNervesEvent)({
|
|
172
|
+
component: "daemon",
|
|
173
|
+
event: "daemon.ouro_path_install_end",
|
|
174
|
+
message: "ouro command installed",
|
|
175
|
+
meta: { scriptPath, pathReady, shellProfileUpdated },
|
|
176
|
+
});
|
|
177
|
+
return { installed: true, scriptPath, pathReady, shellProfileUpdated };
|
|
178
|
+
}
|
|
@@ -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,
|
|
@@ -43,23 +43,27 @@ const DEFAULT_RUNTIME_LOGGING = {
|
|
|
43
43
|
level: "info",
|
|
44
44
|
sinks: ["terminal", "ndjson"],
|
|
45
45
|
};
|
|
46
|
+
function defaultLevelForProcess(processName) {
|
|
47
|
+
return processName === "daemon" ? "info" : "warn";
|
|
48
|
+
}
|
|
46
49
|
function isLogLevel(value) {
|
|
47
50
|
return value === "debug" || value === "info" || value === "warn" || value === "error";
|
|
48
51
|
}
|
|
49
|
-
function resolveRuntimeLoggingConfig(configPath) {
|
|
52
|
+
function resolveRuntimeLoggingConfig(configPath, processName) {
|
|
53
|
+
const defaultLevel = defaultLevelForProcess(processName);
|
|
50
54
|
let parsed = null;
|
|
51
55
|
try {
|
|
52
56
|
const raw = fs.readFileSync(configPath, "utf-8");
|
|
53
57
|
parsed = JSON.parse(raw);
|
|
54
58
|
}
|
|
55
59
|
catch {
|
|
56
|
-
return { ...DEFAULT_RUNTIME_LOGGING };
|
|
60
|
+
return { ...DEFAULT_RUNTIME_LOGGING, level: defaultLevel };
|
|
57
61
|
}
|
|
58
62
|
if (!parsed || typeof parsed !== "object") {
|
|
59
|
-
return { ...DEFAULT_RUNTIME_LOGGING };
|
|
63
|
+
return { ...DEFAULT_RUNTIME_LOGGING, level: defaultLevel };
|
|
60
64
|
}
|
|
61
65
|
const candidate = parsed;
|
|
62
|
-
const level = isLogLevel(candidate.level) ? candidate.level :
|
|
66
|
+
const level = isLogLevel(candidate.level) ? candidate.level : defaultLevel;
|
|
63
67
|
const sinks = Array.isArray(candidate.sinks)
|
|
64
68
|
? candidate.sinks.filter((entry) => entry === "terminal" || entry === "ndjson")
|
|
65
69
|
: DEFAULT_RUNTIME_LOGGING.sinks;
|
|
@@ -71,7 +75,7 @@ function resolveRuntimeLoggingConfig(configPath) {
|
|
|
71
75
|
function configureDaemonRuntimeLogger(processName, options = {}) {
|
|
72
76
|
const homeDir = options.homeDir ?? os.homedir();
|
|
73
77
|
const configPath = options.configPath ?? path.join(homeDir, ".agentstate", "daemon", "logging.json");
|
|
74
|
-
const config = resolveRuntimeLoggingConfig(configPath);
|
|
78
|
+
const config = resolveRuntimeLoggingConfig(configPath, processName);
|
|
75
79
|
const sinks = config.sinks.map((sinkName) => {
|
|
76
80
|
if (sinkName === "terminal") {
|
|
77
81
|
return (0, nerves_1.createTerminalSink)();
|