@perkos/perkos-a2a 0.8.35 → 0.9.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 +99 -21
- package/dist/agent.d.ts +14 -0
- package/dist/agent.d.ts.map +1 -0
- package/dist/agent.js +363 -0
- package/dist/agent.js.map +1 -0
- package/dist/hermes-cli.d.ts +30 -0
- package/dist/hermes-cli.d.ts.map +1 -0
- package/dist/hermes-cli.js +120 -0
- package/dist/hermes-cli.js.map +1 -0
- package/dist/hermes-plugin.d.ts +91 -0
- package/dist/hermes-plugin.d.ts.map +1 -0
- package/dist/hermes-plugin.js +196 -0
- package/dist/hermes-plugin.js.map +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1022 -26
- package/dist/index.js.map +4 -4
- package/dist/types.d.ts +36 -1
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +4 -1
- package/dist/types.js.map +1 -1
- package/docs/chat-client.md +190 -0
- package/docs/demo-setup.md +273 -0
- package/openclaw.plugin.json +38 -1
- package/package.json +33 -1
- package/scripts/hermes/install.mjs +211 -0
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* install-hermes — managed-service install helper for the
|
|
4
|
+
* Hermes-flavoured PerkOS A2A bridge.
|
|
5
|
+
*
|
|
6
|
+
* Goal: parity with `openclaw plugins install @perkos/perkos-a2a`. The
|
|
7
|
+
* OpenClaw flow drops a plugin into the runtime and the runtime
|
|
8
|
+
* supervises it. Hermes has no plugin host, so the equivalent is a
|
|
9
|
+
* systemd unit (Linux) or a launchd plist (macOS) that supervises the
|
|
10
|
+
* `perkos-a2a-hermes` CLI alongside the Hermes API Server.
|
|
11
|
+
*
|
|
12
|
+
* Two modes:
|
|
13
|
+
* --emit Print the unit/plist to stdout. Use when you want to pipe
|
|
14
|
+
* into your own deploy tooling.
|
|
15
|
+
* --write Write to the conventional system location. Requires root
|
|
16
|
+
* on Linux; on macOS writes to ~/Library/LaunchAgents.
|
|
17
|
+
*
|
|
18
|
+
* Required:
|
|
19
|
+
* --agent-name <name>
|
|
20
|
+
*
|
|
21
|
+
* Common:
|
|
22
|
+
* --relay-url <wss> Transport relay URL
|
|
23
|
+
* --relay-key <key> Transport relay API key
|
|
24
|
+
* --hermes-url <url> Hermes API base URL (default http://127.0.0.1:8642)
|
|
25
|
+
* --port <n> Local A2A port (default 5060)
|
|
26
|
+
* --user <name> systemd User= (default current $USER)
|
|
27
|
+
*
|
|
28
|
+
* Pre-flight: confirms the Hermes API responds before emitting the
|
|
29
|
+
* unit. Skip with --skip-preflight.
|
|
30
|
+
*/
|
|
31
|
+
|
|
32
|
+
import { writeFileSync, mkdirSync, existsSync, statSync, chmodSync } from "node:fs";
|
|
33
|
+
import { homedir, platform, userInfo } from "node:os";
|
|
34
|
+
import { join, resolve } from "node:path";
|
|
35
|
+
|
|
36
|
+
function argValue(flag) {
|
|
37
|
+
const idx = process.argv.indexOf(flag);
|
|
38
|
+
if (idx === -1) return undefined;
|
|
39
|
+
return process.argv[idx + 1];
|
|
40
|
+
}
|
|
41
|
+
function hasFlag(flag) { return process.argv.includes(flag); }
|
|
42
|
+
|
|
43
|
+
function fail(msg, code = 1) {
|
|
44
|
+
console.error(`install-hermes: ${msg}`);
|
|
45
|
+
process.exit(code);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function help() {
|
|
49
|
+
console.log(`install-hermes — managed-service install for perkos-a2a-hermes.
|
|
50
|
+
|
|
51
|
+
Usage:
|
|
52
|
+
install-hermes --emit --agent-name Apollo [...]
|
|
53
|
+
install-hermes --write --agent-name Apollo [...]
|
|
54
|
+
|
|
55
|
+
Required:
|
|
56
|
+
--agent-name <name>
|
|
57
|
+
|
|
58
|
+
Common:
|
|
59
|
+
--relay-url <wss> Transport relay URL (or A2A_RELAY_URL)
|
|
60
|
+
--relay-key <key> Transport relay API key (or A2A_RELAY_API_KEY)
|
|
61
|
+
--hermes-url <url> Hermes API base URL (default http://127.0.0.1:8642)
|
|
62
|
+
--hermes-token <token> Hermes API bearer token (or HERMES_API_KEY)
|
|
63
|
+
--port <n> Local A2A port (default 5060)
|
|
64
|
+
--user <name> systemd User= (default \$USER)
|
|
65
|
+
--skip-preflight Skip the Hermes API reachability check
|
|
66
|
+
|
|
67
|
+
Output:
|
|
68
|
+
Linux: systemd unit at /etc/systemd/system/perkos-a2a-hermes.service
|
|
69
|
+
macOS: launchd plist at ~/Library/LaunchAgents/xyz.perkos.a2a-hermes.plist
|
|
70
|
+
`);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
if (hasFlag("-h") || hasFlag("--help")) { help(); process.exit(0); }
|
|
74
|
+
const mode = hasFlag("--emit") ? "emit" : hasFlag("--write") ? "write" : null;
|
|
75
|
+
if (!mode) fail("specify --emit or --write (see --help)", 2);
|
|
76
|
+
|
|
77
|
+
const agentName = argValue("--agent-name");
|
|
78
|
+
if (!agentName) fail("--agent-name is required", 2);
|
|
79
|
+
const port = Number(argValue("--port") || 5060);
|
|
80
|
+
if (!Number.isFinite(port) || port <= 0) fail(`invalid --port ${argValue("--port")}`, 2);
|
|
81
|
+
const relayUrl = argValue("--relay-url") || process.env.A2A_RELAY_URL;
|
|
82
|
+
const relayKey = argValue("--relay-key") || process.env.A2A_RELAY_API_KEY;
|
|
83
|
+
const hermesUrl = argValue("--hermes-url") || process.env.HERMES_API_URL || "http://127.0.0.1:8642";
|
|
84
|
+
const hermesToken = argValue("--hermes-token") || process.env.HERMES_API_KEY;
|
|
85
|
+
const user = argValue("--user") || userInfo().username;
|
|
86
|
+
|
|
87
|
+
async function preflight() {
|
|
88
|
+
if (hasFlag("--skip-preflight")) return;
|
|
89
|
+
let res;
|
|
90
|
+
try {
|
|
91
|
+
res = await fetch(`${hermesUrl.replace(/\/+$/, "")}/v1/responses`, {
|
|
92
|
+
method: "OPTIONS",
|
|
93
|
+
});
|
|
94
|
+
} catch (err) {
|
|
95
|
+
fail(`Hermes API not reachable at ${hermesUrl} (${err.message}). Re-run with --skip-preflight if intentional.`);
|
|
96
|
+
}
|
|
97
|
+
// Any response — including 405 Method Not Allowed — proves something is listening.
|
|
98
|
+
if (!res) fail(`Hermes API check returned no response. Re-run with --skip-preflight if intentional.`);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
function unitLinux() {
|
|
102
|
+
const env = [
|
|
103
|
+
`Environment=A2A_AGENT_NAME=${agentName}`,
|
|
104
|
+
`Environment=A2A_PORT=${port}`,
|
|
105
|
+
`Environment=HERMES_API_URL=${hermesUrl}`,
|
|
106
|
+
];
|
|
107
|
+
if (relayUrl) env.push(`Environment=A2A_RELAY_URL=${relayUrl}`);
|
|
108
|
+
if (relayKey) env.push(`Environment=A2A_RELAY_API_KEY=${relayKey}`);
|
|
109
|
+
if (hermesToken) env.push(`Environment=HERMES_API_KEY=${hermesToken}`);
|
|
110
|
+
|
|
111
|
+
return `[Unit]
|
|
112
|
+
Description=PerkOS A2A bridge (Hermes runtime)
|
|
113
|
+
After=network-online.target
|
|
114
|
+
Wants=network-online.target
|
|
115
|
+
|
|
116
|
+
[Service]
|
|
117
|
+
Type=simple
|
|
118
|
+
User=${user}
|
|
119
|
+
ExecStart=${process.execPath} ${resolve(process.argv[1], "..", "..", "..", "dist", "hermes-cli.js")}
|
|
120
|
+
Restart=on-failure
|
|
121
|
+
RestartSec=5
|
|
122
|
+
${env.join("\n")}
|
|
123
|
+
|
|
124
|
+
[Install]
|
|
125
|
+
WantedBy=default.target
|
|
126
|
+
`;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
function plistMac() {
|
|
130
|
+
const envPairs = [
|
|
131
|
+
["A2A_AGENT_NAME", agentName],
|
|
132
|
+
["A2A_PORT", String(port)],
|
|
133
|
+
["HERMES_API_URL", hermesUrl],
|
|
134
|
+
];
|
|
135
|
+
if (relayUrl) envPairs.push(["A2A_RELAY_URL", relayUrl]);
|
|
136
|
+
if (relayKey) envPairs.push(["A2A_RELAY_API_KEY", relayKey]);
|
|
137
|
+
if (hermesToken) envPairs.push(["HERMES_API_KEY", hermesToken]);
|
|
138
|
+
|
|
139
|
+
const envXml = envPairs
|
|
140
|
+
.map(([k, v]) => ` <key>${k}</key>\n <string>${escapeXml(v)}</string>`)
|
|
141
|
+
.join("\n");
|
|
142
|
+
|
|
143
|
+
const cliPath = resolve(process.argv[1], "..", "..", "..", "dist", "hermes-cli.js");
|
|
144
|
+
|
|
145
|
+
return `<?xml version="1.0" encoding="UTF-8"?>
|
|
146
|
+
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
147
|
+
<plist version="1.0">
|
|
148
|
+
<dict>
|
|
149
|
+
<key>Label</key>
|
|
150
|
+
<string>xyz.perkos.a2a-hermes</string>
|
|
151
|
+
<key>ProgramArguments</key>
|
|
152
|
+
<array>
|
|
153
|
+
<string>${process.execPath}</string>
|
|
154
|
+
<string>${cliPath}</string>
|
|
155
|
+
</array>
|
|
156
|
+
<key>RunAtLoad</key>
|
|
157
|
+
<true/>
|
|
158
|
+
<key>KeepAlive</key>
|
|
159
|
+
<true/>
|
|
160
|
+
<key>EnvironmentVariables</key>
|
|
161
|
+
<dict>
|
|
162
|
+
${envXml}
|
|
163
|
+
</dict>
|
|
164
|
+
<key>StandardOutPath</key>
|
|
165
|
+
<string>${homedir()}/Library/Logs/perkos-a2a-hermes.log</string>
|
|
166
|
+
<key>StandardErrorPath</key>
|
|
167
|
+
<string>${homedir()}/Library/Logs/perkos-a2a-hermes.err.log</string>
|
|
168
|
+
</dict>
|
|
169
|
+
</plist>
|
|
170
|
+
`;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
function escapeXml(s) {
|
|
174
|
+
return String(s)
|
|
175
|
+
.replace(/&/g, "&")
|
|
176
|
+
.replace(/</g, "<")
|
|
177
|
+
.replace(/>/g, ">")
|
|
178
|
+
.replace(/"/g, """)
|
|
179
|
+
.replace(/'/g, "'");
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
async function main() {
|
|
183
|
+
await preflight();
|
|
184
|
+
const os = platform();
|
|
185
|
+
const content = os === "darwin" ? plistMac() : unitLinux();
|
|
186
|
+
if (mode === "emit") {
|
|
187
|
+
process.stdout.write(content);
|
|
188
|
+
return;
|
|
189
|
+
}
|
|
190
|
+
// mode === "write"
|
|
191
|
+
let target;
|
|
192
|
+
if (os === "darwin") {
|
|
193
|
+
const dir = join(homedir(), "Library", "LaunchAgents");
|
|
194
|
+
if (!existsSync(dir)) mkdirSync(dir, { recursive: true });
|
|
195
|
+
target = join(dir, "xyz.perkos.a2a-hermes.plist");
|
|
196
|
+
writeFileSync(target, content, { mode: 0o644 });
|
|
197
|
+
console.log(`wrote ${target}`);
|
|
198
|
+
console.log("activate: launchctl load -w " + target);
|
|
199
|
+
} else {
|
|
200
|
+
target = "/etc/systemd/system/perkos-a2a-hermes.service";
|
|
201
|
+
try {
|
|
202
|
+
writeFileSync(target, content, { mode: 0o644 });
|
|
203
|
+
console.log(`wrote ${target}`);
|
|
204
|
+
console.log("activate: sudo systemctl daemon-reload && sudo systemctl enable --now perkos-a2a-hermes");
|
|
205
|
+
} catch (err) {
|
|
206
|
+
fail(`cannot write ${target} (${err.message}). Re-run with sudo, or use --emit and pipe to sudo tee.`);
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
main().catch((err) => fail(err.message ?? String(err)));
|