anyclaw 0.2.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,26 @@
1
+ /**
2
+ * Gateway Adapters
3
+ *
4
+ * Protocol translators for different claw gateway types.
5
+ * Each adapter knows how to send a message and parse the SSE stream
6
+ * from a specific type of local AI agent.
7
+ */
8
+ export interface ChannelEvent {
9
+ type: "content" | "tool_call" | "tool_result" | "error";
10
+ data: Record<string, unknown>;
11
+ }
12
+ export interface GatewayAdapter {
13
+ name: string;
14
+ send(gatewayUrl: string, message: string, onEvent: (event: ChannelEvent) => void, signal?: AbortSignal): Promise<string>;
15
+ }
16
+ /**
17
+ * PaeanClaw / ZeroClaw adapter
18
+ * Both expose POST /api/chat with SSE response.
19
+ */
20
+ export declare const clawAdapter: GatewayAdapter;
21
+ /**
22
+ * OpenAI-compatible adapter
23
+ * For any endpoint that speaks POST /v1/chat/completions with SSE.
24
+ */
25
+ export declare const openaiAdapter: GatewayAdapter;
26
+ export declare function getAdapter(type: string): GatewayAdapter;
@@ -0,0 +1,125 @@
1
+ /**
2
+ * Gateway Adapters
3
+ *
4
+ * Protocol translators for different claw gateway types.
5
+ * Each adapter knows how to send a message and parse the SSE stream
6
+ * from a specific type of local AI agent.
7
+ */
8
+ async function readSSEStream(body, handler) {
9
+ const reader = body.getReader();
10
+ const decoder = new TextDecoder();
11
+ let buffer = "";
12
+ while (true) {
13
+ const { done, value } = await reader.read();
14
+ if (done)
15
+ break;
16
+ buffer += decoder.decode(value, { stream: true });
17
+ const lines = buffer.split("\n");
18
+ buffer = lines.pop() || "";
19
+ for (const line of lines) {
20
+ handler(line);
21
+ }
22
+ }
23
+ }
24
+ /**
25
+ * PaeanClaw / ZeroClaw adapter
26
+ * Both expose POST /api/chat with SSE response.
27
+ */
28
+ export const clawAdapter = {
29
+ name: "claw",
30
+ async send(gatewayUrl, message, onEvent, signal) {
31
+ const url = `${gatewayUrl.replace(/\/$/, "")}/api/chat`;
32
+ const res = await fetch(url, {
33
+ method: "POST",
34
+ headers: { "Content-Type": "application/json" },
35
+ body: JSON.stringify({ message }),
36
+ signal,
37
+ });
38
+ if (!res.ok || !res.body) {
39
+ throw new Error(`Gateway error: HTTP ${res.status}`);
40
+ }
41
+ let fullContent = "";
42
+ await readSSEStream(res.body, (line) => {
43
+ if (!line.startsWith("data: "))
44
+ return;
45
+ try {
46
+ const event = JSON.parse(line.slice(6));
47
+ switch (event.type) {
48
+ case "content":
49
+ fullContent += event.text || "";
50
+ onEvent({ type: "content", data: { text: event.text, partial: true } });
51
+ break;
52
+ case "tool_call":
53
+ onEvent({ type: "tool_call", data: { name: event.name, id: event.name } });
54
+ break;
55
+ case "tool_result":
56
+ onEvent({ type: "tool_result", data: { name: event.name, status: "completed" } });
57
+ break;
58
+ case "done":
59
+ onEvent({ type: "content", data: { text: event.content || fullContent, partial: false } });
60
+ break;
61
+ case "error":
62
+ onEvent({ type: "error", data: { error: event.error } });
63
+ break;
64
+ }
65
+ }
66
+ catch {
67
+ // skip malformed lines
68
+ }
69
+ });
70
+ return fullContent;
71
+ },
72
+ };
73
+ /**
74
+ * OpenAI-compatible adapter
75
+ * For any endpoint that speaks POST /v1/chat/completions with SSE.
76
+ */
77
+ export const openaiAdapter = {
78
+ name: "openai",
79
+ async send(gatewayUrl, message, onEvent, signal) {
80
+ const url = `${gatewayUrl.replace(/\/$/, "")}/v1/chat/completions`;
81
+ const res = await fetch(url, {
82
+ method: "POST",
83
+ headers: { "Content-Type": "application/json" },
84
+ body: JSON.stringify({
85
+ model: "default",
86
+ messages: [{ role: "user", content: message }],
87
+ stream: true,
88
+ }),
89
+ signal,
90
+ });
91
+ if (!res.ok || !res.body) {
92
+ throw new Error(`Gateway error: HTTP ${res.status}`);
93
+ }
94
+ let fullContent = "";
95
+ await readSSEStream(res.body, (line) => {
96
+ const trimmed = line.trim();
97
+ if (!trimmed.startsWith("data: ") || trimmed === "data: [DONE]")
98
+ return;
99
+ try {
100
+ const chunk = JSON.parse(trimmed.slice(6));
101
+ const delta = chunk.choices?.[0]?.delta;
102
+ if (delta?.content) {
103
+ fullContent += delta.content;
104
+ onEvent({ type: "content", data: { text: delta.content, partial: true } });
105
+ }
106
+ }
107
+ catch {
108
+ // skip
109
+ }
110
+ });
111
+ return fullContent;
112
+ },
113
+ };
114
+ export function getAdapter(type) {
115
+ switch (type) {
116
+ case "claw":
117
+ case "paeanclaw":
118
+ case "zeroclaw":
119
+ return clawAdapter;
120
+ case "openai":
121
+ return openaiAdapter;
122
+ default:
123
+ return clawAdapter;
124
+ }
125
+ }
package/dist/cli.d.ts ADDED
@@ -0,0 +1,17 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * AnyClaw CLI
4
+ *
5
+ * Multi-command toolkit for local AI agent gateways.
6
+ *
7
+ * Usage:
8
+ * anyclaw Show help
9
+ * anyclaw bridge [options] Connect local gateway to AnyClaw relay
10
+ * anyclaw install [variant] Install a claw variant (paeanclaw, zeroclaw, etc.)
11
+ * anyclaw status Check locally running agent gateways
12
+ * anyclaw version Show version
13
+ */
14
+ declare const VERSION = "0.2.0";
15
+ declare const BANNER = "\n \u2584\u2580\u2588 \u2588\u2584\u2591\u2588 \u2588\u2584\u2588 \u2588\u2580\u2580 \u2588\u2591\u2591 \u2584\u2580\u2588 \u2588\u2591\u2588\u2591\u2588\n \u2588\u2580\u2588 \u2588\u2591\u2580\u2588 \u2591\u2588\u2591 \u2588\u2584\u2584 \u2588\u2584\u2584 \u2588\u2580\u2588 \u2580\u2584\u2580\u2584\u2580\n";
16
+ declare function printHelp(): void;
17
+ declare function main(): Promise<void>;
package/dist/cli.js ADDED
@@ -0,0 +1,78 @@
1
+ #!/usr/bin/env node
2
+ "use strict";
3
+ /**
4
+ * AnyClaw CLI
5
+ *
6
+ * Multi-command toolkit for local AI agent gateways.
7
+ *
8
+ * Usage:
9
+ * anyclaw Show help
10
+ * anyclaw bridge [options] Connect local gateway to AnyClaw relay
11
+ * anyclaw install [variant] Install a claw variant (paeanclaw, zeroclaw, etc.)
12
+ * anyclaw status Check locally running agent gateways
13
+ * anyclaw version Show version
14
+ */
15
+ const VERSION = "0.2.0";
16
+ const BANNER = `
17
+ ▄▀█ █▄░█ █▄█ █▀▀ █░░ ▄▀█ █░█░█
18
+ █▀█ █░▀█ ░█░ █▄▄ █▄▄ █▀█ ▀▄▀▄▀
19
+ `;
20
+ function printHelp() {
21
+ console.log(BANNER);
22
+ console.log(` AnyClaw CLI v${VERSION}`);
23
+ console.log(` https://anyclaw.sh\n`);
24
+ console.log(` Usage:`);
25
+ console.log(` anyclaw <command> [options]\n`);
26
+ console.log(` Commands:`);
27
+ console.log(` bridge Connect a local AI agent gateway to the AnyClaw relay`);
28
+ console.log(` install Install a claw variant (paeanclaw, zeroclaw, openclaw, nanoclaw)`);
29
+ console.log(` status Check locally running agent gateways`);
30
+ console.log(` version Show version info`);
31
+ console.log(` help Show this help message\n`);
32
+ console.log(` Examples:`);
33
+ console.log(` anyclaw bridge -g http://localhost:3007 -k ck_g_abc123`);
34
+ console.log(` anyclaw install paeanclaw`);
35
+ console.log(` anyclaw status\n`);
36
+ }
37
+ async function main() {
38
+ const args = process.argv.slice(2);
39
+ const command = args[0] || "help";
40
+ // Backward compat: if invoked as `anyclaw-bridge` (old bin name),
41
+ // or if first arg looks like a bridge flag, route to bridge
42
+ const invokedAs = process.argv[1]?.endsWith("anyclaw-bridge") ?? false;
43
+ const bridgeFlags = ["-g", "--gateway", "-k", "--key", "-t", "--type", "-s", "--service"];
44
+ const looksLikeBridge = invokedAs || bridgeFlags.includes(command);
45
+ if (looksLikeBridge) {
46
+ const { runBridge } = await import("./commands/bridge.js");
47
+ return runBridge(looksLikeBridge && !invokedAs ? args : args);
48
+ }
49
+ switch (command) {
50
+ case "bridge": {
51
+ const { runBridge } = await import("./commands/bridge.js");
52
+ return runBridge(args.slice(1));
53
+ }
54
+ case "install": {
55
+ const { runInstall } = await import("./commands/install.js");
56
+ return runInstall(args.slice(1));
57
+ }
58
+ case "status": {
59
+ const { runStatus } = await import("./commands/status.js");
60
+ return runStatus();
61
+ }
62
+ case "version":
63
+ case "--version":
64
+ case "-v":
65
+ console.log(`anyclaw v${VERSION}`);
66
+ break;
67
+ case "help":
68
+ case "--help":
69
+ case "-h":
70
+ default:
71
+ printHelp();
72
+ break;
73
+ }
74
+ }
75
+ main().catch((err) => {
76
+ console.error("Fatal:", err);
77
+ process.exit(1);
78
+ });
@@ -0,0 +1 @@
1
+ export declare function runBridge(args: string[]): Promise<void>;
@@ -0,0 +1,157 @@
1
+ /**
2
+ * Bridge Command
3
+ *
4
+ * Connects a local AI agent gateway to the AnyClaw relay service.
5
+ * Polls for incoming messages, forwards them to the local gateway,
6
+ * and streams responses back through the relay.
7
+ */
8
+ import { getAdapter } from "../adapters.js";
9
+ import { pollRequests, claimRequest, pushEvents, completeRequest, } from "../relay.js";
10
+ function parseArgs(args) {
11
+ const config = {
12
+ gatewayUrl: "http://localhost:3007",
13
+ gatewayType: "claw",
14
+ clawKey: "",
15
+ serviceUrl: "http://localhost:4777",
16
+ };
17
+ for (let i = 0; i < args.length; i++) {
18
+ switch (args[i]) {
19
+ case "--gateway":
20
+ case "-g":
21
+ config.gatewayUrl = args[++i];
22
+ break;
23
+ case "--type":
24
+ case "-t":
25
+ config.gatewayType = args[++i];
26
+ break;
27
+ case "--key":
28
+ case "-k":
29
+ config.clawKey = args[++i];
30
+ break;
31
+ case "--service":
32
+ case "-s":
33
+ config.serviceUrl = args[++i];
34
+ break;
35
+ case "--help":
36
+ case "-h":
37
+ printHelp();
38
+ process.exit(0);
39
+ }
40
+ }
41
+ config.clawKey = config.clawKey || process.env.CLAW_KEY || "";
42
+ config.serviceUrl =
43
+ config.serviceUrl || process.env.ANYCLAW_SERVICE_URL || "http://localhost:4777";
44
+ config.gatewayUrl =
45
+ config.gatewayUrl || process.env.GATEWAY_URL || "http://localhost:3007";
46
+ return config;
47
+ }
48
+ function printHelp() {
49
+ console.log(`
50
+ AnyClaw Bridge — connects your local AI agent to the AnyClaw relay
51
+
52
+ Usage:
53
+ anyclaw bridge --gateway <url> --key <clawkey> [options]
54
+
55
+ Options:
56
+ -g, --gateway <url> Local agent gateway URL (default: http://localhost:3007)
57
+ -t, --type <type> Gateway type: claw, paeanclaw, zeroclaw, openai (default: claw)
58
+ -k, --key <key> ClawKey for relay authentication
59
+ -s, --service <url> AnyClaw service URL (default: http://localhost:4777)
60
+ -h, --help Show this help
61
+
62
+ Environment:
63
+ CLAW_KEY ClawKey (alternative to --key)
64
+ ANYCLAW_SERVICE_URL Service URL (alternative to --service)
65
+ GATEWAY_URL Gateway URL (alternative to --gateway)
66
+
67
+ Examples:
68
+ anyclaw bridge -g http://localhost:3007 -k ck_g_abc123
69
+ anyclaw bridge -g http://localhost:42617 -t zeroclaw -k ck_p_xyz789
70
+ `);
71
+ }
72
+ const MIN_POLL_MS = 500;
73
+ const MAX_POLL_MS = 5000;
74
+ const BACKOFF_FACTOR = 1.5;
75
+ async function processRequest(relay, gatewayUrl, gatewayType, requestId, message) {
76
+ const claimed = await claimRequest(relay, requestId);
77
+ if (!claimed) {
78
+ console.log(` [skip] Could not claim ${requestId}`);
79
+ return;
80
+ }
81
+ console.log(` [run] ${message.slice(0, 60)}${message.length > 60 ? "..." : ""}`);
82
+ const adapter = getAdapter(gatewayType);
83
+ const eventBatch = [];
84
+ const flush = async () => {
85
+ if (eventBatch.length === 0)
86
+ return;
87
+ const batch = eventBatch.splice(0);
88
+ await pushEvents(relay, requestId, batch);
89
+ };
90
+ try {
91
+ const fullContent = await adapter.send(gatewayUrl, message, (event) => {
92
+ eventBatch.push(event);
93
+ if (eventBatch.length >= 5 ||
94
+ event.type === "tool_call" ||
95
+ event.type === "tool_result") {
96
+ flush();
97
+ }
98
+ });
99
+ await flush();
100
+ await completeRequest(relay, requestId, { content: fullContent });
101
+ console.log(` [done] ${requestId}`);
102
+ }
103
+ catch (err) {
104
+ const errorMsg = err instanceof Error ? err.message : String(err);
105
+ console.error(` [err] ${requestId}: ${errorMsg}`);
106
+ await flush();
107
+ await completeRequest(relay, requestId, { error: errorMsg });
108
+ }
109
+ }
110
+ export async function runBridge(args) {
111
+ const config = parseArgs(args);
112
+ if (!config.clawKey) {
113
+ console.error("Error: ClawKey is required. Use --key or set CLAW_KEY env var.");
114
+ console.error("Run with --help for usage.");
115
+ process.exit(1);
116
+ }
117
+ console.log(`\n ▄▀█ █▄░█ █▄█ █▀▀ █░░ ▄▀█ █░█░█`);
118
+ console.log(` █▀█ █░▀█ ░█░ █▄▄ █▄▄ █▀█ ▀▄▀▄▀`);
119
+ console.log(`\n AnyClaw Bridge v0.2.0`);
120
+ console.log(` Gateway: ${config.gatewayUrl} (${config.gatewayType})`);
121
+ console.log(` Service: ${config.serviceUrl}`);
122
+ console.log(` Key: ${config.clawKey.slice(0, 8)}...`);
123
+ console.log(`\n Waiting for messages from anyclaw.sh / anyclaw.dev ...\n`);
124
+ const relay = {
125
+ serviceUrl: config.serviceUrl,
126
+ clawKey: config.clawKey,
127
+ };
128
+ let pollInterval = MIN_POLL_MS;
129
+ let running = true;
130
+ process.on("SIGINT", () => {
131
+ console.log("\n Bridge stopped.");
132
+ running = false;
133
+ process.exit(0);
134
+ });
135
+ while (running) {
136
+ try {
137
+ const pending = await pollRequests(relay);
138
+ if (pending.length > 0) {
139
+ pollInterval = MIN_POLL_MS;
140
+ for (const req of pending) {
141
+ await processRequest(relay, config.gatewayUrl, config.gatewayType, req.requestId, req.message);
142
+ }
143
+ }
144
+ else {
145
+ pollInterval = Math.min(pollInterval * BACKOFF_FACTOR, MAX_POLL_MS);
146
+ }
147
+ }
148
+ catch (err) {
149
+ const msg = err instanceof Error ? err.message : String(err);
150
+ if (!msg.includes("ECONNREFUSED")) {
151
+ console.error(` [poll] ${msg}`);
152
+ }
153
+ pollInterval = MAX_POLL_MS;
154
+ }
155
+ await new Promise((resolve) => setTimeout(resolve, pollInterval));
156
+ }
157
+ }
@@ -0,0 +1 @@
1
+ export declare function runInstall(args: string[]): Promise<void>;
@@ -0,0 +1,111 @@
1
+ /**
2
+ * Install Command
3
+ *
4
+ * Helps users install various claw variants: paeanclaw, zeroclaw, openclaw, nanoclaw.
5
+ * Detects available runtimes and suggests the best install method.
6
+ */
7
+ import { execSync } from "child_process";
8
+ const C = "\x1b[36m";
9
+ const G = "\x1b[32m";
10
+ const Y = "\x1b[33m";
11
+ const R = "\x1b[31m";
12
+ const B = "\x1b[1m";
13
+ const D = "\x1b[0m";
14
+ function has(cmd) {
15
+ try {
16
+ execSync(`command -v ${cmd}`, { stdio: "ignore" });
17
+ return true;
18
+ }
19
+ catch {
20
+ return false;
21
+ }
22
+ }
23
+ function run(cmd) {
24
+ try {
25
+ execSync(cmd, { stdio: "inherit" });
26
+ return true;
27
+ }
28
+ catch {
29
+ return false;
30
+ }
31
+ }
32
+ const VARIANTS = {
33
+ paeanclaw: {
34
+ name: "PaeanClaw",
35
+ description: "Ultra-minimal local AI agent runtime (~365 lines). MCP tools, web PWA, Telegram.",
36
+ methods: [
37
+ { runtime: "bun", command: "bun install -g --no-optional paeanclaw", check: () => has("bun") },
38
+ { runtime: "npm", command: "npm install -g paeanclaw", check: () => has("npm") },
39
+ ],
40
+ },
41
+ zeroclaw: {
42
+ name: "ZeroClaw",
43
+ description: "Rust-based AI agent runtime. Extremely low resource usage (<5MB RAM).",
44
+ methods: [
45
+ { runtime: "cargo", command: "cargo install zeroclaw", check: () => has("cargo") },
46
+ { runtime: "npm", command: "npm install -g zeroclaw", check: () => has("npm") },
47
+ ],
48
+ },
49
+ openclaw: {
50
+ name: "OpenClaw",
51
+ description: "Full-featured Python AI agent. 60+ tools, 16+ platforms, voice mode.",
52
+ methods: [
53
+ { runtime: "pip", command: "pip install openclaw", check: () => has("pip") || has("pip3") },
54
+ { runtime: "pipx", command: "pipx install openclaw", check: () => has("pipx") },
55
+ ],
56
+ },
57
+ nanoclaw: {
58
+ name: "NanoClaw",
59
+ description: "Container-isolated AI agent. Docker/Apple Container sandboxing.",
60
+ methods: [
61
+ { runtime: "docker", command: "docker pull nanoclaw/nanoclaw", check: () => has("docker") },
62
+ { runtime: "npm", command: "npm install -g nanoclaw", check: () => has("npm") },
63
+ ],
64
+ },
65
+ };
66
+ function printVariants() {
67
+ console.log(`\n ${B}Available claw variants:${D}\n`);
68
+ for (const [key, v] of Object.entries(VARIANTS)) {
69
+ const runtimes = v.methods
70
+ .filter((m) => m.check())
71
+ .map((m) => m.runtime);
72
+ const available = runtimes.length > 0
73
+ ? `${G}available${D} (${runtimes.join(", ")})`
74
+ : `${Y}missing runtime${D}`;
75
+ console.log(` ${C}${key.padEnd(12)}${D} ${v.name} — ${v.description}`);
76
+ console.log(`${"".padEnd(15)}${available}`);
77
+ }
78
+ console.log(`\n Usage: ${B}anyclaw install <variant>${D}\n`);
79
+ }
80
+ export async function runInstall(args) {
81
+ const variant = args[0]?.toLowerCase();
82
+ if (!variant || variant === "--help" || variant === "-h") {
83
+ printVariants();
84
+ return;
85
+ }
86
+ const v = VARIANTS[variant];
87
+ if (!v) {
88
+ console.error(`${R}Unknown variant: ${variant}${D}`);
89
+ printVariants();
90
+ process.exit(1);
91
+ }
92
+ console.log(`\n ${B}Installing ${v.name}${D}`);
93
+ console.log(` ${v.description}\n`);
94
+ const method = v.methods.find((m) => m.check());
95
+ if (!method) {
96
+ const needed = v.methods.map((m) => m.runtime).join(" or ");
97
+ console.error(`${R}No compatible runtime found.${D} Install one of: ${needed}`);
98
+ process.exit(1);
99
+ }
100
+ console.log(` ${C}▸${D} Using ${method.runtime}: ${B}${method.command}${D}\n`);
101
+ const ok = run(method.command);
102
+ if (ok) {
103
+ console.log(`\n ${G}✓${D} ${v.name} installed successfully.`);
104
+ console.log(` Run ${C}anyclaw status${D} to verify.\n`);
105
+ }
106
+ else {
107
+ console.error(`\n ${R}✗${D} Installation failed. Try running the command manually:`);
108
+ console.error(` ${method.command}\n`);
109
+ process.exit(1);
110
+ }
111
+ }
@@ -0,0 +1 @@
1
+ export declare function runStatus(): Promise<void>;
@@ -0,0 +1,79 @@
1
+ /**
2
+ * Status Command
3
+ *
4
+ * Checks locally running AI agent gateways by probing common ports
5
+ * and checking for installed claw binaries.
6
+ */
7
+ import { execSync } from "child_process";
8
+ const C = "\x1b[36m";
9
+ const G = "\x1b[32m";
10
+ const Y = "\x1b[33m";
11
+ const R = "\x1b[31m";
12
+ const B = "\x1b[1m";
13
+ const D = "\x1b[0m";
14
+ const TARGETS = [
15
+ { name: "PaeanClaw", port: 3007, type: "claw", healthPath: "/" },
16
+ { name: "ZeroClaw", port: 42617, type: "claw", healthPath: "/" },
17
+ { name: "OpenAI-compat (LM Studio)", port: 1234, type: "openai", healthPath: "/v1/models" },
18
+ { name: "OpenAI-compat (vLLM)", port: 8080, type: "openai", healthPath: "/v1/models" },
19
+ { name: "Ollama", port: 11434, type: "openai", healthPath: "/v1/models" },
20
+ ];
21
+ function has(cmd) {
22
+ try {
23
+ return execSync(`command -v ${cmd}`, { encoding: "utf-8" }).trim();
24
+ }
25
+ catch {
26
+ return null;
27
+ }
28
+ }
29
+ async function probe(port, path) {
30
+ try {
31
+ const controller = new AbortController();
32
+ const timeout = setTimeout(() => controller.abort(), 2000);
33
+ const res = await fetch(`http://localhost:${port}${path}`, {
34
+ signal: controller.signal,
35
+ });
36
+ clearTimeout(timeout);
37
+ return res.ok || res.status < 500;
38
+ }
39
+ catch {
40
+ return false;
41
+ }
42
+ }
43
+ export async function runStatus() {
44
+ console.log(`\n ${B}AnyClaw Status${D}\n`);
45
+ // Check installed binaries
46
+ console.log(` ${B}Installed:${D}`);
47
+ const bins = [
48
+ { cmd: "paeanclaw", label: "PaeanClaw" },
49
+ { cmd: "zeroclaw", label: "ZeroClaw" },
50
+ { cmd: "openclaw", label: "OpenClaw" },
51
+ { cmd: "nanoclaw", label: "NanoClaw" },
52
+ ];
53
+ let anyInstalled = false;
54
+ for (const b of bins) {
55
+ const location = has(b.cmd);
56
+ if (location) {
57
+ console.log(` ${G}●${D} ${b.label.padEnd(14)} ${C}${location}${D}`);
58
+ anyInstalled = true;
59
+ }
60
+ }
61
+ if (!anyInstalled) {
62
+ console.log(` ${Y}(none found)${D} Run ${C}anyclaw install${D} to get started.`);
63
+ }
64
+ // Probe ports
65
+ console.log(`\n ${B}Running Gateways:${D}`);
66
+ let anyRunning = false;
67
+ for (const target of TARGETS) {
68
+ const running = await probe(target.port, target.healthPath);
69
+ if (running) {
70
+ console.log(` ${G}●${D} ${target.name.padEnd(28)} ${C}:${target.port}${D} (${target.type})`);
71
+ anyRunning = true;
72
+ }
73
+ }
74
+ if (!anyRunning) {
75
+ console.log(` ${R}●${D} No running gateways detected.`);
76
+ console.log(` Start a gateway, e.g.: ${C}paeanclaw${D} or ${C}zeroclaw${D}`);
77
+ }
78
+ console.log("");
79
+ }
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export {};
package/dist/index.js ADDED
@@ -0,0 +1,168 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * AnyClaw Bridge
4
+ *
5
+ * Standalone sidecar that connects ANY local AI agent gateway
6
+ * to the AnyClaw relay service. Zero modification to the agent required.
7
+ *
8
+ * Usage:
9
+ * anyclaw-bridge --gateway http://localhost:3007 --key ck_g_xxx
10
+ * anyclaw-bridge --gateway http://localhost:42617 --type zeroclaw --key ck_p_xxx
11
+ * anyclaw-bridge --gateway http://localhost:8080 --type openai --key ck_g_xxx
12
+ */
13
+ import { getAdapter } from "./adapters.js";
14
+ import { pollRequests, claimRequest, pushEvents, completeRequest, } from "./relay.js";
15
+ function parseArgs() {
16
+ const args = process.argv.slice(2);
17
+ const config = {
18
+ gatewayUrl: "http://localhost:3007",
19
+ gatewayType: "claw",
20
+ clawKey: "",
21
+ serviceUrl: "http://localhost:4777",
22
+ };
23
+ for (let i = 0; i < args.length; i++) {
24
+ switch (args[i]) {
25
+ case "--gateway":
26
+ case "-g":
27
+ config.gatewayUrl = args[++i];
28
+ break;
29
+ case "--type":
30
+ case "-t":
31
+ config.gatewayType = args[++i];
32
+ break;
33
+ case "--key":
34
+ case "-k":
35
+ config.clawKey = args[++i];
36
+ break;
37
+ case "--service":
38
+ case "-s":
39
+ config.serviceUrl = args[++i];
40
+ break;
41
+ case "--help":
42
+ case "-h":
43
+ printHelp();
44
+ process.exit(0);
45
+ }
46
+ }
47
+ config.clawKey = config.clawKey || process.env.CLAW_KEY || "";
48
+ config.serviceUrl =
49
+ config.serviceUrl || process.env.ANYCLAW_SERVICE_URL || "http://localhost:4777";
50
+ config.gatewayUrl =
51
+ config.gatewayUrl || process.env.GATEWAY_URL || "http://localhost:3007";
52
+ return config;
53
+ }
54
+ function printHelp() {
55
+ console.log(`
56
+ AnyClaw Bridge — connects your local AI agent to the AnyClaw relay
57
+
58
+ Usage:
59
+ anyclaw-bridge --gateway <url> --key <clawkey> [options]
60
+
61
+ Options:
62
+ -g, --gateway <url> Local agent gateway URL (default: http://localhost:3007)
63
+ -t, --type <type> Gateway type: claw, paeanclaw, zeroclaw, openai (default: claw)
64
+ -k, --key <key> ClawKey for relay authentication
65
+ -s, --service <url> AnyClaw service URL (default: http://localhost:4777)
66
+ -h, --help Show this help
67
+
68
+ Environment:
69
+ CLAW_KEY ClawKey (alternative to --key)
70
+ ANYCLAW_SERVICE_URL Service URL (alternative to --service)
71
+ GATEWAY_URL Gateway URL (alternative to --gateway)
72
+
73
+ Examples:
74
+ anyclaw-bridge -g http://localhost:3007 -k ck_g_abc123
75
+ anyclaw-bridge -g http://localhost:42617 -t zeroclaw -k ck_p_xyz789
76
+ `);
77
+ }
78
+ // ── Bridge Loop ────────────────────────────────────────
79
+ const MIN_POLL_MS = 500;
80
+ const MAX_POLL_MS = 5000;
81
+ const BACKOFF_FACTOR = 1.5;
82
+ async function processRequest(relay, gatewayUrl, gatewayType, requestId, message) {
83
+ const claimed = await claimRequest(relay, requestId);
84
+ if (!claimed) {
85
+ console.log(` [skip] Could not claim ${requestId}`);
86
+ return;
87
+ }
88
+ console.log(` [run] ${message.slice(0, 60)}${message.length > 60 ? "..." : ""}`);
89
+ const adapter = getAdapter(gatewayType);
90
+ const eventBatch = [];
91
+ const flush = async () => {
92
+ if (eventBatch.length === 0)
93
+ return;
94
+ const batch = eventBatch.splice(0);
95
+ await pushEvents(relay, requestId, batch);
96
+ };
97
+ try {
98
+ const fullContent = await adapter.send(gatewayUrl, message, (event) => {
99
+ eventBatch.push(event);
100
+ if (eventBatch.length >= 5 ||
101
+ event.type === "tool_call" ||
102
+ event.type === "tool_result") {
103
+ flush();
104
+ }
105
+ });
106
+ await flush();
107
+ await completeRequest(relay, requestId, { content: fullContent });
108
+ console.log(` [done] ${requestId}`);
109
+ }
110
+ catch (err) {
111
+ const errorMsg = err instanceof Error ? err.message : String(err);
112
+ console.error(` [err] ${requestId}: ${errorMsg}`);
113
+ await flush();
114
+ await completeRequest(relay, requestId, { error: errorMsg });
115
+ }
116
+ }
117
+ async function main() {
118
+ const config = parseArgs();
119
+ if (!config.clawKey) {
120
+ console.error("Error: ClawKey is required. Use --key or set CLAW_KEY env var.");
121
+ console.error("Run with --help for usage.");
122
+ process.exit(1);
123
+ }
124
+ console.log(`\n ▄▀█ █▄░█ █▄█ █▀▀ █░░ ▄▀█ █░█░█`);
125
+ console.log(` █▀█ █░▀█ ░█░ █▄▄ █▄▄ █▀█ ▀▄▀▄▀`);
126
+ console.log(`\n AnyClaw Bridge v0.1.0`);
127
+ console.log(` Gateway: ${config.gatewayUrl} (${config.gatewayType})`);
128
+ console.log(` Service: ${config.serviceUrl}`);
129
+ console.log(` Key: ${config.clawKey.slice(0, 8)}...`);
130
+ console.log(`\n Waiting for messages from anyclaw.sh / anyclaw.dev ...\n`);
131
+ const relay = {
132
+ serviceUrl: config.serviceUrl,
133
+ clawKey: config.clawKey,
134
+ };
135
+ let pollInterval = MIN_POLL_MS;
136
+ let running = true;
137
+ process.on("SIGINT", () => {
138
+ console.log("\n Bridge stopped.");
139
+ running = false;
140
+ process.exit(0);
141
+ });
142
+ while (running) {
143
+ try {
144
+ const pending = await pollRequests(relay);
145
+ if (pending.length > 0) {
146
+ pollInterval = MIN_POLL_MS;
147
+ for (const req of pending) {
148
+ await processRequest(relay, config.gatewayUrl, config.gatewayType, req.requestId, req.message);
149
+ }
150
+ }
151
+ else {
152
+ pollInterval = Math.min(pollInterval * BACKOFF_FACTOR, MAX_POLL_MS);
153
+ }
154
+ }
155
+ catch (err) {
156
+ const msg = err instanceof Error ? err.message : String(err);
157
+ if (!msg.includes("ECONNREFUSED")) {
158
+ console.error(` [poll] ${msg}`);
159
+ }
160
+ pollInterval = MAX_POLL_MS;
161
+ }
162
+ await new Promise((resolve) => setTimeout(resolve, pollInterval));
163
+ }
164
+ }
165
+ main().catch((err) => {
166
+ console.error("Fatal:", err);
167
+ process.exit(1);
168
+ });
@@ -0,0 +1,15 @@
1
+ export interface RelayConfig {
2
+ serviceUrl: string;
3
+ clawKey: string;
4
+ }
5
+ export interface PendingRequest {
6
+ requestId: string;
7
+ message: string;
8
+ }
9
+ export declare function pollRequests(config: RelayConfig): Promise<PendingRequest[]>;
10
+ export declare function claimRequest(config: RelayConfig, requestId: string): Promise<boolean>;
11
+ export declare function pushEvents(config: RelayConfig, requestId: string, events: unknown[]): Promise<void>;
12
+ export declare function completeRequest(config: RelayConfig, requestId: string, result: {
13
+ content?: string;
14
+ error?: string;
15
+ }): Promise<void>;
package/dist/relay.js ADDED
@@ -0,0 +1,49 @@
1
+ /**
2
+ * Relay Client
3
+ *
4
+ * Communicates with the AnyClaw Service (or zero-api) relay.
5
+ * Polls for incoming messages, claims them, pushes events, completes.
6
+ */
7
+ import crypto from "crypto";
8
+ const SESSION_ID = crypto.randomBytes(8).toString("hex");
9
+ async function relayFetch(config, path, options = {}) {
10
+ return fetch(`${config.serviceUrl.replace(/\/$/, "")}${path}`, {
11
+ headers: {
12
+ "Content-Type": "application/json",
13
+ "X-Claw-Key": config.clawKey,
14
+ ...options.headers,
15
+ },
16
+ ...options,
17
+ });
18
+ }
19
+ export async function pollRequests(config) {
20
+ const res = await relayFetch(config, "/claw/channel/poll", {
21
+ method: "POST",
22
+ body: JSON.stringify({ sessionId: SESSION_ID }),
23
+ });
24
+ if (!res.ok) {
25
+ const body = await res.json().catch(() => ({}));
26
+ throw new Error(`Poll failed: ${body.error || res.status}`);
27
+ }
28
+ const data = (await res.json());
29
+ return data.requests || [];
30
+ }
31
+ export async function claimRequest(config, requestId) {
32
+ const res = await relayFetch(config, `/claw/channel/claim/${requestId}`, {
33
+ method: "POST",
34
+ body: JSON.stringify({ sessionId: SESSION_ID }),
35
+ });
36
+ return res.ok;
37
+ }
38
+ export async function pushEvents(config, requestId, events) {
39
+ await relayFetch(config, `/claw/channel/events/${requestId}`, {
40
+ method: "POST",
41
+ body: JSON.stringify({ sessionId: SESSION_ID, events }),
42
+ });
43
+ }
44
+ export async function completeRequest(config, requestId, result) {
45
+ await relayFetch(config, `/claw/channel/complete/${requestId}`, {
46
+ method: "POST",
47
+ body: JSON.stringify({ sessionId: SESSION_ID, ...result }),
48
+ });
49
+ }
package/package.json ADDED
@@ -0,0 +1,45 @@
1
+ {
2
+ "name": "anyclaw",
3
+ "version": "0.2.0",
4
+ "description": "AnyClaw CLI — bridge, install, and manage local AI agent gateways",
5
+ "type": "module",
6
+ "bin": {
7
+ "anyclaw": "dist/cli.js",
8
+ "anyclaw-bridge": "dist/cli.js"
9
+ },
10
+ "main": "./dist/cli.js",
11
+ "files": [
12
+ "dist"
13
+ ],
14
+ "scripts": {
15
+ "build": "tsc",
16
+ "start": "node dist/cli.js",
17
+ "dev": "tsc && node dist/cli.js",
18
+ "prepublishOnly": "npm run build"
19
+ },
20
+ "keywords": [
21
+ "anyclaw",
22
+ "ai",
23
+ "agent",
24
+ "bridge",
25
+ "relay",
26
+ "mcp",
27
+ "paeanclaw",
28
+ "zeroclaw",
29
+ "openclaw",
30
+ "nanoclaw",
31
+ "cli"
32
+ ],
33
+ "repository": {
34
+ "type": "git",
35
+ "url": "https://github.com/paean-ai/anyclaw.git",
36
+ "directory": "bridge"
37
+ },
38
+ "homepage": "https://anyclaw.sh",
39
+ "license": "MIT",
40
+ "dependencies": {},
41
+ "devDependencies": {
42
+ "@types/node": "^22.0.0",
43
+ "typescript": "^5.7.0"
44
+ }
45
+ }