@myvillage/cli 1.2.2 → 1.5.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,168 @@
1
+ import { existsSync, mkdirSync, readFileSync, writeFileSync, readdirSync, unlinkSync } from 'fs';
2
+ import { join } from 'path';
3
+ import { getConfigDir } from './config.js';
4
+ import { parse as parseYaml, stringify as stringifyYaml } from 'yaml';
5
+
6
+ // ── Directory Helpers ───────────────────────────────────
7
+
8
+ export function getAgentsDir() {
9
+ const dir = join(getConfigDir(), 'agents');
10
+ if (!existsSync(dir)) {
11
+ mkdirSync(dir, { recursive: true });
12
+ }
13
+ return dir;
14
+ }
15
+
16
+ export function getAgentDir(name) {
17
+ return join(getAgentsDir(), name);
18
+ }
19
+
20
+ export function agentExists(name) {
21
+ const dir = getAgentDir(name);
22
+ return existsSync(dir) && existsSync(join(dir, 'agent.config.yaml'));
23
+ }
24
+
25
+ // ── Config Read/Write ───────────────────────────────────
26
+
27
+ export function readAgentConfig(name) {
28
+ const configPath = join(getAgentDir(name), 'agent.config.yaml');
29
+ if (!existsSync(configPath)) return null;
30
+ try {
31
+ const raw = readFileSync(configPath, 'utf-8');
32
+ return parseYaml(raw);
33
+ } catch {
34
+ return null;
35
+ }
36
+ }
37
+
38
+ export function writeAgentConfig(name, config) {
39
+ const configPath = join(getAgentDir(name), 'agent.config.yaml');
40
+ writeFileSync(configPath, stringifyYaml(config, { lineWidth: 0 }));
41
+ }
42
+
43
+ // ── Tools YAML Read/Write ───────────────────────────────
44
+
45
+ export function readToolsYaml(name) {
46
+ const toolsPath = join(getAgentDir(name), 'tools.yaml');
47
+ if (!existsSync(toolsPath)) return { servers: {} };
48
+ try {
49
+ const raw = readFileSync(toolsPath, 'utf-8');
50
+ return parseYaml(raw) || { servers: {} };
51
+ } catch {
52
+ return { servers: {} };
53
+ }
54
+ }
55
+
56
+ export function writeToolsYaml(name, toolsConfig) {
57
+ const toolsPath = join(getAgentDir(name), 'tools.yaml');
58
+ writeFileSync(toolsPath, stringifyYaml(toolsConfig, { lineWidth: 0 }));
59
+ }
60
+
61
+ // ── Daemon Status ───────────────────────────────────────
62
+
63
+ export function getDaemonInfo(name) {
64
+ const pidFile = join(getAgentDir(name), 'daemon.pid');
65
+ if (!existsSync(pidFile)) return null;
66
+ try {
67
+ return JSON.parse(readFileSync(pidFile, 'utf-8'));
68
+ } catch {
69
+ return null;
70
+ }
71
+ }
72
+
73
+ export function isDaemonRunning(name) {
74
+ const info = getDaemonInfo(name);
75
+ if (!info) return false;
76
+ try {
77
+ process.kill(info.pid, 0);
78
+ return true;
79
+ } catch {
80
+ // Process dead, clean up stale PID file
81
+ cleanupPidFile(name);
82
+ return false;
83
+ }
84
+ }
85
+
86
+ export function cleanupPidFile(name) {
87
+ const pidFile = join(getAgentDir(name), 'daemon.pid');
88
+ try {
89
+ if (existsSync(pidFile)) unlinkSync(pidFile);
90
+ } catch {
91
+ // ignore
92
+ }
93
+ }
94
+
95
+ // ── List Local Agents ───────────────────────────────────
96
+
97
+ export function listLocalAgents() {
98
+ const agentsDir = getAgentsDir();
99
+ if (!existsSync(agentsDir)) return [];
100
+
101
+ const entries = readdirSync(agentsDir, { withFileTypes: true });
102
+ const agents = [];
103
+
104
+ for (const entry of entries) {
105
+ if (!entry.isDirectory()) continue;
106
+ const configPath = join(agentsDir, entry.name, 'agent.config.yaml');
107
+ if (!existsSync(configPath)) continue;
108
+
109
+ try {
110
+ const config = parseYaml(readFileSync(configPath, 'utf-8'));
111
+ const running = isDaemonRunning(entry.name);
112
+ const daemonInfo = getDaemonInfo(entry.name);
113
+
114
+ agents.push({
115
+ name: entry.name,
116
+ config,
117
+ isRunning: running,
118
+ lastHeartbeat: daemonInfo?.lastHeartbeat || null,
119
+ });
120
+ } catch {
121
+ // Skip malformed agent directories
122
+ }
123
+ }
124
+
125
+ return agents;
126
+ }
127
+
128
+ // ── Log Reading ─────────────────────────────────────────
129
+
130
+ export function readAgentLogs(name, options = {}) {
131
+ const logsDir = join(getAgentDir(name), 'logs');
132
+ if (!existsSync(logsDir)) return [];
133
+
134
+ const { since, limit = 50 } = options;
135
+
136
+ // Find log files, sorted newest first
137
+ const files = readdirSync(logsDir)
138
+ .filter(f => f.endsWith('.jsonl'))
139
+ .sort()
140
+ .reverse();
141
+
142
+ const entries = [];
143
+
144
+ for (const file of files) {
145
+ const lines = readFileSync(join(logsDir, file), 'utf-8')
146
+ .split('\n')
147
+ .filter(Boolean);
148
+
149
+ for (const line of lines.reverse()) {
150
+ try {
151
+ const entry = JSON.parse(line);
152
+ if (since && new Date(entry.ts) < new Date(since)) continue;
153
+ entries.push(entry);
154
+ if (entries.length >= limit) return entries.reverse();
155
+ } catch {
156
+ // skip malformed lines
157
+ }
158
+ }
159
+ }
160
+
161
+ return entries.reverse();
162
+ }
163
+
164
+ export function getLogFilePath(name) {
165
+ const logsDir = join(getAgentDir(name), 'logs');
166
+ const today = new Date().toISOString().slice(0, 10);
167
+ return join(logsDir, `${today}.jsonl`);
168
+ }