@just-every/manager 0.2.0

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.
@@ -0,0 +1,38 @@
1
+ var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
2
+ get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
3
+ }) : x)(function(x) {
4
+ if (typeof require !== "undefined") return require.apply(this, arguments);
5
+ throw Error('Dynamic require of "' + x + '" is not supported');
6
+ });
7
+
8
+ // src/constants.ts
9
+ import os from "os";
10
+ import path from "path";
11
+ var DEFAULT_HOST = "127.0.0.1";
12
+ var DEFAULT_PORT = Number(process.env.MANAGERD_PORT ?? 7765);
13
+ var MANAGERD_HOME = (() => {
14
+ if (process.env.MANAGERD_HOME) {
15
+ return path.resolve(process.env.MANAGERD_HOME);
16
+ }
17
+ return path.join(os.homedir(), ".managerd");
18
+ })();
19
+ var DATA_DIRS = {
20
+ root: MANAGERD_HOME,
21
+ logs: path.join(MANAGERD_HOME, "logs"),
22
+ sessions: path.join(MANAGERD_HOME, "sessions")
23
+ };
24
+ var DB_PATH = path.join(MANAGERD_HOME, "state.db");
25
+ var PID_FILE = path.join(MANAGERD_HOME, "managerd.pid");
26
+ var DEFAULT_CODEX_HISTORY = path.join(os.homedir(), ".codex", "sessions");
27
+ var DEFAULT_CODEX_TRANSCRIPT = "output.json";
28
+
29
+ export {
30
+ __require,
31
+ DEFAULT_HOST,
32
+ DEFAULT_PORT,
33
+ DATA_DIRS,
34
+ DB_PATH,
35
+ PID_FILE,
36
+ DEFAULT_CODEX_HISTORY,
37
+ DEFAULT_CODEX_TRANSCRIPT
38
+ };
package/dist/cli.js ADDED
@@ -0,0 +1,593 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ DATA_DIRS,
4
+ DEFAULT_HOST,
5
+ DEFAULT_PORT,
6
+ PID_FILE
7
+ } from "./chunk-ZQKKKD3J.js";
8
+
9
+ // src/cli.ts
10
+ import { Command } from "commander";
11
+ import chalk from "chalk";
12
+ import fetch from "node-fetch";
13
+ import fs3 from "fs/promises";
14
+ import fsSync from "fs";
15
+ import path3 from "path";
16
+ import os3 from "os";
17
+ import { fileURLToPath } from "url";
18
+
19
+ // src/integrations/sync.ts
20
+ import fs from "fs";
21
+ import path from "path";
22
+ import os from "os";
23
+ import { spawnSync } from "child_process";
24
+ var home = os.homedir();
25
+ async function runIntegrationSync(agent) {
26
+ switch (agent) {
27
+ case "codex":
28
+ return syncCodex();
29
+ case "claude":
30
+ return syncClaude();
31
+ case "gemini":
32
+ return syncGemini();
33
+ default:
34
+ throw new Error(`Unknown integration agent: ${agent}`);
35
+ }
36
+ }
37
+ function syncCodex() {
38
+ const checks = [];
39
+ const manualSteps = [];
40
+ const command = process.env.MANAGER_CODEX_COMMAND ?? "codex";
41
+ const historyDir = process.env.MANAGER_CODEX_HISTORY ?? path.join(home, ".codex", "sessions");
42
+ const configPath = path.join(home, ".codex", "config.json");
43
+ const cmdPath = resolveCommand(command);
44
+ if (cmdPath) {
45
+ checks.push({ name: `Codex binary (${command})`, status: "ok", detail: cmdPath });
46
+ } else {
47
+ checks.push({ name: `Codex binary (${command})`, status: "missing", detail: "not found on PATH" });
48
+ }
49
+ if (fs.existsSync(historyDir)) {
50
+ checks.push({ name: "History directory", status: "ok", detail: historyDir });
51
+ } else {
52
+ checks.push({ name: "History directory", status: "missing", detail: historyDir });
53
+ manualSteps.push(`Create the history directory: mkdir -p ${historyDir}`);
54
+ }
55
+ if (fs.existsSync(configPath)) {
56
+ try {
57
+ const raw = fs.readFileSync(configPath, "utf8");
58
+ const config = JSON.parse(raw);
59
+ const configuredHistory = config.history_dir;
60
+ if (configuredHistory === historyDir) {
61
+ checks.push({ name: "Codex history_dir", status: "ok", detail: configuredHistory });
62
+ } else {
63
+ checks.push({
64
+ name: "Codex history_dir",
65
+ status: "warn",
66
+ detail: `Config uses ${configuredHistory || "unset"}`
67
+ });
68
+ manualSteps.push(`Update ${configPath} -> history_dir = "${historyDir}"`);
69
+ }
70
+ } catch (err) {
71
+ checks.push({ name: "Codex config parse", status: "warn", detail: err.message });
72
+ manualSteps.push(`Fix JSON in ${configPath} so manager can verify settings.`);
73
+ }
74
+ } else {
75
+ checks.push({ name: "Codex config file", status: "missing", detail: configPath });
76
+ manualSteps.push(`Create ${configPath} with { "history_dir": "${historyDir}" }`);
77
+ }
78
+ const status = determineStatus(checks, cmdPath ? "ready" : "not_detected");
79
+ return { agent: "codex", status, checks, manualSteps };
80
+ }
81
+ function syncClaude() {
82
+ const checks = [];
83
+ const manualSteps = [];
84
+ const configDir = path.join(home, ".config", "claude");
85
+ const hookPath = path.join(configDir, "hooks", "on_session_start");
86
+ if (fs.existsSync(configDir)) {
87
+ checks.push({ name: "Claude config directory", status: "ok", detail: configDir });
88
+ } else {
89
+ checks.push({ name: "Claude config directory", status: "missing", detail: configDir });
90
+ manualSteps.push(`Create ${configDir} (Claude Code stores hooks here).`);
91
+ }
92
+ if (fs.existsSync(hookPath)) {
93
+ const content = fs.readFileSync(hookPath, "utf8");
94
+ if (content.includes("manager")) {
95
+ checks.push({ name: "Hook script", status: "ok", detail: hookPath });
96
+ } else {
97
+ checks.push({ name: "Hook script", status: "warn", detail: "does not reference manager CLI" });
98
+ manualSteps.push(`Update ${hookPath} to forward events to manager (see docs/manager-daemon-cli.md).`);
99
+ }
100
+ } else {
101
+ checks.push({ name: "Hook script", status: "missing", detail: hookPath });
102
+ manualSteps.push(`Create a hook script at ${hookPath} that runs manager's Claude hook helper.`);
103
+ }
104
+ return { agent: "claude", status: determineStatus(checks), checks, manualSteps };
105
+ }
106
+ function syncGemini() {
107
+ const checks = [];
108
+ const manualSteps = [];
109
+ const geminiDir = path.join(home, ".gemini");
110
+ const configPath = path.join(geminiDir, "config.toml");
111
+ const desiredEndpoint = process.env.MANAGER_GEMINI_OTLP ?? "http://127.0.0.1:4318";
112
+ if (fs.existsSync(geminiDir)) {
113
+ checks.push({ name: "Gemini directory", status: "ok", detail: geminiDir });
114
+ } else {
115
+ checks.push({ name: "Gemini directory", status: "missing", detail: geminiDir });
116
+ manualSteps.push("Install Gemini CLI and log in so ~/.gemini is created.");
117
+ }
118
+ if (fs.existsSync(configPath)) {
119
+ const text = fs.readFileSync(configPath, "utf8");
120
+ const enabled = /telemetry\.enabled\s*=\s*true/.test(text);
121
+ const endpointMatch = text.includes(desiredEndpoint);
122
+ checks.push({
123
+ name: "Telemetry enabled",
124
+ status: enabled ? "ok" : "warn",
125
+ detail: enabled ? "enabled" : "telemetry.enabled not true"
126
+ });
127
+ checks.push({
128
+ name: "OTLP endpoint",
129
+ status: endpointMatch ? "ok" : "warn",
130
+ detail: endpointMatch ? desiredEndpoint : "endpoint not set to manager"
131
+ });
132
+ if (!enabled || !endpointMatch) {
133
+ manualSteps.push(
134
+ `Edit ${configPath} telemetry section so exporter targets ${desiredEndpoint}.`
135
+ );
136
+ }
137
+ } else {
138
+ checks.push({ name: "Gemini config file", status: "missing", detail: configPath });
139
+ manualSteps.push(`Create ${configPath} and set telemetry endpoint to ${desiredEndpoint}.`);
140
+ }
141
+ return { agent: "gemini", status: determineStatus(checks), checks, manualSteps };
142
+ }
143
+ function determineStatus(checks, fallback = "ready") {
144
+ if (checks.some((check) => check.status === "missing")) {
145
+ return fallback === "not_detected" && checks.every((c) => c.status === "missing") ? "not_detected" : "partial";
146
+ }
147
+ if (checks.some((check) => check.status === "warn")) {
148
+ return "partial";
149
+ }
150
+ return fallback === "not_detected" ? "not_detected" : "ready";
151
+ }
152
+ function resolveCommand(command) {
153
+ const resolved = spawnSync("which", [command], { encoding: "utf8" });
154
+ if (resolved.status === 0) {
155
+ return resolved.stdout.trim();
156
+ }
157
+ return null;
158
+ }
159
+
160
+ // src/integrations/install.ts
161
+ import os2 from "os";
162
+ import path2 from "path";
163
+ import fs2 from "fs/promises";
164
+ import { promisify } from "util";
165
+ import { exec as execCb } from "child_process";
166
+ var exec = promisify(execCb);
167
+ async function installIntegration(agent) {
168
+ switch (agent) {
169
+ case "claude":
170
+ return installClaudeHook();
171
+ case "gemini":
172
+ return installGeminiWatcher();
173
+ case "codex":
174
+ return "Codex integration does not require installation; history dir is created automatically.";
175
+ default:
176
+ throw new Error(`Unknown agent ${agent}`);
177
+ }
178
+ }
179
+ async function installClaudeHook() {
180
+ const configDir = process.env.CLAUDE_CONFIG_DIR ?? path2.join(os2.homedir(), ".config", "claude");
181
+ const hookDir = path2.join(configDir, "hooks");
182
+ const scriptPath = path2.join(hookDir, "managerd-hook.sh");
183
+ await fs2.mkdir(hookDir, { recursive: true });
184
+ const script = `#!/usr/bin/env bash
185
+ set -euo pipefail
186
+ payload="$(cat)"
187
+ if [ -z "$payload" ]; then
188
+ exit 0
189
+ fi
190
+ url="${process.env.MANAGERD_HOOK_URL ?? "http://127.0.0.1:7765/hooks/claude"}"
191
+ curl -fsS -X POST "$url" -H 'content-type: application/json' -d "$payload" >/dev/null 2>&1 || true
192
+ `;
193
+ await fs2.writeFile(scriptPath, script, { mode: 493 });
194
+ return `Hook script installed to ${scriptPath}. Point Claude Code hooks at this script to stream events to managerd.`;
195
+ }
196
+ async function installGeminiWatcher() {
197
+ const sessionsDir = path2.join(os2.homedir(), ".gemini", "tmp");
198
+ await fs2.mkdir(sessionsDir, { recursive: true });
199
+ return `Gemini watcher enabled (managerd scans ${sessionsDir}). Ensure Gemini CLI writes session files there.`;
200
+ }
201
+
202
+ // src/cli.ts
203
+ var program = new Command();
204
+ program.name("manager").description("Supervise local AI agents with Manager");
205
+ var daemonCommand = program.command("daemon").description("Manage the managerd background service");
206
+ daemonCommand.command("start").description("Run managerd in the foreground").action(async () => {
207
+ const { startDaemon } = await import("./daemon-DTD73K4H.js");
208
+ await startDaemon();
209
+ });
210
+ daemonCommand.command("status").description("Check daemon health").option("--host <host>", "Daemon host override", DEFAULT_HOST).option("--port <port>", "Daemon port override", String(DEFAULT_PORT)).action(async (opts) => {
211
+ const result = await queryDaemon("/status", opts.host, Number(opts.port));
212
+ if (!result.ok) {
213
+ console.error(chalk.red("Daemon unavailable:"), result.error ?? "unknown");
214
+ process.exitCode = 1;
215
+ return;
216
+ }
217
+ console.log(chalk.green("managerd running"));
218
+ console.log("Uptime:", Math.round(result.uptimeMs / 1e3), "s");
219
+ console.log("Codex connector:", result.codex);
220
+ console.log("Recent sessions:");
221
+ for (const session of result.sessions ?? []) {
222
+ console.log(` - ${session.id} [${session.agentType}] ${session.status}`);
223
+ }
224
+ });
225
+ daemonCommand.command("stop").description("Stop a running managerd instance").action(async () => {
226
+ const pid = await readPidFile();
227
+ if (!pid) {
228
+ console.log("managerd is not running.");
229
+ return;
230
+ }
231
+ try {
232
+ process.kill(pid, "SIGTERM");
233
+ console.log(`Sent SIGTERM to managerd (pid ${pid}).`);
234
+ } catch (err) {
235
+ console.error(chalk.red("Failed to stop managerd:"), err?.message ?? err);
236
+ }
237
+ });
238
+ daemonCommand.command("logs").description("Show managerd logs").option("--follow", "Stream logs until interrupted").action(async (opts) => {
239
+ await tailLogs(Boolean(opts.follow));
240
+ });
241
+ daemonCommand.command("install").description("Install managerd as a user-level systemd service").action(async () => {
242
+ await installSystemdService();
243
+ });
244
+ var agentCommand = program.command("agent").description("Interact with local agents via managerd");
245
+ agentCommand.command("launch <agent>").description("Launch an agent session through the daemon").option("--cmd <path>", "Override executable path").option("--arg <value...>", "Arguments passed to the agent").option("--raw", "Skip automatic Codex flag injection (testing only)").option("--max-restarts <n>", "Maximum automatic restarts (default: 3)", "3").option("--no-auto-restart", "Disable automatic restarts").option("--host <host>", "Daemon host override", DEFAULT_HOST).option("--port <port>", "Daemon port override", String(DEFAULT_PORT)).action(async (agent, opts) => {
246
+ if (!["codex", "gemini"].includes(agent)) {
247
+ console.error(chalk.red("Supported agents: codex, gemini"));
248
+ process.exitCode = 1;
249
+ return;
250
+ }
251
+ const maxRestarts = Number(opts.maxRestarts ?? 3);
252
+ const payload = {
253
+ agent,
254
+ command: opts.cmd,
255
+ args: opts.arg ?? [],
256
+ autoRestart: opts.autoRestart !== false,
257
+ maxRestarts: Number.isFinite(maxRestarts) ? maxRestarts : 3
258
+ };
259
+ if (agent === "codex") {
260
+ payload.disableManagedFlags = Boolean(opts.raw);
261
+ }
262
+ const response = await callDaemon("/rpc/launch", payload, opts.host, Number(opts.port));
263
+ if (!response.ok) {
264
+ console.error(chalk.red("Launch failed:"), response.error ?? "unknown");
265
+ process.exitCode = 1;
266
+ return;
267
+ }
268
+ const { result } = response;
269
+ console.log(chalk.green(`${agent} session launched`));
270
+ console.log("Local session ID:", result.localSessionId);
271
+ console.log("Tool session ID:", result.toolSessionId);
272
+ console.log("Command:", `${result.command} ${result.args.join(" ")}`);
273
+ });
274
+ agentCommand.command("ingest-gemini <sessionFile>").description("Ingest a Gemini CLI session JSON file into managerd").option("--session-id <id>", "Override Gemini session id").option("--host <host>", "Daemon host override", DEFAULT_HOST).option("--port <port>", "Daemon port override", String(DEFAULT_PORT)).action(async (sessionFile, opts) => {
275
+ const absolute = path3.resolve(sessionFile);
276
+ let contents;
277
+ try {
278
+ contents = await fs3.readFile(absolute, "utf8");
279
+ } catch (err) {
280
+ console.error(chalk.red("Unable to read session file:"), err?.message ?? err);
281
+ process.exitCode = 1;
282
+ return;
283
+ }
284
+ let data;
285
+ try {
286
+ data = JSON.parse(contents);
287
+ } catch (err) {
288
+ console.error(chalk.red("Session file is not valid JSON:"), err?.message ?? err);
289
+ process.exitCode = 1;
290
+ return;
291
+ }
292
+ const sessionId = opts.sessionId ?? data.sessionId ?? data.id ?? inferSessionIdFromPath(absolute);
293
+ const payload = {
294
+ sessionId,
295
+ filePath: absolute,
296
+ data
297
+ };
298
+ const response = await callDaemon("/rpc/gemini/ingest", payload, opts.host, Number(opts.port));
299
+ if (!response.ok) {
300
+ console.error(chalk.red("Gemini ingestion failed:"), response.error ?? "unknown");
301
+ process.exitCode = 1;
302
+ return;
303
+ }
304
+ console.log(chalk.green("Gemini session ingested"));
305
+ if (response.inserted != null) {
306
+ console.log("Events inserted:", response.inserted);
307
+ }
308
+ if (response.warnings?.length) {
309
+ console.warn(chalk.yellow("Warnings:"));
310
+ response.warnings.forEach((warning) => console.warn(" -", warning));
311
+ }
312
+ });
313
+ program.command("sessions").description("List locally tracked sessions").option("--host <host>", "Daemon host override", DEFAULT_HOST).option("--port <port>", "Daemon port override", String(DEFAULT_PORT)).action(async (opts) => {
314
+ const result = await queryDaemon("/sessions", opts.host, Number(opts.port));
315
+ if (!result.ok) {
316
+ console.error(chalk.red("Unable to read sessions:"), result.error ?? "unknown");
317
+ process.exitCode = 1;
318
+ return;
319
+ }
320
+ if (!result.sessions?.length) {
321
+ console.log("No sessions recorded yet.");
322
+ return;
323
+ }
324
+ const rows = result.sessions.map((session) => ({
325
+ id: session.id.slice(0, 8),
326
+ agent: session.agentType,
327
+ status: session.status,
328
+ command: session.command,
329
+ started: formatDate(session.createdAt ?? session.created_at),
330
+ updated: formatDate(session.updatedAt ?? session.updated_at)
331
+ }));
332
+ const header = ["ID", "Agent", "Status", "Started", "Updated", "Command"];
333
+ const lines = [header, ...rows.map((row) => [row.id, row.agent, row.status, row.started, row.updated, row.command])];
334
+ const widths = header.map((_, index) => Math.max(...lines.map((line) => String(line[index] ?? "").length)));
335
+ for (const line of lines) {
336
+ const formatted = line.map((value, idx) => String(value ?? "").padEnd(widths[idx])).join(" ");
337
+ console.log(formatted);
338
+ }
339
+ });
340
+ program.command("sessions:events <sessionId>").description("View events captured for a session (ingests Codex transcripts on demand)").option("--host <host>", "Daemon host override", DEFAULT_HOST).option("--port <port>", "Daemon port override", String(DEFAULT_PORT)).option("--limit <n>", "Limit number of events", "20").option("--json", "Print raw JSON").action(async (sessionId, opts) => {
341
+ const payload = {
342
+ sessionId,
343
+ limit: Number(opts.limit ?? 20)
344
+ };
345
+ const response = await callDaemon("/rpc/sessions/events", payload, opts.host, Number(opts.port));
346
+ if (!response.ok) {
347
+ console.error(chalk.red("Failed to fetch events:"), response.error ?? "unknown");
348
+ process.exitCode = 1;
349
+ return;
350
+ }
351
+ const { events, warnings, session } = response;
352
+ if (session) {
353
+ console.log(chalk.bold(`Session ${session.id}`));
354
+ console.log(`Agent: ${session.agentType}`);
355
+ if (session.command) {
356
+ console.log(`Command: ${session.command}`);
357
+ }
358
+ console.log("Started:", formatDate(session.createdAt ?? session.created_at));
359
+ console.log("Status:", session.status);
360
+ console.log("");
361
+ }
362
+ if (warnings?.length) {
363
+ console.warn(chalk.yellow("Warnings:"));
364
+ for (const warning of warnings) {
365
+ console.warn("-", warning);
366
+ }
367
+ }
368
+ if (opts.json) {
369
+ console.log(JSON.stringify(events, null, 2));
370
+ return;
371
+ }
372
+ if (!events.length) {
373
+ console.log("No events recorded yet.");
374
+ return;
375
+ }
376
+ for (const event of events) {
377
+ renderEvent(event);
378
+ }
379
+ });
380
+ var integrationsCommand = program.command("integrations").description("Inspect agent integrations");
381
+ integrationsCommand.command("sync <agent>").description("Check local integration status (read-only)").option("--json", "Output JSON report").action(async (agent, opts) => {
382
+ if (!["codex", "claude", "gemini"].includes(agent)) {
383
+ console.error(chalk.red("Supported agents: codex, claude, gemini"));
384
+ process.exitCode = 1;
385
+ return;
386
+ }
387
+ try {
388
+ const report = await runIntegrationSync(agent);
389
+ if (opts.json) {
390
+ console.log(JSON.stringify(report, null, 2));
391
+ return;
392
+ }
393
+ renderIntegrationReport(report);
394
+ } catch (err) {
395
+ console.error(chalk.red("Failed to run sync:"), err.message ?? err);
396
+ process.exitCode = 1;
397
+ }
398
+ });
399
+ integrationsCommand.command("install <agent>").description("Install helper assets for an integration").action(async (agent) => {
400
+ if (!["codex", "claude", "gemini"].includes(agent)) {
401
+ console.error(chalk.red("Supported agents: codex, claude, gemini"));
402
+ process.exitCode = 1;
403
+ return;
404
+ }
405
+ try {
406
+ const message = await installIntegration(agent);
407
+ console.log(chalk.green("Install complete:"), message);
408
+ } catch (err) {
409
+ console.error(chalk.red("Install failed:"), err?.message ?? err);
410
+ process.exitCode = 1;
411
+ }
412
+ });
413
+ program.parseAsync(process.argv);
414
+ async function queryDaemon(pathname, host = DEFAULT_HOST, port = DEFAULT_PORT) {
415
+ try {
416
+ const res = await fetch(`http://${host}:${port}${pathname}`);
417
+ if (!res.ok) return { ok: false, error: `HTTP ${res.status}` };
418
+ return await res.json();
419
+ } catch (err) {
420
+ return { ok: false, error: err?.message ?? "connection_error" };
421
+ }
422
+ }
423
+ async function callDaemon(pathname, payload, host = DEFAULT_HOST, port = DEFAULT_PORT) {
424
+ try {
425
+ const res = await fetch(`http://${host}:${port}${pathname}`, {
426
+ method: "POST",
427
+ body: JSON.stringify(payload),
428
+ headers: { "content-type": "application/json" }
429
+ });
430
+ if (!res.ok) return { ok: false, error: `HTTP ${res.status}` };
431
+ return await res.json();
432
+ } catch (err) {
433
+ return { ok: false, error: err?.message ?? "connection_error" };
434
+ }
435
+ }
436
+ function renderEvent(event) {
437
+ const time = event.timestamp ? new Date(event.timestamp).toLocaleTimeString() : "unknown time";
438
+ const header = `[${time}] ${event.role ? event.role.toUpperCase() : event.eventType}`;
439
+ if (event.eventType === "message") {
440
+ console.log(header);
441
+ console.log(` ${event.text ?? event.summary ?? JSON.stringify(event.content)}`);
442
+ return;
443
+ }
444
+ if (event.eventType === "tool_use") {
445
+ console.log(`${header} TOOL ${event.toolName ?? ""}`.trim());
446
+ console.log(` Input: ${truncate(event.toolInput ?? "", 200)}`);
447
+ return;
448
+ }
449
+ if (event.eventType === "tool_result") {
450
+ console.log(`${header} TOOL RESULT`);
451
+ console.log(` Output: ${truncate(event.toolOutput ?? "", 200)}`);
452
+ return;
453
+ }
454
+ if (event.eventType === "error") {
455
+ console.log(`${header} ERROR ${event.errorType ?? ""}`.trim());
456
+ console.log(` ${event.errorMessage}`);
457
+ return;
458
+ }
459
+ console.log(header);
460
+ console.log(" ", JSON.stringify(event));
461
+ }
462
+ function truncate(value, max = 200) {
463
+ if (!value) return "";
464
+ return value.length > max ? `${value.slice(0, max)}\u2026` : value;
465
+ }
466
+ function inferSessionIdFromPath(filePath) {
467
+ const base = path3.basename(filePath);
468
+ return base.replace(/\.[^.]+$/, "") || `gemini-${Date.now()}`;
469
+ }
470
+ function formatDate(value) {
471
+ if (!value) return "-";
472
+ const date = new Date(value);
473
+ if (Number.isNaN(date.getTime())) return "-";
474
+ return date.toLocaleString();
475
+ }
476
+ async function readPidFile() {
477
+ try {
478
+ const contents = await fs3.readFile(PID_FILE, "utf8");
479
+ const pid = Number(contents.trim());
480
+ return Number.isFinite(pid) ? pid : null;
481
+ } catch {
482
+ return null;
483
+ }
484
+ }
485
+ async function tailLogs(follow) {
486
+ const logPath = path3.join(DATA_DIRS.logs, "managerd.log");
487
+ try {
488
+ await fs3.access(logPath);
489
+ } catch {
490
+ console.log("Logs not found yet. Run managerd once to create logs.");
491
+ return;
492
+ }
493
+ const printCurrent = async () => {
494
+ const text = await fs3.readFile(logPath, "utf8");
495
+ if (text) process.stdout.write(text);
496
+ };
497
+ if (!follow) {
498
+ await printCurrent();
499
+ return;
500
+ }
501
+ let position = 0;
502
+ const stats = await fs3.stat(logPath);
503
+ if (stats.size > 0) {
504
+ await printCurrent();
505
+ position = stats.size;
506
+ }
507
+ console.log(chalk.gray("--- streaming managerd logs (Ctrl+C to stop) ---"));
508
+ const watcher = fsSync.watch(logPath, async (event) => {
509
+ if (event !== "change") return;
510
+ try {
511
+ const handle = await fs3.open(logPath, "r");
512
+ const current = await handle.stat();
513
+ if (current.size > position) {
514
+ const length = current.size - position;
515
+ const buffer = Buffer.alloc(length);
516
+ await handle.read(buffer, 0, length, position);
517
+ process.stdout.write(buffer.toString());
518
+ position = current.size;
519
+ }
520
+ await handle.close();
521
+ } catch (err) {
522
+ console.error(chalk.red("Failed to read logs:"), err instanceof Error ? err.message : err);
523
+ }
524
+ });
525
+ const cleanup = () => watcher.close();
526
+ process.on("SIGINT", () => {
527
+ cleanup();
528
+ process.exit(0);
529
+ });
530
+ process.on("SIGTERM", () => {
531
+ cleanup();
532
+ process.exit(0);
533
+ });
534
+ }
535
+ async function installSystemdService() {
536
+ const serviceDir = path3.join(os3.homedir(), ".config", "systemd", "user");
537
+ await fs3.mkdir(serviceDir, { recursive: true });
538
+ const servicePath = path3.join(serviceDir, "managerd.service");
539
+ const execStart = `${process.execPath} ${getManagerdEntry()}`;
540
+ const workingDir = path3.dirname(getManagerdEntry());
541
+ const unit = `[Unit]
542
+ Description=Justevery Manager Daemon
543
+ After=network.target
544
+
545
+ [Service]
546
+ ExecStart=${execStart}
547
+ Restart=on-failure
548
+ Environment=MANAGERD_STORAGE=memory
549
+ WorkingDirectory=${workingDir}
550
+
551
+ [Install]
552
+ WantedBy=default.target
553
+ `;
554
+ await fs3.writeFile(servicePath, unit, "utf8");
555
+ console.log("Installed systemd service to", servicePath);
556
+ console.log("Run the following commands to enable it:");
557
+ console.log(" systemctl --user daemon-reload");
558
+ console.log(" systemctl --user enable --now managerd.service");
559
+ }
560
+ function getManagerdEntry() {
561
+ const here = path3.dirname(fileURLToPath(import.meta.url));
562
+ return path3.resolve(here, "managerd.js");
563
+ }
564
+ function renderIntegrationReport(report) {
565
+ console.log(chalk.bold(`Integration: ${report.agent}`));
566
+ console.log("Status:", report.status);
567
+ console.log("\nChecks:");
568
+ for (const check of report.checks) {
569
+ const prefix = statusIcon(check.status);
570
+ const detail = check.detail ? ` \u2014 ${check.detail}` : "";
571
+ console.log(` ${prefix} ${check.name}${detail}`);
572
+ }
573
+ if (report.manualSteps.length) {
574
+ console.log("\nManual steps:");
575
+ report.manualSteps.forEach((step, idx) => {
576
+ console.log(` ${idx + 1}. ${step}`);
577
+ });
578
+ } else {
579
+ console.log("\nNo manual steps required.");
580
+ }
581
+ }
582
+ function statusIcon(status) {
583
+ switch (status) {
584
+ case "ok":
585
+ return chalk.green("\u2714");
586
+ case "missing":
587
+ return chalk.red("\u2716");
588
+ case "warn":
589
+ return chalk.yellow("!");
590
+ default:
591
+ return chalk.gray("-");
592
+ }
593
+ }
@@ -0,0 +1,7 @@
1
+ import {
2
+ startDaemon
3
+ } from "./chunk-KD5PYPXI.js";
4
+ import "./chunk-ZQKKKD3J.js";
5
+ export {
6
+ startDaemon
7
+ };
@@ -0,0 +1,11 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ startDaemon
4
+ } from "./chunk-KD5PYPXI.js";
5
+ import "./chunk-ZQKKKD3J.js";
6
+
7
+ // src/managerd.ts
8
+ startDaemon().catch((err) => {
9
+ console.error("Failed to start managerd", err);
10
+ process.exitCode = 1;
11
+ });
package/package.json ADDED
@@ -0,0 +1,39 @@
1
+ {
2
+ "name": "@just-every/manager",
3
+ "version": "0.2.0",
4
+ "private": false,
5
+ "type": "module",
6
+ "bin": {
7
+ "manager": "dist/cli.js",
8
+ "managerd": "dist/managerd.js"
9
+ },
10
+ "scripts": {
11
+ "build": "tsup src/cli.ts src/managerd.ts --format esm --target node20 --out-dir dist",
12
+ "dev": "tsx src/cli.ts",
13
+ "dev:daemon": "tsx src/managerd.ts",
14
+ "test": "vitest run",
15
+ "test:watch": "vitest"
16
+ },
17
+ "dependencies": {
18
+ "better-sqlite3": "^11.5.0",
19
+ "chalk": "^5.3.0",
20
+ "commander": "^14.0.0",
21
+ "execa": "^9.4.0",
22
+ "node-fetch": "^3.3.2",
23
+ "pino": "^9.5.0",
24
+ "pino-pretty": "^11.2.2",
25
+ "tiny-invariant": "^1.3.3",
26
+ "tslib": "^2.8.1",
27
+ "uuid": "^11.0.3"
28
+ },
29
+ "devDependencies": {
30
+ "@types/better-sqlite3": "^7.6.11",
31
+ "@types/node": "^22.7.11",
32
+ "@types/pino-pretty": "^5.0.0",
33
+ "@types/uuid": "^10.0.0",
34
+ "tsup": "^8.3.0",
35
+ "tsx": "^4.20.6",
36
+ "typescript": "^5.6.3",
37
+ "vitest": "^2.1.8"
38
+ }
39
+ }