@ouro.bot/cli 0.1.0-alpha.3 → 0.1.0-alpha.30
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/AdoptionSpecialist.ouro/psyche/identities/python.md +30 -0
- package/assets/ouroboros.png +0 -0
- package/changelog.json +80 -0
- package/dist/heart/config.js +66 -4
- package/dist/heart/core.js +75 -2
- package/dist/heart/daemon/agent-discovery.js +81 -0
- package/dist/heart/daemon/daemon-cli.js +562 -64
- package/dist/heart/daemon/daemon-entry.js +14 -5
- package/dist/heart/daemon/daemon-runtime-sync.js +90 -0
- package/dist/heart/daemon/daemon.js +87 -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/hooks/bundle-meta.js +92 -0
- package/dist/heart/daemon/launchd.js +134 -0
- package/dist/heart/daemon/ouro-bot-global-installer.js +128 -0
- 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/run-hooks.js +37 -0
- 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/staged-restart.js +114 -0
- package/dist/heart/daemon/subagent-installer.js +10 -1
- package/dist/heart/daemon/update-checker.js +103 -0
- package/dist/heart/daemon/update-hooks.js +138 -0
- package/dist/heart/daemon/wrapper-publish-guard.js +86 -0
- package/dist/heart/identity.js +85 -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 +69 -0
- package/dist/mind/first-impressions.js +2 -1
- package/dist/mind/friends/channel.js +8 -0
- package/dist/mind/friends/types.js +1 -1
- package/dist/mind/phrases.js +1 -0
- package/dist/mind/prompt.js +94 -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 +434 -0
- package/dist/senses/bluebubbles-entry.js +11 -0
- package/dist/senses/bluebubbles-media.js +338 -0
- package/dist/senses/bluebubbles-model.js +251 -0
- package/dist/senses/bluebubbles-mutation-log.js +76 -0
- package/dist/senses/bluebubbles-session-cleanup.js +73 -0
- package/dist/senses/bluebubbles.js +449 -0
- package/dist/senses/cli.js +299 -133
- package/dist/senses/debug-activity.js +108 -0
- package/dist/senses/teams.js +173 -54
- package/package.json +15 -6
- 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
|
@@ -35,6 +35,7 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
35
35
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
36
|
exports.ensureDaemonRunning = ensureDaemonRunning;
|
|
37
37
|
exports.parseOuroCommand = parseOuroCommand;
|
|
38
|
+
exports.discoverExistingCredentials = discoverExistingCredentials;
|
|
38
39
|
exports.createDefaultOuroCliDeps = createDefaultOuroCliDeps;
|
|
39
40
|
exports.runOuroCli = runOuroCli;
|
|
40
41
|
const child_process_1 = require("child_process");
|
|
@@ -47,15 +48,174 @@ const runtime_1 = require("../../nerves/runtime");
|
|
|
47
48
|
const store_file_1 = require("../../mind/friends/store-file");
|
|
48
49
|
const types_1 = require("../../mind/friends/types");
|
|
49
50
|
const ouro_uti_1 = require("./ouro-uti");
|
|
51
|
+
const ouro_path_installer_1 = require("./ouro-path-installer");
|
|
50
52
|
const subagent_installer_1 = require("./subagent-installer");
|
|
51
53
|
const hatch_flow_1 = require("./hatch-flow");
|
|
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
|
+
const runtime_metadata_1 = require("./runtime-metadata");
|
|
58
|
+
const daemon_runtime_sync_1 = require("./daemon-runtime-sync");
|
|
59
|
+
const agent_discovery_1 = require("./agent-discovery");
|
|
60
|
+
const update_hooks_1 = require("./update-hooks");
|
|
61
|
+
const bundle_meta_1 = require("./hooks/bundle-meta");
|
|
62
|
+
const bundle_manifest_1 = require("../../mind/bundle-manifest");
|
|
63
|
+
const ouro_bot_global_installer_1 = require("./ouro-bot-global-installer");
|
|
64
|
+
function stringField(value) {
|
|
65
|
+
return typeof value === "string" ? value : null;
|
|
66
|
+
}
|
|
67
|
+
function numberField(value) {
|
|
68
|
+
return typeof value === "number" && Number.isFinite(value) ? value : null;
|
|
69
|
+
}
|
|
70
|
+
function booleanField(value) {
|
|
71
|
+
return typeof value === "boolean" ? value : null;
|
|
72
|
+
}
|
|
73
|
+
function parseStatusPayload(data) {
|
|
74
|
+
if (!data || typeof data !== "object" || Array.isArray(data))
|
|
75
|
+
return null;
|
|
76
|
+
const raw = data;
|
|
77
|
+
const overview = raw.overview;
|
|
78
|
+
const senses = raw.senses;
|
|
79
|
+
const workers = raw.workers;
|
|
80
|
+
if (!overview || typeof overview !== "object" || Array.isArray(overview))
|
|
81
|
+
return null;
|
|
82
|
+
if (!Array.isArray(senses) || !Array.isArray(workers))
|
|
83
|
+
return null;
|
|
84
|
+
const parsedOverview = {
|
|
85
|
+
daemon: stringField(overview.daemon) ?? "unknown",
|
|
86
|
+
health: stringField(overview.health) ?? "unknown",
|
|
87
|
+
socketPath: stringField(overview.socketPath) ?? "unknown",
|
|
88
|
+
version: stringField(overview.version) ?? "unknown",
|
|
89
|
+
lastUpdated: stringField(overview.lastUpdated) ?? "unknown",
|
|
90
|
+
workerCount: numberField(overview.workerCount) ?? 0,
|
|
91
|
+
senseCount: numberField(overview.senseCount) ?? 0,
|
|
92
|
+
};
|
|
93
|
+
const parsedSenses = senses.map((entry) => {
|
|
94
|
+
if (!entry || typeof entry !== "object" || Array.isArray(entry))
|
|
95
|
+
return null;
|
|
96
|
+
const row = entry;
|
|
97
|
+
const agent = stringField(row.agent);
|
|
98
|
+
const sense = stringField(row.sense);
|
|
99
|
+
const status = stringField(row.status);
|
|
100
|
+
const detail = stringField(row.detail);
|
|
101
|
+
const enabled = booleanField(row.enabled);
|
|
102
|
+
if (!agent || !sense || !status || detail === null || enabled === null)
|
|
103
|
+
return null;
|
|
104
|
+
return {
|
|
105
|
+
agent,
|
|
106
|
+
sense,
|
|
107
|
+
label: stringField(row.label) ?? undefined,
|
|
108
|
+
enabled,
|
|
109
|
+
status,
|
|
110
|
+
detail,
|
|
111
|
+
};
|
|
112
|
+
});
|
|
113
|
+
const parsedWorkers = workers.map((entry) => {
|
|
114
|
+
if (!entry || typeof entry !== "object" || Array.isArray(entry))
|
|
115
|
+
return null;
|
|
116
|
+
const row = entry;
|
|
117
|
+
const agent = stringField(row.agent);
|
|
118
|
+
const worker = stringField(row.worker);
|
|
119
|
+
const status = stringField(row.status);
|
|
120
|
+
const restartCount = numberField(row.restartCount);
|
|
121
|
+
const hasPid = Object.prototype.hasOwnProperty.call(row, "pid");
|
|
122
|
+
const pid = row.pid === null ? null : numberField(row.pid);
|
|
123
|
+
const pidInvalid = !hasPid || (row.pid !== null && pid === null);
|
|
124
|
+
if (!agent || !worker || !status || restartCount === null || pidInvalid)
|
|
125
|
+
return null;
|
|
126
|
+
return {
|
|
127
|
+
agent,
|
|
128
|
+
worker,
|
|
129
|
+
status,
|
|
130
|
+
pid,
|
|
131
|
+
restartCount,
|
|
132
|
+
};
|
|
133
|
+
});
|
|
134
|
+
if (parsedSenses.some((row) => row === null) || parsedWorkers.some((row) => row === null))
|
|
135
|
+
return null;
|
|
136
|
+
return {
|
|
137
|
+
overview: parsedOverview,
|
|
138
|
+
senses: parsedSenses,
|
|
139
|
+
workers: parsedWorkers,
|
|
140
|
+
};
|
|
141
|
+
}
|
|
142
|
+
function humanizeSenseName(sense, label) {
|
|
143
|
+
if (label)
|
|
144
|
+
return label;
|
|
145
|
+
if (sense === "cli")
|
|
146
|
+
return "CLI";
|
|
147
|
+
if (sense === "bluebubbles")
|
|
148
|
+
return "BlueBubbles";
|
|
149
|
+
if (sense === "teams")
|
|
150
|
+
return "Teams";
|
|
151
|
+
return sense;
|
|
152
|
+
}
|
|
153
|
+
function formatTable(headers, rows) {
|
|
154
|
+
const widths = headers.map((header, index) => Math.max(header.length, ...rows.map((row) => row[index].length)));
|
|
155
|
+
const renderRow = (row) => `| ${row.map((cell, index) => cell.padEnd(widths[index])).join(" | ")} |`;
|
|
156
|
+
const divider = `|-${widths.map((width) => "-".repeat(width)).join("-|-")}-|`;
|
|
157
|
+
return [
|
|
158
|
+
renderRow(headers),
|
|
159
|
+
divider,
|
|
160
|
+
...rows.map(renderRow),
|
|
161
|
+
].join("\n");
|
|
162
|
+
}
|
|
163
|
+
function formatDaemonStatusOutput(response, fallback) {
|
|
164
|
+
const payload = parseStatusPayload(response.data);
|
|
165
|
+
if (!payload)
|
|
166
|
+
return fallback;
|
|
167
|
+
const overviewRows = [
|
|
168
|
+
["Daemon", payload.overview.daemon],
|
|
169
|
+
["Socket", payload.overview.socketPath],
|
|
170
|
+
["Version", payload.overview.version],
|
|
171
|
+
["Last Updated", payload.overview.lastUpdated],
|
|
172
|
+
["Workers", String(payload.overview.workerCount)],
|
|
173
|
+
["Senses", String(payload.overview.senseCount)],
|
|
174
|
+
["Health", payload.overview.health],
|
|
175
|
+
];
|
|
176
|
+
const senseRows = payload.senses.map((row) => [
|
|
177
|
+
row.agent,
|
|
178
|
+
humanizeSenseName(row.sense, row.label),
|
|
179
|
+
row.enabled ? "ON" : "OFF",
|
|
180
|
+
row.status,
|
|
181
|
+
row.detail,
|
|
182
|
+
]);
|
|
183
|
+
const workerRows = payload.workers.map((row) => [
|
|
184
|
+
row.agent,
|
|
185
|
+
row.worker,
|
|
186
|
+
row.status,
|
|
187
|
+
row.pid === null ? "n/a" : String(row.pid),
|
|
188
|
+
String(row.restartCount),
|
|
189
|
+
]);
|
|
190
|
+
return [
|
|
191
|
+
"Overview",
|
|
192
|
+
formatTable(["Item", "Value"], overviewRows),
|
|
193
|
+
"",
|
|
194
|
+
"Senses",
|
|
195
|
+
formatTable(["Agent", "Sense", "Enabled", "State", "Detail"], senseRows),
|
|
196
|
+
"",
|
|
197
|
+
"Workers",
|
|
198
|
+
formatTable(["Agent", "Worker", "State", "PID", "Restarts"], workerRows),
|
|
199
|
+
].join("\n");
|
|
200
|
+
}
|
|
52
201
|
async function ensureDaemonRunning(deps) {
|
|
53
202
|
const alive = await deps.checkSocketAlive(deps.socketPath);
|
|
54
203
|
if (alive) {
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
204
|
+
const localRuntime = (0, runtime_metadata_1.getRuntimeMetadata)();
|
|
205
|
+
return (0, daemon_runtime_sync_1.ensureCurrentDaemonRuntime)({
|
|
206
|
+
socketPath: deps.socketPath,
|
|
207
|
+
localVersion: localRuntime.version,
|
|
208
|
+
fetchRunningVersion: async () => {
|
|
209
|
+
const status = await deps.sendCommand(deps.socketPath, { kind: "daemon.status" });
|
|
210
|
+
const payload = parseStatusPayload(status.data);
|
|
211
|
+
return payload?.overview.version ?? "unknown";
|
|
212
|
+
},
|
|
213
|
+
stopDaemon: async () => {
|
|
214
|
+
await deps.sendCommand(deps.socketPath, { kind: "daemon.stop" });
|
|
215
|
+
},
|
|
216
|
+
cleanupStaleSocket: deps.cleanupStaleSocket,
|
|
217
|
+
startDaemonProcess: deps.startDaemonProcess,
|
|
218
|
+
});
|
|
59
219
|
}
|
|
60
220
|
deps.cleanupStaleSocket(deps.socketPath);
|
|
61
221
|
const started = await deps.startDaemonProcess(deps.socketPath);
|
|
@@ -68,13 +228,50 @@ function usage() {
|
|
|
68
228
|
return [
|
|
69
229
|
"Usage:",
|
|
70
230
|
" ouro [up]",
|
|
71
|
-
" ouro stop|status|logs|hatch",
|
|
231
|
+
" ouro stop|down|status|logs|hatch",
|
|
232
|
+
" ouro -v|--version",
|
|
72
233
|
" ouro chat <agent>",
|
|
73
234
|
" ouro msg --to <agent> [--session <id>] [--task <ref>] <message>",
|
|
74
235
|
" ouro poke <agent> --task <task-id>",
|
|
75
236
|
" ouro link <agent> --friend <id> --provider <provider> --external-id <external-id>",
|
|
76
237
|
].join("\n");
|
|
77
238
|
}
|
|
239
|
+
function formatVersionOutput() {
|
|
240
|
+
return (0, runtime_metadata_1.getRuntimeMetadata)().version;
|
|
241
|
+
}
|
|
242
|
+
function buildStoppedStatusPayload(socketPath) {
|
|
243
|
+
const metadata = (0, runtime_metadata_1.getRuntimeMetadata)();
|
|
244
|
+
return {
|
|
245
|
+
overview: {
|
|
246
|
+
daemon: "stopped",
|
|
247
|
+
health: "warn",
|
|
248
|
+
socketPath,
|
|
249
|
+
version: metadata.version,
|
|
250
|
+
lastUpdated: metadata.lastUpdated,
|
|
251
|
+
workerCount: 0,
|
|
252
|
+
senseCount: 0,
|
|
253
|
+
},
|
|
254
|
+
senses: [],
|
|
255
|
+
workers: [],
|
|
256
|
+
};
|
|
257
|
+
}
|
|
258
|
+
function daemonUnavailableStatusOutput(socketPath) {
|
|
259
|
+
return [
|
|
260
|
+
formatDaemonStatusOutput({
|
|
261
|
+
ok: true,
|
|
262
|
+
summary: "daemon not running",
|
|
263
|
+
data: buildStoppedStatusPayload(socketPath),
|
|
264
|
+
}, "daemon not running"),
|
|
265
|
+
"",
|
|
266
|
+
"daemon not running; run `ouro up`",
|
|
267
|
+
].join("\n");
|
|
268
|
+
}
|
|
269
|
+
function isDaemonUnavailableError(error) {
|
|
270
|
+
const code = typeof error === "object" && error !== null && "code" in error
|
|
271
|
+
? String(error.code ?? "")
|
|
272
|
+
: "";
|
|
273
|
+
return code === "ENOENT" || code === "ECONNREFUSED";
|
|
274
|
+
}
|
|
78
275
|
function parseMessageCommand(args) {
|
|
79
276
|
let to;
|
|
80
277
|
let sessionId;
|
|
@@ -241,7 +438,7 @@ function parseOuroCommand(args) {
|
|
|
241
438
|
return { kind: "daemon.up" };
|
|
242
439
|
if (head === "up")
|
|
243
440
|
return { kind: "daemon.up" };
|
|
244
|
-
if (head === "stop")
|
|
441
|
+
if (head === "stop" || head === "down")
|
|
245
442
|
return { kind: "daemon.stop" };
|
|
246
443
|
if (head === "status")
|
|
247
444
|
return { kind: "daemon.status" };
|
|
@@ -401,39 +598,15 @@ async function defaultPromptInput(question) {
|
|
|
401
598
|
}
|
|
402
599
|
}
|
|
403
600
|
function defaultListDiscoveredAgents() {
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
}
|
|
409
|
-
catch {
|
|
410
|
-
return [];
|
|
411
|
-
}
|
|
412
|
-
const discovered = [];
|
|
413
|
-
for (const entry of entries) {
|
|
414
|
-
if (!entry.isDirectory() || !entry.name.endsWith(".ouro"))
|
|
415
|
-
continue;
|
|
416
|
-
const agentName = entry.name.slice(0, -5);
|
|
417
|
-
const configPath = path.join(bundlesRoot, entry.name, "agent.json");
|
|
418
|
-
let enabled = true;
|
|
419
|
-
try {
|
|
420
|
-
const raw = fs.readFileSync(configPath, "utf-8");
|
|
421
|
-
const parsed = JSON.parse(raw);
|
|
422
|
-
if (typeof parsed.enabled === "boolean") {
|
|
423
|
-
enabled = parsed.enabled;
|
|
424
|
-
}
|
|
425
|
-
}
|
|
426
|
-
catch {
|
|
427
|
-
continue;
|
|
428
|
-
}
|
|
429
|
-
if (enabled) {
|
|
430
|
-
discovered.push(agentName);
|
|
431
|
-
}
|
|
432
|
-
}
|
|
433
|
-
return discovered.sort((left, right) => left.localeCompare(right));
|
|
601
|
+
return (0, agent_discovery_1.listEnabledBundleAgents)({
|
|
602
|
+
bundlesRoot: (0, identity_1.getAgentBundlesRoot)(),
|
|
603
|
+
readdirSync: fs.readdirSync,
|
|
604
|
+
readFileSync: fs.readFileSync,
|
|
605
|
+
});
|
|
434
606
|
}
|
|
435
607
|
async function defaultLinkFriendIdentity(command) {
|
|
436
|
-
const
|
|
608
|
+
const fp = path.join((0, identity_1.getAgentBundlesRoot)(), `${command.agent}.ouro`, "friends");
|
|
609
|
+
const friendStore = new store_file_1.FileFriendStore(fp);
|
|
437
610
|
const current = await friendStore.get(command.friendId);
|
|
438
611
|
if (!current) {
|
|
439
612
|
return `friend not found: ${command.friendId}`;
|
|
@@ -457,6 +630,247 @@ async function defaultLinkFriendIdentity(command) {
|
|
|
457
630
|
});
|
|
458
631
|
return `linked ${command.provider}:${command.externalId} to ${command.friendId}`;
|
|
459
632
|
}
|
|
633
|
+
function discoverExistingCredentials(secretsRoot) {
|
|
634
|
+
const found = [];
|
|
635
|
+
let entries;
|
|
636
|
+
try {
|
|
637
|
+
entries = fs.readdirSync(secretsRoot, { withFileTypes: true });
|
|
638
|
+
}
|
|
639
|
+
catch {
|
|
640
|
+
return found;
|
|
641
|
+
}
|
|
642
|
+
for (const entry of entries) {
|
|
643
|
+
if (!entry.isDirectory())
|
|
644
|
+
continue;
|
|
645
|
+
const secretsPath = path.join(secretsRoot, entry.name, "secrets.json");
|
|
646
|
+
let raw;
|
|
647
|
+
try {
|
|
648
|
+
raw = fs.readFileSync(secretsPath, "utf-8");
|
|
649
|
+
}
|
|
650
|
+
catch {
|
|
651
|
+
continue;
|
|
652
|
+
}
|
|
653
|
+
let parsed;
|
|
654
|
+
try {
|
|
655
|
+
parsed = JSON.parse(raw);
|
|
656
|
+
}
|
|
657
|
+
catch {
|
|
658
|
+
continue;
|
|
659
|
+
}
|
|
660
|
+
if (!parsed.providers)
|
|
661
|
+
continue;
|
|
662
|
+
for (const [provName, provConfig] of Object.entries(parsed.providers)) {
|
|
663
|
+
if (provName === "anthropic" && provConfig.setupToken) {
|
|
664
|
+
found.push({ agentName: entry.name, provider: "anthropic", credentials: { setupToken: provConfig.setupToken }, providerConfig: { ...provConfig } });
|
|
665
|
+
}
|
|
666
|
+
else if (provName === "openai-codex" && provConfig.oauthAccessToken) {
|
|
667
|
+
found.push({ agentName: entry.name, provider: "openai-codex", credentials: { oauthAccessToken: provConfig.oauthAccessToken }, providerConfig: { ...provConfig } });
|
|
668
|
+
}
|
|
669
|
+
else if (provName === "minimax" && provConfig.apiKey) {
|
|
670
|
+
found.push({ agentName: entry.name, provider: "minimax", credentials: { apiKey: provConfig.apiKey }, providerConfig: { ...provConfig } });
|
|
671
|
+
}
|
|
672
|
+
else if (provName === "azure" && provConfig.apiKey && provConfig.endpoint && provConfig.deployment) {
|
|
673
|
+
found.push({ agentName: entry.name, provider: "azure", credentials: { apiKey: provConfig.apiKey, endpoint: provConfig.endpoint, deployment: provConfig.deployment }, providerConfig: { ...provConfig } });
|
|
674
|
+
}
|
|
675
|
+
}
|
|
676
|
+
}
|
|
677
|
+
// Deduplicate by provider+credential value (keep first seen)
|
|
678
|
+
const seen = new Set();
|
|
679
|
+
return found.filter((cred) => {
|
|
680
|
+
const key = `${cred.provider}:${JSON.stringify(cred.credentials)}`;
|
|
681
|
+
if (seen.has(key))
|
|
682
|
+
return false;
|
|
683
|
+
seen.add(key);
|
|
684
|
+
return true;
|
|
685
|
+
});
|
|
686
|
+
}
|
|
687
|
+
/* v8 ignore start -- integration: interactive terminal specialist session @preserve */
|
|
688
|
+
async function defaultRunAdoptionSpecialist() {
|
|
689
|
+
const { runCliSession } = await Promise.resolve().then(() => __importStar(require("../../senses/cli")));
|
|
690
|
+
const { patchRuntimeConfig } = await Promise.resolve().then(() => __importStar(require("../config")));
|
|
691
|
+
const { setAgentName, setAgentConfigOverride } = await Promise.resolve().then(() => __importStar(require("../identity")));
|
|
692
|
+
const readlinePromises = await Promise.resolve().then(() => __importStar(require("readline/promises")));
|
|
693
|
+
const crypto = await Promise.resolve().then(() => __importStar(require("crypto")));
|
|
694
|
+
// Phase 1: cold CLI — collect provider/credentials with a simple readline
|
|
695
|
+
const coldRl = readlinePromises.createInterface({ input: process.stdin, output: process.stdout });
|
|
696
|
+
const coldPrompt = async (q) => {
|
|
697
|
+
const answer = await coldRl.question(q);
|
|
698
|
+
return answer.trim();
|
|
699
|
+
};
|
|
700
|
+
let providerRaw;
|
|
701
|
+
let credentials = {};
|
|
702
|
+
let providerConfig = {};
|
|
703
|
+
const tempDir = path.join(os.tmpdir(), `ouro-hatch-${crypto.randomUUID()}`);
|
|
704
|
+
try {
|
|
705
|
+
const secretsRoot = path.join(os.homedir(), ".agentsecrets");
|
|
706
|
+
const discovered = discoverExistingCredentials(secretsRoot);
|
|
707
|
+
const existingBundleCount = (0, specialist_orchestrator_1.listExistingBundles)((0, identity_1.getAgentBundlesRoot)()).length;
|
|
708
|
+
const hatchVerb = existingBundleCount > 0 ? "let's hatch a new agent." : "let's hatch your first agent.";
|
|
709
|
+
// Default models per provider (used when entering new credentials)
|
|
710
|
+
const defaultModels = {
|
|
711
|
+
anthropic: "claude-opus-4-6",
|
|
712
|
+
minimax: "MiniMax-Text-01",
|
|
713
|
+
"openai-codex": "gpt-5.4",
|
|
714
|
+
azure: "",
|
|
715
|
+
};
|
|
716
|
+
if (discovered.length > 0) {
|
|
717
|
+
process.stdout.write(`\n\ud83d\udc0d welcome to ouroboros! ${hatchVerb}\n`);
|
|
718
|
+
process.stdout.write("i found existing API credentials:\n\n");
|
|
719
|
+
const unique = [...new Map(discovered.map((d) => [`${d.provider}`, d])).values()];
|
|
720
|
+
for (let i = 0; i < unique.length; i++) {
|
|
721
|
+
const model = unique[i].providerConfig.model || unique[i].providerConfig.deployment || "";
|
|
722
|
+
const modelLabel = model ? `, ${model}` : "";
|
|
723
|
+
process.stdout.write(` ${i + 1}. ${unique[i].provider}${modelLabel} (from ${unique[i].agentName})\n`);
|
|
724
|
+
}
|
|
725
|
+
process.stdout.write("\n");
|
|
726
|
+
const choice = await coldPrompt("use one of these? enter number, or 'new' for a different key: ");
|
|
727
|
+
const idx = parseInt(choice, 10) - 1;
|
|
728
|
+
if (idx >= 0 && idx < unique.length) {
|
|
729
|
+
providerRaw = unique[idx].provider;
|
|
730
|
+
credentials = unique[idx].credentials;
|
|
731
|
+
providerConfig = unique[idx].providerConfig;
|
|
732
|
+
}
|
|
733
|
+
else {
|
|
734
|
+
const pRaw = await coldPrompt("provider (anthropic/azure/minimax/openai-codex): ");
|
|
735
|
+
if (!isAgentProvider(pRaw)) {
|
|
736
|
+
process.stdout.write("unknown provider. run `ouro hatch` to try again.\n");
|
|
737
|
+
coldRl.close();
|
|
738
|
+
return null;
|
|
739
|
+
}
|
|
740
|
+
providerRaw = pRaw;
|
|
741
|
+
providerConfig = { model: defaultModels[providerRaw] };
|
|
742
|
+
if (providerRaw === "anthropic")
|
|
743
|
+
credentials.setupToken = await coldPrompt("API key: ");
|
|
744
|
+
if (providerRaw === "openai-codex")
|
|
745
|
+
credentials.oauthAccessToken = await coldPrompt("OAuth token: ");
|
|
746
|
+
if (providerRaw === "minimax")
|
|
747
|
+
credentials.apiKey = await coldPrompt("API key: ");
|
|
748
|
+
if (providerRaw === "azure") {
|
|
749
|
+
credentials.apiKey = await coldPrompt("API key: ");
|
|
750
|
+
credentials.endpoint = await coldPrompt("endpoint: ");
|
|
751
|
+
credentials.deployment = await coldPrompt("deployment: ");
|
|
752
|
+
}
|
|
753
|
+
}
|
|
754
|
+
}
|
|
755
|
+
else {
|
|
756
|
+
process.stdout.write(`\n\ud83d\udc0d welcome to ouroboros! ${hatchVerb}\n`);
|
|
757
|
+
process.stdout.write("i need an API key to power our conversation.\n\n");
|
|
758
|
+
const pRaw = await coldPrompt("provider (anthropic/azure/minimax/openai-codex): ");
|
|
759
|
+
if (!isAgentProvider(pRaw)) {
|
|
760
|
+
process.stdout.write("unknown provider. run `ouro hatch` to try again.\n");
|
|
761
|
+
coldRl.close();
|
|
762
|
+
return null;
|
|
763
|
+
}
|
|
764
|
+
providerRaw = pRaw;
|
|
765
|
+
providerConfig = { model: defaultModels[providerRaw] };
|
|
766
|
+
if (providerRaw === "anthropic")
|
|
767
|
+
credentials.setupToken = await coldPrompt("API key: ");
|
|
768
|
+
if (providerRaw === "openai-codex")
|
|
769
|
+
credentials.oauthAccessToken = await coldPrompt("OAuth token: ");
|
|
770
|
+
if (providerRaw === "minimax")
|
|
771
|
+
credentials.apiKey = await coldPrompt("API key: ");
|
|
772
|
+
if (providerRaw === "azure") {
|
|
773
|
+
credentials.apiKey = await coldPrompt("API key: ");
|
|
774
|
+
credentials.endpoint = await coldPrompt("endpoint: ");
|
|
775
|
+
credentials.deployment = await coldPrompt("deployment: ");
|
|
776
|
+
}
|
|
777
|
+
}
|
|
778
|
+
coldRl.close();
|
|
779
|
+
process.stdout.write("\n");
|
|
780
|
+
// Phase 2: configure runtime for adoption specialist
|
|
781
|
+
const bundleSourceDir = path.resolve(__dirname, "..", "..", "..", "AdoptionSpecialist.ouro");
|
|
782
|
+
const bundlesRoot = (0, identity_1.getAgentBundlesRoot)();
|
|
783
|
+
const secretsRoot2 = path.join(os.homedir(), ".agentsecrets");
|
|
784
|
+
// Suppress non-critical log noise during adoption (no secrets.json, etc.)
|
|
785
|
+
const { setRuntimeLogger } = await Promise.resolve().then(() => __importStar(require("../../nerves/runtime")));
|
|
786
|
+
const { createLogger } = await Promise.resolve().then(() => __importStar(require("../../nerves")));
|
|
787
|
+
setRuntimeLogger(createLogger({ level: "error" }));
|
|
788
|
+
// Configure runtime: set agent identity + config override so runAgent
|
|
789
|
+
// doesn't try to read from ~/AgentBundles/AdoptionSpecialist.ouro/
|
|
790
|
+
setAgentName("AdoptionSpecialist");
|
|
791
|
+
// Build specialist system prompt
|
|
792
|
+
const soulText = (0, specialist_orchestrator_1.loadSoulText)(bundleSourceDir);
|
|
793
|
+
const identitiesDir = path.join(bundleSourceDir, "psyche", "identities");
|
|
794
|
+
const identity = (0, specialist_orchestrator_1.pickRandomIdentity)(identitiesDir);
|
|
795
|
+
// Load identity-specific spinner phrases (falls back to DEFAULT_AGENT_PHRASES)
|
|
796
|
+
const { loadIdentityPhrases } = await Promise.resolve().then(() => __importStar(require("./specialist-orchestrator")));
|
|
797
|
+
const phrases = loadIdentityPhrases(bundleSourceDir, identity.fileName);
|
|
798
|
+
setAgentConfigOverride({
|
|
799
|
+
version: 1,
|
|
800
|
+
enabled: true,
|
|
801
|
+
provider: providerRaw,
|
|
802
|
+
phrases,
|
|
803
|
+
});
|
|
804
|
+
patchRuntimeConfig({
|
|
805
|
+
providers: {
|
|
806
|
+
[providerRaw]: { ...providerConfig, ...credentials },
|
|
807
|
+
},
|
|
808
|
+
});
|
|
809
|
+
const existingBundles = (0, specialist_orchestrator_1.listExistingBundles)(bundlesRoot);
|
|
810
|
+
const systemPrompt = (0, specialist_prompt_1.buildSpecialistSystemPrompt)(soulText, identity.content, existingBundles, {
|
|
811
|
+
tempDir,
|
|
812
|
+
provider: providerRaw,
|
|
813
|
+
});
|
|
814
|
+
// Build specialist tools
|
|
815
|
+
const specialistTools = (0, specialist_tools_1.getSpecialistTools)();
|
|
816
|
+
const specialistExecTool = (0, specialist_tools_1.createSpecialistExecTool)({
|
|
817
|
+
tempDir,
|
|
818
|
+
credentials,
|
|
819
|
+
provider: providerRaw,
|
|
820
|
+
bundlesRoot,
|
|
821
|
+
secretsRoot: secretsRoot2,
|
|
822
|
+
animationWriter: (text) => process.stdout.write(text),
|
|
823
|
+
});
|
|
824
|
+
// Run the adoption specialist session via runCliSession
|
|
825
|
+
const result = await runCliSession({
|
|
826
|
+
agentName: "AdoptionSpecialist",
|
|
827
|
+
tools: specialistTools,
|
|
828
|
+
execTool: specialistExecTool,
|
|
829
|
+
exitOnToolCall: "complete_adoption",
|
|
830
|
+
autoFirstTurn: true,
|
|
831
|
+
banner: false,
|
|
832
|
+
disableCommands: true,
|
|
833
|
+
skipSystemPromptRefresh: true,
|
|
834
|
+
messages: [
|
|
835
|
+
{ role: "system", content: systemPrompt },
|
|
836
|
+
{ role: "user", content: "hi" },
|
|
837
|
+
],
|
|
838
|
+
});
|
|
839
|
+
if (result.exitReason === "tool_exit" && result.toolResult) {
|
|
840
|
+
const parsed = typeof result.toolResult === "string" ? JSON.parse(result.toolResult) : result.toolResult;
|
|
841
|
+
if (parsed.success && parsed.agentName) {
|
|
842
|
+
return parsed.agentName;
|
|
843
|
+
}
|
|
844
|
+
}
|
|
845
|
+
return null;
|
|
846
|
+
}
|
|
847
|
+
catch (err) {
|
|
848
|
+
process.stderr.write(`\nouro adoption error: ${err instanceof Error ? err.stack ?? err.message : String(err)}\n`);
|
|
849
|
+
coldRl.close();
|
|
850
|
+
return null;
|
|
851
|
+
}
|
|
852
|
+
finally {
|
|
853
|
+
// Clear specialist config/identity so the hatched agent gets its own
|
|
854
|
+
setAgentConfigOverride(null);
|
|
855
|
+
const { resetProviderRuntime } = await Promise.resolve().then(() => __importStar(require("../core")));
|
|
856
|
+
resetProviderRuntime();
|
|
857
|
+
const { resetConfigCache } = await Promise.resolve().then(() => __importStar(require("../config")));
|
|
858
|
+
resetConfigCache();
|
|
859
|
+
// Restore default logging
|
|
860
|
+
const { setRuntimeLogger: restoreLogger } = await Promise.resolve().then(() => __importStar(require("../../nerves/runtime")));
|
|
861
|
+
restoreLogger(null);
|
|
862
|
+
// Clean up temp dir if it still exists
|
|
863
|
+
try {
|
|
864
|
+
if (fs.existsSync(tempDir)) {
|
|
865
|
+
fs.rmSync(tempDir, { recursive: true, force: true });
|
|
866
|
+
}
|
|
867
|
+
}
|
|
868
|
+
catch {
|
|
869
|
+
// Best effort cleanup
|
|
870
|
+
}
|
|
871
|
+
}
|
|
872
|
+
}
|
|
873
|
+
/* v8 ignore stop */
|
|
460
874
|
function createDefaultOuroCliDeps(socketPath = "/tmp/ouroboros-daemon.sock") {
|
|
461
875
|
return {
|
|
462
876
|
socketPath,
|
|
@@ -471,7 +885,10 @@ function createDefaultOuroCliDeps(socketPath = "/tmp/ouroboros-daemon.sock") {
|
|
|
471
885
|
listDiscoveredAgents: defaultListDiscoveredAgents,
|
|
472
886
|
runHatchFlow: hatch_flow_1.runHatchFlow,
|
|
473
887
|
promptInput: defaultPromptInput,
|
|
888
|
+
runAdoptionSpecialist: defaultRunAdoptionSpecialist,
|
|
474
889
|
registerOuroBundleType: ouro_uti_1.registerOuroBundleUti,
|
|
890
|
+
installOuroCommand: ouro_path_installer_1.installOuroCommand,
|
|
891
|
+
syncGlobalOuroBotWrapper: ouro_bot_global_installer_1.syncGlobalOuroBotWrapper,
|
|
475
892
|
/* v8 ignore next 3 -- integration: launches interactive CLI session @preserve */
|
|
476
893
|
startChat: async (agentName) => {
|
|
477
894
|
const { main } = await Promise.resolve().then(() => __importStar(require("../../senses/cli")));
|
|
@@ -533,12 +950,63 @@ async function registerOuroBundleTypeNonBlocking(deps) {
|
|
|
533
950
|
});
|
|
534
951
|
}
|
|
535
952
|
}
|
|
953
|
+
async function performSystemSetup(deps) {
|
|
954
|
+
// Install ouro command to PATH (non-blocking)
|
|
955
|
+
if (deps.installOuroCommand) {
|
|
956
|
+
try {
|
|
957
|
+
deps.installOuroCommand();
|
|
958
|
+
}
|
|
959
|
+
catch (error) {
|
|
960
|
+
(0, runtime_1.emitNervesEvent)({
|
|
961
|
+
level: "warn",
|
|
962
|
+
component: "daemon",
|
|
963
|
+
event: "daemon.system_setup_ouro_cmd_error",
|
|
964
|
+
message: "failed to install ouro command to PATH",
|
|
965
|
+
meta: { error: error instanceof Error ? error.message : /* v8 ignore next -- defensive: non-Error catch branch @preserve */ String(error) },
|
|
966
|
+
});
|
|
967
|
+
}
|
|
968
|
+
}
|
|
969
|
+
if (deps.syncGlobalOuroBotWrapper) {
|
|
970
|
+
try {
|
|
971
|
+
await Promise.resolve(deps.syncGlobalOuroBotWrapper());
|
|
972
|
+
}
|
|
973
|
+
catch (error) {
|
|
974
|
+
(0, runtime_1.emitNervesEvent)({
|
|
975
|
+
level: "warn",
|
|
976
|
+
component: "daemon",
|
|
977
|
+
event: "daemon.system_setup_ouro_bot_wrapper_error",
|
|
978
|
+
message: "failed to sync global ouro.bot wrapper",
|
|
979
|
+
meta: { error: error instanceof Error ? error.message : /* v8 ignore next -- defensive: non-Error catch branch @preserve */ String(error) },
|
|
980
|
+
});
|
|
981
|
+
}
|
|
982
|
+
}
|
|
983
|
+
// Install subagents (claude/codex skills)
|
|
984
|
+
try {
|
|
985
|
+
await deps.installSubagents();
|
|
986
|
+
}
|
|
987
|
+
catch (error) {
|
|
988
|
+
(0, runtime_1.emitNervesEvent)({
|
|
989
|
+
level: "warn",
|
|
990
|
+
component: "daemon",
|
|
991
|
+
event: "daemon.subagent_install_error",
|
|
992
|
+
message: "subagent auto-install failed",
|
|
993
|
+
meta: { error: error instanceof Error ? error.message : /* v8 ignore next -- defensive: non-Error catch branch @preserve */ String(error) },
|
|
994
|
+
});
|
|
995
|
+
}
|
|
996
|
+
// Register .ouro bundle type (UTI on macOS)
|
|
997
|
+
await registerOuroBundleTypeNonBlocking(deps);
|
|
998
|
+
}
|
|
536
999
|
async function runOuroCli(args, deps = createDefaultOuroCliDeps()) {
|
|
537
1000
|
if (args.includes("--help") || args.includes("-h")) {
|
|
538
1001
|
const text = usage();
|
|
539
1002
|
deps.writeStdout(text);
|
|
540
1003
|
return text;
|
|
541
1004
|
}
|
|
1005
|
+
if (args.length === 1 && (args[0] === "-v" || args[0] === "--version")) {
|
|
1006
|
+
const text = formatVersionOutput();
|
|
1007
|
+
deps.writeStdout(text);
|
|
1008
|
+
return text;
|
|
1009
|
+
}
|
|
542
1010
|
let command;
|
|
543
1011
|
try {
|
|
544
1012
|
command = parseOuroCommand(args);
|
|
@@ -556,7 +1024,20 @@ async function runOuroCli(args, deps = createDefaultOuroCliDeps()) {
|
|
|
556
1024
|
}
|
|
557
1025
|
if (args.length === 0) {
|
|
558
1026
|
const discovered = await Promise.resolve(deps.listDiscoveredAgents ? deps.listDiscoveredAgents() : defaultListDiscoveredAgents());
|
|
559
|
-
if (discovered.length === 0) {
|
|
1027
|
+
if (discovered.length === 0 && deps.runAdoptionSpecialist) {
|
|
1028
|
+
// System setup first — ouro command, subagents, UTI — before the interactive specialist
|
|
1029
|
+
await performSystemSetup(deps);
|
|
1030
|
+
const hatchlingName = await deps.runAdoptionSpecialist();
|
|
1031
|
+
if (!hatchlingName) {
|
|
1032
|
+
return "";
|
|
1033
|
+
}
|
|
1034
|
+
await ensureDaemonRunning(deps);
|
|
1035
|
+
if (deps.startChat) {
|
|
1036
|
+
await deps.startChat(hatchlingName);
|
|
1037
|
+
}
|
|
1038
|
+
return "";
|
|
1039
|
+
}
|
|
1040
|
+
else if (discovered.length === 0) {
|
|
560
1041
|
command = { kind: "hatch.start" };
|
|
561
1042
|
}
|
|
562
1043
|
else if (discovered.length === 1) {
|
|
@@ -596,19 +1077,20 @@ async function runOuroCli(args, deps = createDefaultOuroCliDeps()) {
|
|
|
596
1077
|
meta: { kind: command.kind },
|
|
597
1078
|
});
|
|
598
1079
|
if (command.kind === "daemon.up") {
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
});
|
|
1080
|
+
await performSystemSetup(deps);
|
|
1081
|
+
// Run update hooks before starting daemon so user sees the output
|
|
1082
|
+
(0, update_hooks_1.registerUpdateHook)(bundle_meta_1.bundleMetaHook);
|
|
1083
|
+
const bundlesRoot = (0, identity_1.getAgentBundlesRoot)();
|
|
1084
|
+
const currentVersion = (0, bundle_manifest_1.getPackageVersion)();
|
|
1085
|
+
const updateSummary = await (0, update_hooks_1.applyPendingUpdates)(bundlesRoot, currentVersion);
|
|
1086
|
+
if (updateSummary.updated.length > 0) {
|
|
1087
|
+
const agents = updateSummary.updated.map((e) => e.agent);
|
|
1088
|
+
const from = updateSummary.updated[0].from;
|
|
1089
|
+
const to = updateSummary.updated[0].to;
|
|
1090
|
+
const fromStr = from ? ` (was ${from})` : "";
|
|
1091
|
+
const count = agents.length;
|
|
1092
|
+
deps.writeStdout(`updated ${count} agent${count === 1 ? "" : "s"} to runtime ${to}${fromStr}`);
|
|
610
1093
|
}
|
|
611
|
-
await registerOuroBundleTypeNonBlocking(deps);
|
|
612
1094
|
const daemonResult = await ensureDaemonRunning(deps);
|
|
613
1095
|
deps.writeStdout(daemonResult.message);
|
|
614
1096
|
return daemonResult.message;
|
|
@@ -624,6 +1106,21 @@ async function runOuroCli(args, deps = createDefaultOuroCliDeps()) {
|
|
|
624
1106
|
return message;
|
|
625
1107
|
}
|
|
626
1108
|
if (command.kind === "hatch.start") {
|
|
1109
|
+
// Route through adoption specialist when no explicit hatch args were provided
|
|
1110
|
+
const hasExplicitHatchArgs = !!(command.agentName || command.humanName || command.provider || command.credentials);
|
|
1111
|
+
if (deps.runAdoptionSpecialist && !hasExplicitHatchArgs) {
|
|
1112
|
+
// System setup first — ouro command, subagents, UTI — before the interactive specialist
|
|
1113
|
+
await performSystemSetup(deps);
|
|
1114
|
+
const hatchlingName = await deps.runAdoptionSpecialist();
|
|
1115
|
+
if (!hatchlingName) {
|
|
1116
|
+
return "";
|
|
1117
|
+
}
|
|
1118
|
+
await ensureDaemonRunning(deps);
|
|
1119
|
+
if (deps.startChat) {
|
|
1120
|
+
await deps.startChat(hatchlingName);
|
|
1121
|
+
}
|
|
1122
|
+
return "";
|
|
1123
|
+
}
|
|
627
1124
|
const hatchRunner = deps.runHatchFlow;
|
|
628
1125
|
if (!hatchRunner) {
|
|
629
1126
|
const response = await deps.sendCommand(deps.socketPath, { kind: "hatch.start" });
|
|
@@ -633,19 +1130,7 @@ async function runOuroCli(args, deps = createDefaultOuroCliDeps()) {
|
|
|
633
1130
|
}
|
|
634
1131
|
const hatchInput = await resolveHatchInput(command, deps);
|
|
635
1132
|
const result = await hatchRunner(hatchInput);
|
|
636
|
-
|
|
637
|
-
await deps.installSubagents();
|
|
638
|
-
}
|
|
639
|
-
catch (error) {
|
|
640
|
-
(0, runtime_1.emitNervesEvent)({
|
|
641
|
-
level: "warn",
|
|
642
|
-
component: "daemon",
|
|
643
|
-
event: "daemon.subagent_install_error",
|
|
644
|
-
message: "subagent auto-install failed",
|
|
645
|
-
meta: { error: error instanceof Error ? error.message : String(error) },
|
|
646
|
-
});
|
|
647
|
-
}
|
|
648
|
-
await registerOuroBundleTypeNonBlocking(deps);
|
|
1133
|
+
await performSystemSetup(deps);
|
|
649
1134
|
const daemonResult = await ensureDaemonRunning(deps);
|
|
650
1135
|
if (deps.startChat) {
|
|
651
1136
|
await deps.startChat(hatchInput.agentName);
|
|
@@ -667,9 +1152,22 @@ async function runOuroCli(args, deps = createDefaultOuroCliDeps()) {
|
|
|
667
1152
|
deps.writeStdout(message);
|
|
668
1153
|
return message;
|
|
669
1154
|
}
|
|
1155
|
+
if (command.kind === "daemon.status" && isDaemonUnavailableError(error)) {
|
|
1156
|
+
const message = daemonUnavailableStatusOutput(deps.socketPath);
|
|
1157
|
+
deps.writeStdout(message);
|
|
1158
|
+
return message;
|
|
1159
|
+
}
|
|
1160
|
+
if (command.kind === "daemon.stop" && isDaemonUnavailableError(error)) {
|
|
1161
|
+
const message = "daemon not running";
|
|
1162
|
+
deps.writeStdout(message);
|
|
1163
|
+
return message;
|
|
1164
|
+
}
|
|
670
1165
|
throw error;
|
|
671
1166
|
}
|
|
672
|
-
const
|
|
1167
|
+
const fallbackMessage = response.summary ?? response.message ?? (response.ok ? "ok" : `error: ${response.error ?? "unknown error"}`);
|
|
1168
|
+
const message = command.kind === "daemon.status"
|
|
1169
|
+
? formatDaemonStatusOutput(response, fallbackMessage)
|
|
1170
|
+
: fallbackMessage;
|
|
673
1171
|
deps.writeStdout(message);
|
|
674
1172
|
return message;
|
|
675
1173
|
}
|