@agentmeshhq/agent 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.
Files changed (67) hide show
  1. package/README.md +111 -0
  2. package/dist/cli/attach.d.ts +1 -0
  3. package/dist/cli/attach.js +18 -0
  4. package/dist/cli/attach.js.map +1 -0
  5. package/dist/cli/index.d.ts +2 -0
  6. package/dist/cli/index.js +98 -0
  7. package/dist/cli/index.js.map +1 -0
  8. package/dist/cli/init.d.ts +1 -0
  9. package/dist/cli/init.js +55 -0
  10. package/dist/cli/init.js.map +1 -0
  11. package/dist/cli/list.d.ts +1 -0
  12. package/dist/cli/list.js +45 -0
  13. package/dist/cli/list.js.map +1 -0
  14. package/dist/cli/nudge.d.ts +1 -0
  15. package/dist/cli/nudge.js +72 -0
  16. package/dist/cli/nudge.js.map +1 -0
  17. package/dist/cli/start.d.ts +8 -0
  18. package/dist/cli/start.js +37 -0
  19. package/dist/cli/start.js.map +1 -0
  20. package/dist/cli/stop.d.ts +1 -0
  21. package/dist/cli/stop.js +33 -0
  22. package/dist/cli/stop.js.map +1 -0
  23. package/dist/config/loader.d.ts +10 -0
  24. package/dist/config/loader.js +65 -0
  25. package/dist/config/loader.js.map +1 -0
  26. package/dist/config/schema.d.ts +32 -0
  27. package/dist/config/schema.js +11 -0
  28. package/dist/config/schema.js.map +1 -0
  29. package/dist/core/daemon.d.ts +20 -0
  30. package/dist/core/daemon.js +164 -0
  31. package/dist/core/daemon.js.map +1 -0
  32. package/dist/core/heartbeat.d.ts +14 -0
  33. package/dist/core/heartbeat.js +42 -0
  34. package/dist/core/heartbeat.js.map +1 -0
  35. package/dist/core/injector.d.ts +8 -0
  36. package/dist/core/injector.js +84 -0
  37. package/dist/core/injector.js.map +1 -0
  38. package/dist/core/registry.d.ts +27 -0
  39. package/dist/core/registry.js +52 -0
  40. package/dist/core/registry.js.map +1 -0
  41. package/dist/core/tmux.d.ts +11 -0
  42. package/dist/core/tmux.js +112 -0
  43. package/dist/core/tmux.js.map +1 -0
  44. package/dist/core/websocket.d.ts +25 -0
  45. package/dist/core/websocket.js +65 -0
  46. package/dist/core/websocket.js.map +1 -0
  47. package/dist/index.d.ts +8 -0
  48. package/dist/index.js +10 -0
  49. package/dist/index.js.map +1 -0
  50. package/package.json +35 -0
  51. package/src/cli/attach.ts +22 -0
  52. package/src/cli/index.ts +101 -0
  53. package/src/cli/init.ts +87 -0
  54. package/src/cli/list.ts +62 -0
  55. package/src/cli/nudge.ts +84 -0
  56. package/src/cli/start.ts +50 -0
  57. package/src/cli/stop.ts +39 -0
  58. package/src/config/loader.ts +81 -0
  59. package/src/config/schema.ts +44 -0
  60. package/src/core/daemon.ts +213 -0
  61. package/src/core/heartbeat.ts +54 -0
  62. package/src/core/injector.ts +128 -0
  63. package/src/core/registry.ts +105 -0
  64. package/src/core/tmux.ts +139 -0
  65. package/src/core/websocket.ts +94 -0
  66. package/src/index.ts +9 -0
  67. package/tsconfig.json +8 -0
@@ -0,0 +1,65 @@
1
+ import WebSocket from "ws";
2
+ export class AgentWebSocket {
3
+ ws = null;
4
+ config;
5
+ reconnectAttempts = 0;
6
+ maxReconnectAttempts = 10;
7
+ reconnectDelay = 1000;
8
+ shouldReconnect = true;
9
+ constructor(config) {
10
+ this.config = config;
11
+ }
12
+ connect() {
13
+ if (this.ws?.readyState === WebSocket.OPEN) {
14
+ return;
15
+ }
16
+ const wsUrl = `${this.config.url}?token=${this.config.token}`;
17
+ this.ws = new WebSocket(wsUrl);
18
+ this.ws.on("open", () => {
19
+ this.reconnectAttempts = 0;
20
+ this.config.onConnect?.();
21
+ });
22
+ this.ws.on("message", (data) => {
23
+ try {
24
+ const event = JSON.parse(data.toString());
25
+ this.config.onMessage(event);
26
+ }
27
+ catch (error) {
28
+ console.error("Failed to parse WebSocket message:", error);
29
+ }
30
+ });
31
+ this.ws.on("close", () => {
32
+ this.config.onDisconnect?.();
33
+ this.scheduleReconnect();
34
+ });
35
+ this.ws.on("error", (error) => {
36
+ this.config.onError?.(error);
37
+ });
38
+ }
39
+ scheduleReconnect() {
40
+ if (!this.shouldReconnect) {
41
+ return;
42
+ }
43
+ if (this.reconnectAttempts >= this.maxReconnectAttempts) {
44
+ console.error("Max reconnect attempts reached");
45
+ return;
46
+ }
47
+ this.reconnectAttempts++;
48
+ const delay = this.reconnectDelay * Math.pow(2, this.reconnectAttempts - 1);
49
+ setTimeout(() => {
50
+ console.log(`Reconnecting... (attempt ${this.reconnectAttempts}/${this.maxReconnectAttempts})`);
51
+ this.connect();
52
+ }, delay);
53
+ }
54
+ disconnect() {
55
+ this.shouldReconnect = false;
56
+ if (this.ws) {
57
+ this.ws.close();
58
+ this.ws = null;
59
+ }
60
+ }
61
+ isConnected() {
62
+ return this.ws?.readyState === WebSocket.OPEN;
63
+ }
64
+ }
65
+ //# sourceMappingURL=websocket.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"websocket.js","sourceRoot":"","sources":["../../src/core/websocket.ts"],"names":[],"mappings":"AAAA,OAAO,SAAS,MAAM,IAAI,CAAC;AAgB3B,MAAM,OAAO,cAAc;IACjB,EAAE,GAAqB,IAAI,CAAC;IAC5B,MAAM,CAAkB;IACxB,iBAAiB,GAAG,CAAC,CAAC;IACtB,oBAAoB,GAAG,EAAE,CAAC;IAC1B,cAAc,GAAG,IAAI,CAAC;IACtB,eAAe,GAAG,IAAI,CAAC;IAE/B,YAAY,MAAuB;QACjC,IAAI,CAAC,MAAM,GAAG,MAAM,CAAC;IACvB,CAAC;IAED,OAAO;QACL,IAAI,IAAI,CAAC,EAAE,EAAE,UAAU,KAAK,SAAS,CAAC,IAAI,EAAE,CAAC;YAC3C,OAAO;QACT,CAAC;QAED,MAAM,KAAK,GAAG,GAAG,IAAI,CAAC,MAAM,CAAC,GAAG,UAAU,IAAI,CAAC,MAAM,CAAC,KAAK,EAAE,CAAC;QAE9D,IAAI,CAAC,EAAE,GAAG,IAAI,SAAS,CAAC,KAAK,CAAC,CAAC;QAE/B,IAAI,CAAC,EAAE,CAAC,EAAE,CAAC,MAAM,EAAE,GAAG,EAAE;YACtB,IAAI,CAAC,iBAAiB,GAAG,CAAC,CAAC;YAC3B,IAAI,CAAC,MAAM,CAAC,SAAS,EAAE,EAAE,CAAC;QAC5B,CAAC,CAAC,CAAC;QAEH,IAAI,CAAC,EAAE,CAAC,EAAE,CAAC,SAAS,EAAE,CAAC,IAAI,EAAE,EAAE;YAC7B,IAAI,CAAC;gBACH,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,QAAQ,EAAE,CAAmB,CAAC;gBAC5D,IAAI,CAAC,MAAM,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC;YAC/B,CAAC;YAAC,OAAO,KAAK,EAAE,CAAC;gBACf,OAAO,CAAC,KAAK,CAAC,oCAAoC,EAAE,KAAK,CAAC,CAAC;YAC7D,CAAC;QACH,CAAC,CAAC,CAAC;QAEH,IAAI,CAAC,EAAE,CAAC,EAAE,CAAC,OAAO,EAAE,GAAG,EAAE;YACvB,IAAI,CAAC,MAAM,CAAC,YAAY,EAAE,EAAE,CAAC;YAC7B,IAAI,CAAC,iBAAiB,EAAE,CAAC;QAC3B,CAAC,CAAC,CAAC;QAEH,IAAI,CAAC,EAAE,CAAC,EAAE,CAAC,OAAO,EAAE,CAAC,KAAK,EAAE,EAAE;YAC5B,IAAI,CAAC,MAAM,CAAC,OAAO,EAAE,CAAC,KAAK,CAAC,CAAC;QAC/B,CAAC,CAAC,CAAC;IACL,CAAC;IAEO,iBAAiB;QACvB,IAAI,CAAC,IAAI,CAAC,eAAe,EAAE,CAAC;YAC1B,OAAO;QACT,CAAC;QAED,IAAI,IAAI,CAAC,iBAAiB,IAAI,IAAI,CAAC,oBAAoB,EAAE,CAAC;YACxD,OAAO,CAAC,KAAK,CAAC,gCAAgC,CAAC,CAAC;YAChD,OAAO;QACT,CAAC;QAED,IAAI,CAAC,iBAAiB,EAAE,CAAC;QACzB,MAAM,KAAK,GAAG,IAAI,CAAC,cAAc,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,iBAAiB,GAAG,CAAC,CAAC,CAAC;QAE5E,UAAU,CAAC,GAAG,EAAE;YACd,OAAO,CAAC,GAAG,CACT,4BAA4B,IAAI,CAAC,iBAAiB,IAAI,IAAI,CAAC,oBAAoB,GAAG,CACnF,CAAC;YACF,IAAI,CAAC,OAAO,EAAE,CAAC;QACjB,CAAC,EAAE,KAAK,CAAC,CAAC;IACZ,CAAC;IAED,UAAU;QACR,IAAI,CAAC,eAAe,GAAG,KAAK,CAAC;QAC7B,IAAI,IAAI,CAAC,EAAE,EAAE,CAAC;YACZ,IAAI,CAAC,EAAE,CAAC,KAAK,EAAE,CAAC;YAChB,IAAI,CAAC,EAAE,GAAG,IAAI,CAAC;QACjB,CAAC;IACH,CAAC;IAED,WAAW;QACT,OAAO,IAAI,CAAC,EAAE,EAAE,UAAU,KAAK,SAAS,CAAC,IAAI,CAAC;IAChD,CAAC;CACF"}
@@ -0,0 +1,8 @@
1
+ export { AgentDaemon } from "./core/daemon.js";
2
+ export { AgentWebSocket } from "./core/websocket.js";
3
+ export { Heartbeat } from "./core/heartbeat.js";
4
+ export * from "./core/tmux.js";
5
+ export * from "./core/registry.js";
6
+ export * from "./core/injector.js";
7
+ export * from "./config/schema.js";
8
+ export * from "./config/loader.js";
package/dist/index.js ADDED
@@ -0,0 +1,10 @@
1
+ // Re-export core modules for programmatic usage
2
+ export { AgentDaemon } from "./core/daemon.js";
3
+ export { AgentWebSocket } from "./core/websocket.js";
4
+ export { Heartbeat } from "./core/heartbeat.js";
5
+ export * from "./core/tmux.js";
6
+ export * from "./core/registry.js";
7
+ export * from "./core/injector.js";
8
+ export * from "./config/schema.js";
9
+ export * from "./config/loader.js";
10
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,gDAAgD;AAChD,OAAO,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAC/C,OAAO,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AACrD,OAAO,EAAE,SAAS,EAAE,MAAM,qBAAqB,CAAC;AAChD,cAAc,gBAAgB,CAAC;AAC/B,cAAc,oBAAoB,CAAC;AACnC,cAAc,oBAAoB,CAAC;AACnC,cAAc,oBAAoB,CAAC;AACnC,cAAc,oBAAoB,CAAC"}
package/package.json ADDED
@@ -0,0 +1,35 @@
1
+ {
2
+ "name": "@agentmeshhq/agent",
3
+ "version": "0.1.0",
4
+ "description": "AgentMesh Agent Wrapper - Turn any AI coding assistant into a dispatchable, nudge-able agent",
5
+ "type": "module",
6
+ "main": "dist/index.js",
7
+ "bin": {
8
+ "agentmesh": "dist/cli/index.js"
9
+ },
10
+ "scripts": {
11
+ "dev": "tsx watch src/cli/index.ts",
12
+ "build": "tsc -p tsconfig.json",
13
+ "lint": "biome check src",
14
+ "start": "node dist/cli/index.js"
15
+ },
16
+ "dependencies": {
17
+ "@agentmesh/sdk": "workspace:*",
18
+ "commander": "^12.0.0",
19
+ "picocolors": "^1.0.0",
20
+ "ws": "^8.16.0"
21
+ },
22
+ "devDependencies": {
23
+ "@types/node": "^22.15.21",
24
+ "@types/ws": "^8.5.10"
25
+ },
26
+ "keywords": [
27
+ "agentmesh",
28
+ "ai-agent",
29
+ "tmux",
30
+ "claude",
31
+ "opencode"
32
+ ],
33
+ "author": "AgentMesh",
34
+ "license": "MIT"
35
+ }
@@ -0,0 +1,22 @@
1
+ import { attachSession, sessionExists, getSessionName } from "../core/tmux.js";
2
+ import pc from "picocolors";
3
+
4
+ export function attach(name: string): void {
5
+ if (!name) {
6
+ console.log(pc.red("Agent name is required."));
7
+ process.exit(1);
8
+ }
9
+
10
+ const sessionName = getSessionName(name);
11
+
12
+ if (!sessionExists(sessionName)) {
13
+ console.log(pc.red(`Agent "${name}" is not running.`));
14
+ console.log(`Start it with: ${pc.cyan(`agentmesh start --name ${name}`)}`);
15
+ process.exit(1);
16
+ }
17
+
18
+ console.log(`Attaching to ${sessionName}...`);
19
+ console.log(pc.dim("Detach with: Ctrl+B, D\n"));
20
+
21
+ attachSession(name);
22
+ }
@@ -0,0 +1,101 @@
1
+ #!/usr/bin/env node
2
+
3
+ import { Command } from "commander";
4
+ import { init } from "./init.js";
5
+ import { start } from "./start.js";
6
+ import { stop } from "./stop.js";
7
+ import { list } from "./list.js";
8
+ import { attach } from "./attach.js";
9
+ import { nudge } from "./nudge.js";
10
+ import pc from "picocolors";
11
+
12
+ const program = new Command();
13
+
14
+ program
15
+ .name("agentmesh")
16
+ .description("AgentMesh Agent Wrapper - Turn any AI assistant into a dispatchable agent")
17
+ .version("0.1.0");
18
+
19
+ program
20
+ .command("init")
21
+ .description("Initialize AgentMesh configuration")
22
+ .action(async () => {
23
+ try {
24
+ await init();
25
+ } catch (error) {
26
+ console.error(pc.red((error as Error).message));
27
+ process.exit(1);
28
+ }
29
+ });
30
+
31
+ program
32
+ .command("start")
33
+ .description("Start an agent")
34
+ .requiredOption("-n, --name <name>", "Agent name")
35
+ .option("-c, --command <command>", "Command to run (default: opencode)")
36
+ .option("-w, --workdir <path>", "Working directory")
37
+ .option("-m, --model <model>", "Model identifier")
38
+ .option("-d, --daemonize", "Run in background")
39
+ .action(async (options) => {
40
+ try {
41
+ await start(options);
42
+ } catch (error) {
43
+ console.error(pc.red((error as Error).message));
44
+ process.exit(1);
45
+ }
46
+ });
47
+
48
+ program
49
+ .command("stop")
50
+ .description("Stop an agent")
51
+ .argument("<name>", "Agent name")
52
+ .action(async (name) => {
53
+ try {
54
+ await stop(name);
55
+ } catch (error) {
56
+ console.error(pc.red((error as Error).message));
57
+ process.exit(1);
58
+ }
59
+ });
60
+
61
+ program
62
+ .command("list")
63
+ .alias("ls")
64
+ .description("List running agents")
65
+ .action(async () => {
66
+ try {
67
+ await list();
68
+ } catch (error) {
69
+ console.error(pc.red((error as Error).message));
70
+ process.exit(1);
71
+ }
72
+ });
73
+
74
+ program
75
+ .command("attach")
76
+ .description("Attach to an agent's tmux session")
77
+ .argument("<name>", "Agent name")
78
+ .action((name) => {
79
+ try {
80
+ attach(name);
81
+ } catch (error) {
82
+ console.error(pc.red((error as Error).message));
83
+ process.exit(1);
84
+ }
85
+ });
86
+
87
+ program
88
+ .command("nudge")
89
+ .description("Send a nudge to an agent")
90
+ .argument("<name>", "Agent name")
91
+ .argument("<message>", "Message to send")
92
+ .action(async (name, message) => {
93
+ try {
94
+ await nudge(name, message);
95
+ } catch (error) {
96
+ console.error(pc.red((error as Error).message));
97
+ process.exit(1);
98
+ }
99
+ });
100
+
101
+ program.parse();
@@ -0,0 +1,87 @@
1
+ import * as readline from "node:readline";
2
+ import { loadConfig, saveConfig, createDefaultConfig } from "../config/loader.js";
3
+ import type { Config } from "../config/schema.js";
4
+ import pc from "picocolors";
5
+
6
+ function question(rl: readline.Interface, prompt: string): Promise<string> {
7
+ return new Promise((resolve) => {
8
+ rl.question(prompt, resolve);
9
+ });
10
+ }
11
+
12
+ export async function init(): Promise<void> {
13
+ const existingConfig = loadConfig();
14
+
15
+ if (existingConfig) {
16
+ console.log(pc.yellow("Config already exists at ~/.agentmesh/config.json"));
17
+ console.log("Current workspace:", pc.cyan(existingConfig.workspace));
18
+
19
+ const rl = readline.createInterface({
20
+ input: process.stdin,
21
+ output: process.stdout,
22
+ });
23
+
24
+ const overwrite = await question(rl, "Overwrite? (y/N): ");
25
+ rl.close();
26
+
27
+ if (overwrite.toLowerCase() !== "y") {
28
+ console.log("Aborted.");
29
+ return;
30
+ }
31
+ }
32
+
33
+ console.log(pc.bold("\nAgentMesh Agent Setup\n"));
34
+
35
+ const rl = readline.createInterface({
36
+ input: process.stdin,
37
+ output: process.stdout,
38
+ });
39
+
40
+ try {
41
+ const apiKey = await question(
42
+ rl,
43
+ `API Key ${pc.dim("(from agentmeshhq.dev/settings/api-keys)")}: `
44
+ );
45
+
46
+ if (!apiKey) {
47
+ console.log(pc.red("API Key is required."));
48
+ return;
49
+ }
50
+
51
+ const workspace = await question(
52
+ rl,
53
+ `Workspace ${pc.dim("(default: agentmesh)")}: `
54
+ );
55
+
56
+ const command = await question(
57
+ rl,
58
+ `Default command ${pc.dim("(default: opencode)")}: `
59
+ );
60
+
61
+ const model = await question(
62
+ rl,
63
+ `Default model ${pc.dim("(default: claude-sonnet-4)")}: `
64
+ );
65
+
66
+ const config: Config = createDefaultConfig(
67
+ apiKey.trim(),
68
+ workspace.trim() || "agentmesh"
69
+ );
70
+
71
+ if (command.trim()) {
72
+ config.defaults.command = command.trim();
73
+ }
74
+
75
+ if (model.trim()) {
76
+ config.defaults.model = model.trim();
77
+ }
78
+
79
+ saveConfig(config);
80
+
81
+ console.log(pc.green("\nConfig saved to ~/.agentmesh/config.json"));
82
+ console.log("\nNext steps:");
83
+ console.log(` ${pc.cyan("agentmesh start --name my-agent")}`);
84
+ } finally {
85
+ rl.close();
86
+ }
87
+ }
@@ -0,0 +1,62 @@
1
+ import { loadState, loadConfig } from "../config/loader.js";
2
+ import { sessionExists, getSessionName, getSessionInfo } from "../core/tmux.js";
3
+ import { checkInbox } from "../core/registry.js";
4
+ import pc from "picocolors";
5
+
6
+ export async function list(): Promise<void> {
7
+ const state = loadState();
8
+ const config = loadConfig();
9
+
10
+ if (state.agents.length === 0) {
11
+ console.log(pc.dim("No agents running."));
12
+ console.log(`Start one with: ${pc.cyan("agentmesh start --name <name>")}`);
13
+ return;
14
+ }
15
+
16
+ console.log(pc.bold("Running Agents:\n"));
17
+ console.log(
18
+ `${"NAME".padEnd(20)} ${"STATUS".padEnd(10)} ${"SESSION".padEnd(25)} ${"PENDING"}`
19
+ );
20
+ console.log("-".repeat(70));
21
+
22
+ for (const agent of state.agents) {
23
+ const sessionName = getSessionName(agent.name);
24
+ const exists = sessionExists(sessionName);
25
+ const info = getSessionInfo(agent.name);
26
+
27
+ let status = pc.green("online");
28
+ if (!exists) {
29
+ status = pc.red("offline");
30
+ }
31
+
32
+ let pending = pc.dim("-");
33
+
34
+ // Try to check inbox if we have a token
35
+ if (config && agent.token) {
36
+ try {
37
+ const items = await checkInbox(
38
+ config.hubUrl,
39
+ config.workspace,
40
+ agent.token
41
+ );
42
+ if (items.length > 0) {
43
+ pending = pc.yellow(`${items.length} handoff${items.length === 1 ? "" : "s"}`);
44
+ }
45
+ } catch {
46
+ // Ignore errors
47
+ }
48
+ }
49
+
50
+ const command = info.command ? pc.dim(`(${info.command})`) : "";
51
+
52
+ console.log(
53
+ `${agent.name.padEnd(20)} ${status.padEnd(19)} ${sessionName.padEnd(25)} ${pending}`
54
+ );
55
+
56
+ if (command) {
57
+ console.log(`${"".padEnd(20)} ${command}`);
58
+ }
59
+ }
60
+
61
+ console.log("");
62
+ }
@@ -0,0 +1,84 @@
1
+ import { loadConfig, getAgentState, loadState } from "../config/loader.js";
2
+ import { sendKeys, sessionExists, getSessionName } from "../core/tmux.js";
3
+ import { registerAgent } from "../core/registry.js";
4
+ import pc from "picocolors";
5
+
6
+ export async function nudge(name: string, message: string): Promise<void> {
7
+ const config = loadConfig();
8
+
9
+ if (!config) {
10
+ console.log(pc.red("No config found. Run 'agentmesh init' first."));
11
+ process.exit(1);
12
+ }
13
+
14
+ if (!name) {
15
+ console.log(pc.red("Agent name is required."));
16
+ process.exit(1);
17
+ }
18
+
19
+ if (!message) {
20
+ console.log(pc.red("Message is required."));
21
+ process.exit(1);
22
+ }
23
+
24
+ // Check if this is a local agent
25
+ const localAgent = getAgentState(name);
26
+
27
+ if (localAgent && sessionExists(getSessionName(name))) {
28
+ // Local nudge via tmux send-keys
29
+ const formatted = `[AgentMesh] Nudge from CLI:
30
+ ${message}`;
31
+
32
+ const sent = sendKeys(name, formatted);
33
+
34
+ if (sent) {
35
+ console.log(pc.green(`Nudged "${name}" locally.`));
36
+ } else {
37
+ console.log(pc.red(`Failed to nudge "${name}".`));
38
+ }
39
+ return;
40
+ }
41
+
42
+ // Remote nudge via API
43
+ // First we need to find the agent ID
44
+ const state = loadState();
45
+ const agentState = state.agents.find((a) => a.name === name);
46
+
47
+ if (!agentState?.agentId) {
48
+ console.log(pc.red(`Agent "${name}" not found.`));
49
+ console.log(pc.dim("For remote nudges, provide the agent ID instead."));
50
+ process.exit(1);
51
+ }
52
+
53
+ // Register ourselves to get a token
54
+ try {
55
+ const registration = await registerAgent({
56
+ url: config.hubUrl,
57
+ apiKey: config.apiKey,
58
+ workspace: config.workspace,
59
+ agentName: "cli-nudger",
60
+ model: "cli",
61
+ });
62
+
63
+ const response = await fetch(
64
+ `${config.hubUrl}/api/v1/agents/${agentState.agentId}/nudge`,
65
+ {
66
+ method: "POST",
67
+ headers: {
68
+ Authorization: `Bearer ${registration.token}`,
69
+ "Content-Type": "application/json",
70
+ },
71
+ body: JSON.stringify({ message }),
72
+ }
73
+ );
74
+
75
+ if (response.ok) {
76
+ console.log(pc.green(`Nudged "${name}" via API.`));
77
+ } else {
78
+ const error = await response.text();
79
+ console.log(pc.red(`Failed to nudge: ${error}`));
80
+ }
81
+ } catch (error) {
82
+ console.log(pc.red(`Failed to nudge: ${(error as Error).message}`));
83
+ }
84
+ }
@@ -0,0 +1,50 @@
1
+ import { AgentDaemon } from "../core/daemon.js";
2
+ import { loadConfig, getAgentState } from "../config/loader.js";
3
+ import { sessionExists, getSessionName } from "../core/tmux.js";
4
+ import pc from "picocolors";
5
+
6
+ export interface StartOptions {
7
+ name: string;
8
+ command?: string;
9
+ workdir?: string;
10
+ model?: string;
11
+ daemonize?: boolean;
12
+ }
13
+
14
+ export async function start(options: StartOptions): Promise<void> {
15
+ const config = loadConfig();
16
+
17
+ if (!config) {
18
+ console.log(pc.red("No config found. Run 'agentmesh init' first."));
19
+ process.exit(1);
20
+ }
21
+
22
+ if (!options.name) {
23
+ console.log(pc.red("Agent name is required. Use --name <name>"));
24
+ process.exit(1);
25
+ }
26
+
27
+ // Check if already running
28
+ const existingState = getAgentState(options.name);
29
+ const sessionName = getSessionName(options.name);
30
+
31
+ if (existingState && sessionExists(sessionName)) {
32
+ console.log(pc.yellow(`Agent "${options.name}" is already running.`));
33
+ console.log(`Attach with: ${pc.cyan(`agentmesh attach ${options.name}`)}`);
34
+ process.exit(1);
35
+ }
36
+
37
+ try {
38
+ const daemon = new AgentDaemon(options);
39
+ await daemon.start();
40
+
41
+ // Keep process alive in foreground mode
42
+ if (!options.daemonize) {
43
+ // Process will stay alive due to intervals and WebSocket
44
+ await new Promise(() => {}); // Never resolves
45
+ }
46
+ } catch (error) {
47
+ console.error(pc.red(`Failed to start agent: ${(error as Error).message}`));
48
+ process.exit(1);
49
+ }
50
+ }
@@ -0,0 +1,39 @@
1
+ import { destroySession } from "../core/tmux.js";
2
+ import { removeAgentFromState, getAgentState } from "../config/loader.js";
3
+ import pc from "picocolors";
4
+
5
+ export async function stop(name: string): Promise<void> {
6
+ if (!name) {
7
+ console.log(pc.red("Agent name is required."));
8
+ process.exit(1);
9
+ }
10
+
11
+ const state = getAgentState(name);
12
+
13
+ if (!state) {
14
+ console.log(pc.yellow(`Agent "${name}" is not running.`));
15
+ return;
16
+ }
17
+
18
+ // Try to kill the daemon process
19
+ if (state.pid) {
20
+ try {
21
+ process.kill(state.pid, "SIGTERM");
22
+ console.log(`Sent SIGTERM to daemon process ${state.pid}`);
23
+ } catch {
24
+ // Process might already be dead
25
+ }
26
+ }
27
+
28
+ // Destroy tmux session
29
+ const destroyed = destroySession(name);
30
+
31
+ if (destroyed) {
32
+ console.log(pc.green(`Destroyed tmux session for "${name}"`));
33
+ }
34
+
35
+ // Remove from state
36
+ removeAgentFromState(name);
37
+
38
+ console.log(pc.green(`Agent "${name}" stopped.`));
39
+ }
@@ -0,0 +1,81 @@
1
+ import * as fs from "node:fs";
2
+ import * as path from "node:path";
3
+ import {
4
+ type Config,
5
+ type State,
6
+ type AgentState,
7
+ CONFIG_PATH,
8
+ STATE_PATH,
9
+ DEFAULT_CONFIG,
10
+ } from "./schema.js";
11
+
12
+ export function ensureConfigDir(): void {
13
+ const configDir = path.dirname(CONFIG_PATH);
14
+ if (!fs.existsSync(configDir)) {
15
+ fs.mkdirSync(configDir, { recursive: true });
16
+ }
17
+ }
18
+
19
+ export function loadConfig(): Config | null {
20
+ try {
21
+ if (!fs.existsSync(CONFIG_PATH)) {
22
+ return null;
23
+ }
24
+ const content = fs.readFileSync(CONFIG_PATH, "utf-8");
25
+ return JSON.parse(content) as Config;
26
+ } catch {
27
+ return null;
28
+ }
29
+ }
30
+
31
+ export function saveConfig(config: Config): void {
32
+ ensureConfigDir();
33
+ fs.writeFileSync(CONFIG_PATH, JSON.stringify(config, null, 2));
34
+ }
35
+
36
+ export function loadState(): State {
37
+ try {
38
+ if (!fs.existsSync(STATE_PATH)) {
39
+ return { agents: [] };
40
+ }
41
+ const content = fs.readFileSync(STATE_PATH, "utf-8");
42
+ return JSON.parse(content) as State;
43
+ } catch {
44
+ return { agents: [] };
45
+ }
46
+ }
47
+
48
+ export function saveState(state: State): void {
49
+ ensureConfigDir();
50
+ fs.writeFileSync(STATE_PATH, JSON.stringify(state, null, 2));
51
+ }
52
+
53
+ export function addAgentToState(agent: AgentState): void {
54
+ const state = loadState();
55
+ // Remove existing entry with same name
56
+ state.agents = state.agents.filter((a) => a.name !== agent.name);
57
+ state.agents.push(agent);
58
+ saveState(state);
59
+ }
60
+
61
+ export function removeAgentFromState(name: string): void {
62
+ const state = loadState();
63
+ state.agents = state.agents.filter((a) => a.name !== name);
64
+ saveState(state);
65
+ }
66
+
67
+ export function getAgentState(name: string): AgentState | undefined {
68
+ const state = loadState();
69
+ return state.agents.find((a) => a.name === name);
70
+ }
71
+
72
+ export function createDefaultConfig(
73
+ apiKey: string,
74
+ workspace: string
75
+ ): Config {
76
+ return {
77
+ ...DEFAULT_CONFIG,
78
+ apiKey,
79
+ workspace,
80
+ } as Config;
81
+ }