@meandmyagents/agent-runner 0.1.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 ADDED
@@ -0,0 +1,18 @@
1
+ # MeAndMyAgents Agent Runner
2
+
3
+ Local visible-session runner for MeAndMyAgents.
4
+
5
+ ```bash
6
+ npm exec --yes --registry=https://registry.npmjs.org/ --package=@meandmyagents/agent-runner@latest -- meandmyagents-runner watch --key maa_live_YOUR_AGENT_KEY --launcher codex
7
+ ```
8
+
9
+ The runner polls the cheap `/api/agent/events` endpoint with an agent-bound key.
10
+ When work exists, it claims the wake event and launches a visible Codex or Claude
11
+ CLI session in the task project path. It injects that same key into the child
12
+ process as `MAA_API_KEY`, so one Mac can run several agent profiles without
13
+ sharing identity. The launched agent uses MCP to start, complete, and drain the
14
+ remaining actionable cards.
15
+
16
+ ```bash
17
+ meandmyagents-runner watch --key maa_live_YOUR_AGENT_KEY --launcher claude --once
18
+ ```
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env node
2
+ import { runCli } from "../src/runner.js";
3
+
4
+ runCli().catch((error) => {
5
+ const message = error instanceof Error ? error.message : String(error);
6
+ console.error(message);
7
+ process.exit(1);
8
+ });
package/package.json ADDED
@@ -0,0 +1,21 @@
1
+ {
2
+ "name": "@meandmyagents/agent-runner",
3
+ "version": "0.1.0",
4
+ "description": "Local visible-session task runner for Me And My Agents.",
5
+ "type": "module",
6
+ "bin": {
7
+ "meandmyagents-runner": "bin/meandmyagents-runner.js"
8
+ },
9
+ "files": [
10
+ "bin",
11
+ "src",
12
+ "README.md"
13
+ ],
14
+ "engines": {
15
+ "node": ">=18"
16
+ },
17
+ "publishConfig": {
18
+ "access": "public"
19
+ },
20
+ "license": "UNLICENSED"
21
+ }
package/src/runner.js ADDED
@@ -0,0 +1,238 @@
1
+ import { spawn } from "node:child_process";
2
+
3
+ export const defaultApiUrl = "https://meandmyagents.com/api";
4
+ const supportedLaunchers = new Set(["codex", "claude"]);
5
+
6
+ export function parseRunnerArgs(argv = process.argv.slice(2)) {
7
+ const options = {
8
+ command: argv[0] ?? "help",
9
+ apiUrl: defaultApiUrl,
10
+ intervalSeconds: 10,
11
+ key: "",
12
+ launcher: "codex",
13
+ once: false
14
+ };
15
+
16
+ for (let index = 1; index < argv.length; index += 1) {
17
+ const arg = argv[index];
18
+
19
+ if (arg === "--key" || arg === "-k") {
20
+ options.key = argv[index + 1] ?? "";
21
+ index += 1;
22
+ continue;
23
+ }
24
+
25
+ if (arg === "--api-url" || arg === "-u") {
26
+ options.apiUrl = normalizeApiUrl(argv[index + 1] ?? "");
27
+ index += 1;
28
+ continue;
29
+ }
30
+
31
+ if (arg === "--launcher" || arg === "-l") {
32
+ options.launcher = argv[index + 1] ?? "";
33
+ index += 1;
34
+ continue;
35
+ }
36
+
37
+ if (arg === "--interval") {
38
+ options.intervalSeconds = Number(argv[index + 1] ?? "10");
39
+ index += 1;
40
+ continue;
41
+ }
42
+
43
+ if (arg === "--once") {
44
+ options.once = true;
45
+ continue;
46
+ }
47
+
48
+ if (arg === "--help" || arg === "-h") {
49
+ options.command = "help";
50
+ continue;
51
+ }
52
+
53
+ throw new Error(`Unknown runner option: ${arg}`);
54
+ }
55
+
56
+ if (!Number.isFinite(options.intervalSeconds) || options.intervalSeconds < 2) {
57
+ options.intervalSeconds = 10;
58
+ }
59
+
60
+ if (!supportedLaunchers.has(options.launcher)) {
61
+ throw new Error(`Unsupported launcher: ${options.launcher}`);
62
+ }
63
+
64
+ return options;
65
+ }
66
+
67
+ export function buildAgentPrompt({
68
+ agentName,
69
+ cardId,
70
+ eventId,
71
+ projectName
72
+ }) {
73
+ return [
74
+ `You are ${agentName}. MeAndMyAgents has assigned work in ${projectName}.`,
75
+ `Wake event: ${eventId}. First card that woke this session: ${cardId}.`,
76
+ "Use the MeAndMyAgents MCP server now.",
77
+ "Call whoami to confirm your identity and project access.",
78
+ "Call list_my_tasks, then start_task on the next actionable card before editing files.",
79
+ "Work only in the task codebase.localPath and only when codebase.canModifyCode is true.",
80
+ "Add comments with useful progress and completion summaries.",
81
+ "Call complete_task when a task is ready for human testing.",
82
+ "Call list_my_tasks again after each completion.",
83
+ "Keep taking the next task until no actionable tasks remain, then stop."
84
+ ].join("\n");
85
+ }
86
+
87
+ export function buildLaunchCommand({ launcher, prompt, cwd, apiKey, apiUrl }) {
88
+ if (!supportedLaunchers.has(launcher)) {
89
+ throw new Error(`Unsupported launcher: ${launcher}`);
90
+ }
91
+
92
+ return {
93
+ command: launcher,
94
+ args: [prompt],
95
+ cwd,
96
+ env: {
97
+ MAA_API_KEY: apiKey,
98
+ MAA_API_URL: mcpUrlFromApiUrl(apiUrl)
99
+ }
100
+ };
101
+ }
102
+
103
+ export async function runCli(argv = process.argv.slice(2)) {
104
+ const options = parseRunnerArgs(argv);
105
+
106
+ if (options.command === "help") {
107
+ process.stdout.write(helpText());
108
+ return;
109
+ }
110
+
111
+ if (options.command !== "watch") {
112
+ throw new Error(`Unknown runner command: ${options.command}`);
113
+ }
114
+
115
+ if (!options.key) {
116
+ throw new Error("Missing required --key maa_live_... argument");
117
+ }
118
+
119
+ await watchForEvents(options);
120
+ }
121
+
122
+ async function watchForEvents(options) {
123
+ process.stdout.write(
124
+ `Watching ${options.apiUrl}/agent/events for ${options.launcher} work...\n`
125
+ );
126
+
127
+ do {
128
+ const events = await fetchPendingEvents(options);
129
+
130
+ if (events.length > 0) {
131
+ const event = events[0];
132
+ await claimEvent(options, event.id);
133
+ await launchEvent(options, event);
134
+ } else if (options.once) {
135
+ process.stdout.write("No pending task events.\n");
136
+ }
137
+
138
+ if (!options.once) {
139
+ await sleep(options.intervalSeconds * 1000);
140
+ }
141
+ } while (!options.once);
142
+ }
143
+
144
+ async function fetchPendingEvents(options) {
145
+ const response = await fetch(`${options.apiUrl}/agent/events?limit=10`, {
146
+ headers: { authorization: `Bearer ${options.key}` }
147
+ });
148
+
149
+ if (!response.ok) {
150
+ throw new Error(`Event fetch failed: HTTP ${response.status} ${await response.text()}`);
151
+ }
152
+
153
+ const body = await response.json();
154
+ return Array.isArray(body.events) ? body.events : [];
155
+ }
156
+
157
+ async function claimEvent(options, eventId) {
158
+ const response = await fetch(`${options.apiUrl}/agent/events/${eventId}/claim`, {
159
+ method: "POST",
160
+ headers: { authorization: `Bearer ${options.key}` }
161
+ });
162
+
163
+ if (!response.ok) {
164
+ throw new Error(`Event claim failed: HTTP ${response.status} ${await response.text()}`);
165
+ }
166
+ }
167
+
168
+ async function launchEvent(options, event) {
169
+ const cwd = resolveLaunchCwd(event);
170
+ const prompt = buildAgentPrompt({
171
+ agentName: event.agent?.name ?? options.launcher,
172
+ cardId: event.card?.id ?? "unknown-card",
173
+ eventId: event.id,
174
+ projectName: event.project?.name ?? "this project"
175
+ });
176
+ const launch = buildLaunchCommand({
177
+ launcher: options.launcher,
178
+ prompt,
179
+ cwd,
180
+ apiKey: options.key,
181
+ apiUrl: options.apiUrl
182
+ });
183
+
184
+ process.stdout.write(
185
+ `Launching ${launch.command} for ${event.card?.title ?? event.id} in ${launch.cwd}\n`
186
+ );
187
+
188
+ await new Promise((resolve, reject) => {
189
+ const child = spawn(launch.command, launch.args, {
190
+ cwd: launch.cwd,
191
+ env: { ...process.env, ...launch.env },
192
+ stdio: "inherit"
193
+ });
194
+ child.once("error", reject);
195
+ child.once("exit", (code) => {
196
+ if (code && code !== 0) {
197
+ reject(new Error(`${launch.command} exited with code ${code}`));
198
+ return;
199
+ }
200
+ resolve();
201
+ });
202
+ });
203
+ }
204
+
205
+ function normalizeApiUrl(value) {
206
+ return (value || defaultApiUrl).replace(/\/$/, "").replace(/\/mcp$/, "");
207
+ }
208
+
209
+ function mcpUrlFromApiUrl(value) {
210
+ return `${normalizeApiUrl(value)}/mcp`;
211
+ }
212
+
213
+ export function resolveLaunchCwd(
214
+ event,
215
+ env = { homeDir: process.env.HOME || process.env.USERPROFILE, cwd: process.cwd() }
216
+ ) {
217
+ return event.project?.localPath || env.homeDir || env.cwd;
218
+ }
219
+
220
+ function sleep(ms) {
221
+ return new Promise((resolve) => setTimeout(resolve, ms));
222
+ }
223
+
224
+ function helpText() {
225
+ return `MeAndMyAgents agent runner
226
+
227
+ Usage:
228
+ meandmyagents-runner watch --key maa_live_YOUR_AGENT_KEY --launcher codex
229
+ meandmyagents-runner watch --key maa_live_YOUR_AGENT_KEY --launcher claude --once
230
+
231
+ Options:
232
+ --key, -k Required agent-bound API key.
233
+ --api-url, -u API base URL. Defaults to ${defaultApiUrl}.
234
+ --launcher, -l Visible CLI launcher: codex or claude.
235
+ --interval Poll interval in seconds. Defaults to 10.
236
+ --once Check once, launch at most one visible session, then exit.
237
+ `;
238
+ }