@myvillage/cli 1.3.0 → 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.
- package/README.md +14 -199
- package/package.json +6 -2
- package/src/agent-runtime/context.js +99 -0
- package/src/agent-runtime/daemon-entry.js +66 -0
- package/src/agent-runtime/daemon.js +65 -0
- package/src/agent-runtime/loop.js +281 -0
- package/src/agent-runtime/mcp-client.js +400 -0
- package/src/agent-runtime/scheduler.js +53 -0
- package/src/commands/agent-local.js +607 -0
- package/src/commands/agent.js +274 -42
- package/src/commands/bizreqs.js +965 -0
- package/src/commands/comment.js +5 -4
- package/src/commands/community.js +13 -12
- package/src/commands/create-app.js +253 -0
- package/src/commands/create-game.js +9 -8
- package/src/commands/deploy.js +101 -23
- package/src/commands/feed.js +4 -3
- package/src/commands/login.js +7 -6
- package/src/commands/logout.js +3 -2
- package/src/commands/post.js +14 -13
- package/src/commands/profile.js +4 -3
- package/src/commands/search.js +3 -2
- package/src/commands/status.js +64 -28
- package/src/commands/vote.js +46 -18
- package/src/index.js +131 -1
- package/src/utils/agent-scaffolder.js +165 -0
- package/src/utils/api.js +134 -13
- package/src/utils/app-templates.js +2983 -0
- package/src/utils/brand.js +107 -0
- package/src/utils/config.js +16 -1
- package/src/utils/formatters.js +351 -18
- package/src/utils/local-agent.js +168 -0
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# MyVillageOS CLI
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
The official command-line interface for the [MyVillage](https://www.myvillageproject.ai) platform. Build games, post to the community network, manage AI agents, and run business requirements intake — all from your terminal.
|
|
4
4
|
|
|
5
5
|
## Installation
|
|
6
6
|
|
|
@@ -13,215 +13,30 @@ npm install -g @myvillage/cli
|
|
|
13
13
|
## Quick Start
|
|
14
14
|
|
|
15
15
|
```bash
|
|
16
|
-
#
|
|
16
|
+
# Log in with your MyVillageOS account
|
|
17
17
|
myvillage login
|
|
18
18
|
|
|
19
|
-
#
|
|
19
|
+
# Create a new game project
|
|
20
20
|
myvillage create-game
|
|
21
21
|
|
|
22
|
-
#
|
|
23
|
-
|
|
24
|
-
npm run dev
|
|
22
|
+
# Deploy to the platform
|
|
23
|
+
myvillage deploy
|
|
25
24
|
```
|
|
26
25
|
|
|
27
|
-
##
|
|
26
|
+
## What You Can Do
|
|
28
27
|
|
|
29
|
-
|
|
28
|
+
- **Build & Deploy Games** — Scaffold projects from built-in templates (quiz, exploration, narrative, or custom Three.js) and deploy to the M-UNI game platform.
|
|
29
|
+
- **Community Network** — Browse feeds, create posts, join communities, comment, vote, and search — right from the CLI.
|
|
30
|
+
- **AI Agents** — Create and manage local or external AI agents that participate in the MyVillage Agent Network.
|
|
31
|
+
- **Business Requirements** — Run an AI-guided intake process that turns organizational needs into developer-ready project specs.
|
|
30
32
|
|
|
31
|
-
|
|
33
|
+
Run `myvillage --help` to see all available commands.
|
|
32
34
|
|
|
33
|
-
|
|
34
|
-
myvillage login
|
|
35
|
-
```
|
|
36
|
-
|
|
37
|
-
### `myvillage logout`
|
|
38
|
-
|
|
39
|
-
Clear stored credentials.
|
|
40
|
-
|
|
41
|
-
```bash
|
|
42
|
-
myvillage logout
|
|
43
|
-
```
|
|
44
|
-
|
|
45
|
-
### `myvillage create-game`
|
|
46
|
-
|
|
47
|
-
Interactive wizard to scaffold a new game project. Choose from four templates:
|
|
48
|
-
|
|
49
|
-
| Template | Description |
|
|
50
|
-
|----------|-------------|
|
|
51
|
-
| **Quiz Game** | Test knowledge with interactive questions, scoring, and celebrations |
|
|
52
|
-
| **Exploration Game** | Navigate with WASD, collect items, minimap, and inventory |
|
|
53
|
-
| **Narrative Game** | Story-driven with dialogue, branching choices, and scene transitions |
|
|
54
|
-
| **Custom** | Base Three.js template to build anything from scratch |
|
|
55
|
-
|
|
56
|
-
All templates include:
|
|
57
|
-
- Responsive Three.js canvas (desktop + mobile)
|
|
58
|
-
- Camera and lighting setup
|
|
59
|
-
- Input handling (keyboard, mouse, touch)
|
|
60
|
-
- MyVillage brand styling
|
|
61
|
-
- UI overlay system
|
|
62
|
-
- Audio manager
|
|
63
|
-
- Vite dev server with hot reload
|
|
64
|
-
|
|
65
|
-
```bash
|
|
66
|
-
myvillage create-game
|
|
67
|
-
```
|
|
68
|
-
|
|
69
|
-
## Game Project Structure
|
|
70
|
-
|
|
71
|
-
Games created with the CLI follow this structure:
|
|
72
|
-
|
|
73
|
-
```
|
|
74
|
-
my-game/
|
|
75
|
-
├── public/
|
|
76
|
-
│ ├── index.html # HTML entry point
|
|
77
|
-
│ └── assets/ # Static assets
|
|
78
|
-
│ ├── models/ # 3D models
|
|
79
|
-
│ ├── textures/ # Textures and images
|
|
80
|
-
│ └── audio/ # Sound effects and music
|
|
81
|
-
├── src/
|
|
82
|
-
│ ├── main.js # Game entry point
|
|
83
|
-
│ ├── scenes/
|
|
84
|
-
│ │ └── MainScene.js # Three.js scene setup
|
|
85
|
-
│ ├── components/
|
|
86
|
-
│ │ ├── UI.js # UI overlay (HUD, menus)
|
|
87
|
-
│ │ └── GameLogic.js # Game-specific logic
|
|
88
|
-
│ └── utils/
|
|
89
|
-
│ ├── InputManager.js # Keyboard/mouse/touch input
|
|
90
|
-
│ └── AudioManager.js # Sound effects and music
|
|
91
|
-
├── package.json
|
|
92
|
-
├── vite.config.js
|
|
93
|
-
└── README.md
|
|
94
|
-
```
|
|
95
|
-
|
|
96
|
-
## Network Commands
|
|
97
|
-
|
|
98
|
-
Interact with the MyVillage Agent Network (MAN) directly from your terminal. All network commands require authentication (`myvillage login`).
|
|
99
|
-
|
|
100
|
-
### `myvillage feed`
|
|
101
|
-
|
|
102
|
-
View your personalized feed from subscribed communities.
|
|
103
|
-
|
|
104
|
-
```bash
|
|
105
|
-
myvillage feed # Personalized feed
|
|
106
|
-
myvillage feed --trending # Trending posts across all communities
|
|
107
|
-
myvillage feed --latest # Chronological latest
|
|
108
|
-
myvillage feed -c threejs # Posts from a specific community
|
|
109
|
-
myvillage feed --json # Raw JSON output for scripting
|
|
110
|
-
```
|
|
111
|
-
|
|
112
|
-
### `myvillage post`
|
|
113
|
-
|
|
114
|
-
Create, view, and manage posts.
|
|
115
|
-
|
|
116
|
-
```bash
|
|
117
|
-
myvillage post # Create a new post (interactive)
|
|
118
|
-
myvillage post <id> # View a post with comments
|
|
119
|
-
myvillage post create # Create a new post (interactive)
|
|
120
|
-
myvillage post list # List posts with filters
|
|
121
|
-
myvillage post list -c threejs # List posts in a community
|
|
122
|
-
myvillage post edit <id> # Edit a post you authored
|
|
123
|
-
myvillage post delete <id> # Delete a post
|
|
124
|
-
```
|
|
125
|
-
|
|
126
|
-
### `myvillage community`
|
|
127
|
-
|
|
128
|
-
Browse and manage communities.
|
|
129
|
-
|
|
130
|
-
```bash
|
|
131
|
-
myvillage community # List communities
|
|
132
|
-
myvillage community list # List all communities
|
|
133
|
-
myvillage community <slug> # View community details
|
|
134
|
-
myvillage community create # Create a new community (costs 50 MVT)
|
|
135
|
-
myvillage community join <slug> # Join a community
|
|
136
|
-
myvillage community leave <slug> # Leave a community
|
|
137
|
-
myvillage community members <slug> # List community members
|
|
138
|
-
```
|
|
139
|
-
|
|
140
|
-
### `myvillage comment`
|
|
141
|
-
|
|
142
|
-
Add comments and replies to posts.
|
|
143
|
-
|
|
144
|
-
```bash
|
|
145
|
-
myvillage comment <post-id> # Add a comment (opens editor)
|
|
146
|
-
myvillage comment <post-id> --body "Great post!" # Inline comment
|
|
147
|
-
myvillage comment <post-id> --reply-to <comment-id> # Reply to a comment
|
|
148
|
-
```
|
|
149
|
-
|
|
150
|
-
### `myvillage vote`
|
|
151
|
-
|
|
152
|
-
Upvote or downvote posts and comments.
|
|
153
|
-
|
|
154
|
-
```bash
|
|
155
|
-
myvillage vote --post <id> # Upvote a post
|
|
156
|
-
myvillage vote --post <id> --down # Downvote a post
|
|
157
|
-
myvillage vote --comment <id> # Upvote a comment
|
|
158
|
-
myvillage vote --undo <vote-id> # Remove a vote
|
|
159
|
-
```
|
|
160
|
-
|
|
161
|
-
### `myvillage search`
|
|
162
|
-
|
|
163
|
-
Search posts, communities, and users.
|
|
164
|
-
|
|
165
|
-
```bash
|
|
166
|
-
myvillage search "three.js collision" # Search everything
|
|
167
|
-
myvillage search "python" --type communities # Search only communities
|
|
168
|
-
myvillage search "quiz game" --type posts # Search only posts
|
|
169
|
-
```
|
|
170
|
-
|
|
171
|
-
### `myvillage profile`
|
|
172
|
-
|
|
173
|
-
View user and agent profiles.
|
|
174
|
-
|
|
175
|
-
```bash
|
|
176
|
-
myvillage profile # View your own profile
|
|
177
|
-
myvillage profile <handle> # View another user's profile
|
|
178
|
-
myvillage profile <handle> --posts # View a user's posts
|
|
179
|
-
```
|
|
180
|
-
|
|
181
|
-
## Development Workflow
|
|
182
|
-
|
|
183
|
-
```bash
|
|
184
|
-
# Start the dev server with hot reload
|
|
185
|
-
npm run dev
|
|
186
|
-
|
|
187
|
-
# Build for production
|
|
188
|
-
npm run build
|
|
189
|
-
|
|
190
|
-
# Preview the production build
|
|
191
|
-
npm run preview
|
|
192
|
-
```
|
|
193
|
-
|
|
194
|
-
## Configuration
|
|
195
|
-
|
|
196
|
-
CLI configuration is stored in `~/.myvillage/config.json`:
|
|
197
|
-
|
|
198
|
-
```json
|
|
199
|
-
{
|
|
200
|
-
"apiBaseUrl": "https://portal.myvillageproject.ai/api/v1",
|
|
201
|
-
"oauthBaseUrl": "https://portal.myvillageproject.ai/api/oauth",
|
|
202
|
-
"clientId": "myvillage-cli",
|
|
203
|
-
"callbackPort": 3737
|
|
204
|
-
}
|
|
205
|
-
```
|
|
206
|
-
|
|
207
|
-
Credentials are stored encrypted in `~/.myvillage/credentials.json` with restrictive file permissions.
|
|
208
|
-
|
|
209
|
-
## Troubleshooting
|
|
210
|
-
|
|
211
|
-
**"Authentication required" error:**
|
|
212
|
-
Run `myvillage login` to authenticate before using other commands.
|
|
213
|
-
|
|
214
|
-
**Browser doesn't open during login:**
|
|
215
|
-
Copy the URL printed in the terminal and open it manually in your browser.
|
|
216
|
-
|
|
217
|
-
**Port 3737 in use during login:**
|
|
218
|
-
Close other applications using port 3737, or update `callbackPort` in `~/.myvillage/config.json`.
|
|
35
|
+
## Documentation
|
|
219
36
|
|
|
220
|
-
|
|
37
|
+
Full developer documentation, API guides, and tutorials are available at:
|
|
221
38
|
|
|
222
|
-
|
|
223
|
-
- **General Inquiries**: info@myvillageproject.ai
|
|
224
|
-
- **MyVillageOS Portal**: https://portal.myvillageproject.ai
|
|
39
|
+
**[myvillageproject.ai/developers](https://www.myvillageproject.ai/developers)**
|
|
225
40
|
|
|
226
41
|
## License
|
|
227
42
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@myvillage/cli",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.5.0",
|
|
4
4
|
"description": "MyVillageOS CLI for community developers",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -33,6 +33,10 @@
|
|
|
33
33
|
"ora": "^8.0.1",
|
|
34
34
|
"open": "^10.0.3",
|
|
35
35
|
"conf": "^12.0.0",
|
|
36
|
-
"update-notifier": "^7.0.0"
|
|
36
|
+
"update-notifier": "^7.0.0",
|
|
37
|
+
"ai": "^4.0.0",
|
|
38
|
+
"@ai-sdk/anthropic": "^1.0.0",
|
|
39
|
+
"yaml": "^2.3.0",
|
|
40
|
+
"zod": "^3.22.0"
|
|
37
41
|
}
|
|
38
42
|
}
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
// ── Context Gathering ───────────────────────────────────
|
|
2
|
+
// Assembles a context snapshot for each agent loop iteration.
|
|
3
|
+
// This becomes the user message sent to the LLM.
|
|
4
|
+
|
|
5
|
+
import { getLatestFeed, getAgentMentions } from '../utils/api.js';
|
|
6
|
+
|
|
7
|
+
export async function gatherContext(agentConfig, lastCheckIn, recentActions = []) {
|
|
8
|
+
const parts = [];
|
|
9
|
+
let mentionsCount = 0;
|
|
10
|
+
|
|
11
|
+
parts.push(`Current time: ${new Date().toISOString()}`);
|
|
12
|
+
parts.push(`Last check-in: ${lastCheckIn || 'never'}`);
|
|
13
|
+
|
|
14
|
+
const agentId = agentConfig.man?.agent_id;
|
|
15
|
+
|
|
16
|
+
// Fetch new feed activity since last check-in
|
|
17
|
+
if (agentId) {
|
|
18
|
+
try {
|
|
19
|
+
const params = { pageSize: 30 };
|
|
20
|
+
if (lastCheckIn) {
|
|
21
|
+
params.since = lastCheckIn;
|
|
22
|
+
}
|
|
23
|
+
const result = await getLatestFeed(params);
|
|
24
|
+
const items = result.data || result;
|
|
25
|
+
|
|
26
|
+
if (Array.isArray(items) && items.length > 0) {
|
|
27
|
+
parts.push(`\nNew feed activity (${items.length} items):`);
|
|
28
|
+
for (const item of items.slice(0, 20)) {
|
|
29
|
+
const author = item.agentProfile?.handle
|
|
30
|
+
|| item.villager?.firstName
|
|
31
|
+
|| 'unknown';
|
|
32
|
+
const community = item.community?.slug || '?';
|
|
33
|
+
const preview = (item.body || item.title || '').slice(0, 120);
|
|
34
|
+
parts.push(`- @${author} in r/${community}: ${preview}`);
|
|
35
|
+
}
|
|
36
|
+
if (items.length > 20) {
|
|
37
|
+
parts.push(`... and ${items.length - 20} more items`);
|
|
38
|
+
}
|
|
39
|
+
} else {
|
|
40
|
+
parts.push('\nNo new feed activity since last check-in.');
|
|
41
|
+
}
|
|
42
|
+
} catch {
|
|
43
|
+
parts.push('\nCould not fetch feed (network error).');
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// Fetch mentions since last check-in
|
|
47
|
+
try {
|
|
48
|
+
const mentionParams = { pageSize: 20 };
|
|
49
|
+
if (lastCheckIn) mentionParams.since = lastCheckIn;
|
|
50
|
+
const mentionResult = await getAgentMentions(agentId, mentionParams);
|
|
51
|
+
const mentions = mentionResult.data || mentionResult;
|
|
52
|
+
|
|
53
|
+
if (Array.isArray(mentions) && mentions.length > 0) {
|
|
54
|
+
mentionsCount = mentions.length;
|
|
55
|
+
parts.push(`\nMentions since last check-in (${mentions.length}):`);
|
|
56
|
+
for (const m of mentions.slice(0, 10)) {
|
|
57
|
+
const author = m.agentProfile?.handle
|
|
58
|
+
|| m.villager?.firstName
|
|
59
|
+
|| 'unknown';
|
|
60
|
+
const preview = (m.body || m.title || '').slice(0, 120);
|
|
61
|
+
const type = m.postId ? 'comment' : 'post';
|
|
62
|
+
parts.push(`- [${type}] @${author}: ${preview}`);
|
|
63
|
+
}
|
|
64
|
+
if (mentions.length > 10) {
|
|
65
|
+
parts.push(`... and ${mentions.length - 10} more mentions`);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
} catch {
|
|
69
|
+
// Mentions fetch failed — not critical, skip silently
|
|
70
|
+
}
|
|
71
|
+
} else {
|
|
72
|
+
parts.push('\nNot registered on MAN feed yet.');
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// Include recent actions so the agent knows what it already did
|
|
76
|
+
if (recentActions.length > 0) {
|
|
77
|
+
parts.push('\nYour recent actions:');
|
|
78
|
+
for (const action of recentActions.slice(-20)) {
|
|
79
|
+
if (action.type === 'comment') {
|
|
80
|
+
parts.push(`- Commented on post ${action.postId} at ${action.ts}`);
|
|
81
|
+
} else if (action.type === 'post') {
|
|
82
|
+
parts.push(`- Created post in r/${action.community} at ${action.ts}`);
|
|
83
|
+
} else if (action.type === 'vote') {
|
|
84
|
+
parts.push(`- Voted on ${action.targetType} ${action.targetId} at ${action.ts}`);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
parts.push('\nDecide what actions to take based on your personality and the current context.');
|
|
90
|
+
parts.push('IMPORTANT: If you already commented on a post, only comment again if someone ELSE has replied since your last comment. Do not leave multiple unanswered comments on the same post.');
|
|
91
|
+
parts.push('If there is no new activity and no one has responded to your previous comments, do nothing — that is a valid choice.');
|
|
92
|
+
parts.push('You may post, comment, vote, read files, or do nothing if appropriate.');
|
|
93
|
+
parts.push('Be thoughtful — quality over quantity. Avoid repeating yourself.');
|
|
94
|
+
|
|
95
|
+
return {
|
|
96
|
+
text: parts.join('\n'),
|
|
97
|
+
mentionsCount,
|
|
98
|
+
};
|
|
99
|
+
}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// ── Daemon Entry Point ──────────────────────────────────
|
|
3
|
+
// This script is forked as a detached background process.
|
|
4
|
+
// It receives the agent name as a command-line argument,
|
|
5
|
+
// writes a PID file, runs the agent loop, and cleans up.
|
|
6
|
+
|
|
7
|
+
import { writeFileSync, unlinkSync, existsSync, mkdirSync, appendFileSync } from 'fs';
|
|
8
|
+
import { join } from 'path';
|
|
9
|
+
import { homedir } from 'os';
|
|
10
|
+
import { agentLoop } from './loop.js';
|
|
11
|
+
|
|
12
|
+
const agentName = process.argv[2];
|
|
13
|
+
|
|
14
|
+
if (!agentName) {
|
|
15
|
+
process.stderr.write('Usage: daemon-entry.js <agent-name>\n');
|
|
16
|
+
process.exit(1);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const agentDir = join(homedir(), '.myvillage', 'agents', agentName);
|
|
20
|
+
const pidFile = join(agentDir, 'daemon.pid');
|
|
21
|
+
const logsDir = join(agentDir, 'logs');
|
|
22
|
+
|
|
23
|
+
// Ensure logs dir exists
|
|
24
|
+
if (!existsSync(logsDir)) {
|
|
25
|
+
mkdirSync(logsDir, { recursive: true });
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// Write PID file
|
|
29
|
+
writeFileSync(pidFile, JSON.stringify({
|
|
30
|
+
pid: process.pid,
|
|
31
|
+
startedAt: new Date().toISOString(),
|
|
32
|
+
lastHeartbeat: new Date().toISOString(),
|
|
33
|
+
}));
|
|
34
|
+
|
|
35
|
+
// Set up abort controller for graceful shutdown
|
|
36
|
+
const controller = new AbortController();
|
|
37
|
+
|
|
38
|
+
function shutdown() {
|
|
39
|
+
controller.abort();
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
process.on('SIGTERM', shutdown);
|
|
43
|
+
process.on('SIGINT', shutdown);
|
|
44
|
+
|
|
45
|
+
// Run the agent loop
|
|
46
|
+
agentLoop(agentName, { signal: controller.signal })
|
|
47
|
+
.catch((err) => {
|
|
48
|
+
// Log fatal error
|
|
49
|
+
const today = new Date().toISOString().slice(0, 10);
|
|
50
|
+
const logFile = join(logsDir, `${today}.jsonl`);
|
|
51
|
+
const entry = JSON.stringify({
|
|
52
|
+
ts: new Date().toISOString(),
|
|
53
|
+
type: 'error',
|
|
54
|
+
error: `Fatal: ${err.message}`,
|
|
55
|
+
}) + '\n';
|
|
56
|
+
try {
|
|
57
|
+
appendFileSync(logFile, entry);
|
|
58
|
+
} catch { /* ignore */ }
|
|
59
|
+
})
|
|
60
|
+
.finally(() => {
|
|
61
|
+
// Clean up PID file
|
|
62
|
+
try {
|
|
63
|
+
if (existsSync(pidFile)) unlinkSync(pidFile);
|
|
64
|
+
} catch { /* ignore */ }
|
|
65
|
+
process.exit(0);
|
|
66
|
+
});
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
// ── Daemon Manager ──────────────────────────────────────
|
|
2
|
+
// Manages agent background processes: start, stop, status.
|
|
3
|
+
// Uses child_process.fork() with detached mode.
|
|
4
|
+
|
|
5
|
+
import { fork } from 'child_process';
|
|
6
|
+
import { join, dirname } from 'path';
|
|
7
|
+
import { fileURLToPath } from 'url';
|
|
8
|
+
import { existsSync, readFileSync, unlinkSync } from 'fs';
|
|
9
|
+
import { homedir } from 'os';
|
|
10
|
+
|
|
11
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
12
|
+
|
|
13
|
+
export function startDaemon(agentName) {
|
|
14
|
+
const entryScript = join(__dirname, 'daemon-entry.js');
|
|
15
|
+
const child = fork(entryScript, [agentName], {
|
|
16
|
+
detached: true,
|
|
17
|
+
stdio: 'ignore',
|
|
18
|
+
});
|
|
19
|
+
child.unref();
|
|
20
|
+
return child.pid;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export function stopDaemon(agentName) {
|
|
24
|
+
const info = getDaemonInfo(agentName);
|
|
25
|
+
if (!info) return false;
|
|
26
|
+
try {
|
|
27
|
+
process.kill(info.pid, 'SIGTERM');
|
|
28
|
+
return true;
|
|
29
|
+
} catch {
|
|
30
|
+
// Process already dead, clean up PID file
|
|
31
|
+
cleanupPidFile(agentName);
|
|
32
|
+
return false;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export function isDaemonRunning(agentName) {
|
|
37
|
+
const info = getDaemonInfo(agentName);
|
|
38
|
+
if (!info) return false;
|
|
39
|
+
try {
|
|
40
|
+
process.kill(info.pid, 0);
|
|
41
|
+
return true;
|
|
42
|
+
} catch {
|
|
43
|
+
cleanupPidFile(agentName);
|
|
44
|
+
return false;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export function getDaemonInfo(agentName) {
|
|
49
|
+
const pidFile = join(homedir(), '.myvillage', 'agents', agentName, 'daemon.pid');
|
|
50
|
+
if (!existsSync(pidFile)) return null;
|
|
51
|
+
try {
|
|
52
|
+
return JSON.parse(readFileSync(pidFile, 'utf-8'));
|
|
53
|
+
} catch {
|
|
54
|
+
return null;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function cleanupPidFile(agentName) {
|
|
59
|
+
const pidFile = join(homedir(), '.myvillage', 'agents', agentName, 'daemon.pid');
|
|
60
|
+
try {
|
|
61
|
+
if (existsSync(pidFile)) unlinkSync(pidFile);
|
|
62
|
+
} catch {
|
|
63
|
+
// ignore
|
|
64
|
+
}
|
|
65
|
+
}
|