monty-cli 0.1.2 → 0.1.4
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/monty.js +106 -1
- package/package.json +1 -1
package/monty.js
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
const fs = require("node:fs");
|
|
5
5
|
const os = require("node:os");
|
|
6
6
|
const path = require("node:path");
|
|
7
|
-
const { spawnSync } = require("node:child_process");
|
|
7
|
+
const { spawnSync, execSync } = require("node:child_process");
|
|
8
8
|
|
|
9
9
|
const home = os.homedir();
|
|
10
10
|
const montyDir = path.join(home, ".monty");
|
|
@@ -22,6 +22,7 @@ async function main() {
|
|
|
22
22
|
if (command === "hook") return hookCommandHandler(args);
|
|
23
23
|
if (command === "run") return runWrapped(args);
|
|
24
24
|
if (command === "sync") return syncHistory(args);
|
|
25
|
+
if (command === "daemon") return daemon(args);
|
|
25
26
|
if (command === "doctor") return doctor();
|
|
26
27
|
if (command === "help" || command === "--help" || command === "-h") return help();
|
|
27
28
|
|
|
@@ -60,12 +61,15 @@ async function install(args) {
|
|
|
60
61
|
upsertHookJson(codexPath, "codex");
|
|
61
62
|
upsertCodexOtelConfig(codexConfigPath, config);
|
|
62
63
|
|
|
64
|
+
installLaunchdDaemon();
|
|
65
|
+
|
|
63
66
|
console.log(`Monty installed for ${config.teamId}`);
|
|
64
67
|
console.log(`Site: ${config.siteUrl}`);
|
|
65
68
|
if (config.githubLogin) console.log(`GitHub avatar: ${config.githubLogin}`);
|
|
66
69
|
console.log(`Claude hook: ${claudePath}`);
|
|
67
70
|
console.log(`Codex hook: ${codexPath}`);
|
|
68
71
|
console.log(`Codex telemetry: ${codexConfigPath}`);
|
|
72
|
+
console.log(`Daemon: com.monty.daemon (launchd)`);
|
|
69
73
|
console.log("");
|
|
70
74
|
|
|
71
75
|
if (!options["no-sync"]) {
|
|
@@ -286,6 +290,62 @@ async function sendHeartbeat() {
|
|
|
286
290
|
}
|
|
287
291
|
}
|
|
288
292
|
|
|
293
|
+
function detectLidClosed() {
|
|
294
|
+
try {
|
|
295
|
+
const out = execSync("ioreg -r -k AppleClamshellState -d 4 | grep AppleClamshellState | head -1", { encoding: "utf8", timeout: 3000 });
|
|
296
|
+
if (out.includes("Yes")) return true;
|
|
297
|
+
if (out.includes("No")) return false;
|
|
298
|
+
return false;
|
|
299
|
+
} catch {
|
|
300
|
+
return false;
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
function detectSessions() {
|
|
305
|
+
let claude = 0;
|
|
306
|
+
let codex = 0;
|
|
307
|
+
try { claude = parseInt(execSync("pgrep -x claude 2>/dev/null | wc -l", { encoding: "utf8", timeout: 2000 }).trim(), 10) || 0; } catch {}
|
|
308
|
+
try { codex = parseInt(execSync("pgrep -x codex 2>/dev/null | wc -l", { encoding: "utf8", timeout: 2000 }).trim(), 10) || 0; } catch {}
|
|
309
|
+
return { claude, codex };
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
async function sendPresence() {
|
|
313
|
+
const config = readConfig();
|
|
314
|
+
const siteUrl = cleanUrl(config.siteUrl || process.env.MONTY_SITE_URL || "https://www.trymonty.ai");
|
|
315
|
+
const headers = { "content-type": "application/json" };
|
|
316
|
+
const token = config.ingestToken || process.env.MONTY_INGEST_TOKEN;
|
|
317
|
+
if (token) headers.authorization = `Bearer ${token}`;
|
|
318
|
+
|
|
319
|
+
const controller = new AbortController();
|
|
320
|
+
const timeout = setTimeout(() => controller.abort(), 5000);
|
|
321
|
+
try {
|
|
322
|
+
await fetch(`${siteUrl}/api/lid-state`, {
|
|
323
|
+
method: "POST",
|
|
324
|
+
headers,
|
|
325
|
+
body: JSON.stringify({
|
|
326
|
+
user_name: config.userName || process.env.MONTY_USER || process.env.USER || "unknown",
|
|
327
|
+
lid_closed: detectLidClosed(),
|
|
328
|
+
sessions: detectSessions(),
|
|
329
|
+
}),
|
|
330
|
+
signal: controller.signal,
|
|
331
|
+
});
|
|
332
|
+
} catch {}
|
|
333
|
+
finally { clearTimeout(timeout); }
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
async function daemon(args) {
|
|
337
|
+
const options = parseArgs(args);
|
|
338
|
+
const intervalSec = Number(options.interval) || 5;
|
|
339
|
+
console.log(`Monty daemon running (every ${intervalSec}s). Press Ctrl+C to stop.`);
|
|
340
|
+
|
|
341
|
+
const tick = async () => {
|
|
342
|
+
await sendPresence().catch(() => {});
|
|
343
|
+
};
|
|
344
|
+
|
|
345
|
+
await tick();
|
|
346
|
+
setInterval(tick, intervalSec * 1000);
|
|
347
|
+
}
|
|
348
|
+
|
|
289
349
|
async function runWrapped(args) {
|
|
290
350
|
const [tool, ...toolArgs] = args;
|
|
291
351
|
if (!tool || !["claude", "codex"].includes(tool)) {
|
|
@@ -668,6 +728,50 @@ function doctor() {
|
|
|
668
728
|
}
|
|
669
729
|
}
|
|
670
730
|
|
|
731
|
+
function installLaunchdDaemon() {
|
|
732
|
+
const label = "com.monty.daemon";
|
|
733
|
+
const plistPath = path.join(home, "Library", "LaunchAgents", `${label}.plist`);
|
|
734
|
+
const logPath = path.join(montyDir, "daemon.log");
|
|
735
|
+
|
|
736
|
+
const plist = `<?xml version="1.0" encoding="UTF-8"?>
|
|
737
|
+
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
738
|
+
<plist version="1.0">
|
|
739
|
+
<dict>
|
|
740
|
+
<key>Label</key>
|
|
741
|
+
<string>${label}</string>
|
|
742
|
+
<key>ProgramArguments</key>
|
|
743
|
+
<array>
|
|
744
|
+
<string>${process.execPath}</string>
|
|
745
|
+
<string>${installedCliPath}</string>
|
|
746
|
+
<string>daemon</string>
|
|
747
|
+
</array>
|
|
748
|
+
<key>RunAtLoad</key>
|
|
749
|
+
<true/>
|
|
750
|
+
<key>KeepAlive</key>
|
|
751
|
+
<true/>
|
|
752
|
+
<key>StandardOutPath</key>
|
|
753
|
+
<string>${logPath}</string>
|
|
754
|
+
<key>StandardErrorPath</key>
|
|
755
|
+
<string>${logPath}</string>
|
|
756
|
+
</dict>
|
|
757
|
+
</plist>
|
|
758
|
+
`;
|
|
759
|
+
|
|
760
|
+
try {
|
|
761
|
+
spawnSync("launchctl", ["unload", plistPath], { stdio: "ignore" });
|
|
762
|
+
} catch {}
|
|
763
|
+
|
|
764
|
+
fs.mkdirSync(path.dirname(plistPath), { recursive: true });
|
|
765
|
+
fs.writeFileSync(plistPath, plist);
|
|
766
|
+
|
|
767
|
+
const result = spawnSync("launchctl", ["load", plistPath], { encoding: "utf8", stdio: ["ignore", "pipe", "pipe"] });
|
|
768
|
+
if (result.status === 0) {
|
|
769
|
+
console.log(`Daemon started (${label})`);
|
|
770
|
+
} else {
|
|
771
|
+
console.log(`Daemon plist written to ${plistPath} (launchctl load may need manual run)`);
|
|
772
|
+
}
|
|
773
|
+
}
|
|
774
|
+
|
|
671
775
|
function upsertHookJson(filePath, source) {
|
|
672
776
|
fs.mkdirSync(path.dirname(filePath), { recursive: true });
|
|
673
777
|
const data = readJson(filePath, {});
|
|
@@ -964,6 +1068,7 @@ function help() {
|
|
|
964
1068
|
Usage:
|
|
965
1069
|
monty install --site http://localhost:3000 --team default
|
|
966
1070
|
monty sync Sync all Claude Code & Codex history with real token counts
|
|
1071
|
+
monty daemon Run background presence reporter (lid state + session count)
|
|
967
1072
|
monty capture --source test --prompt "hello"
|
|
968
1073
|
monty run codex exec "prompt"
|
|
969
1074
|
monty run claude -p "prompt"
|