agentlife 1.0.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/dev-dashboard.ts +238 -0
- package/index.test.ts +1905 -0
- package/index.ts +1433 -0
- package/openclaw.plugin.json +16 -0
- package/package.json +11 -0
package/dev-dashboard.ts
ADDED
|
@@ -0,0 +1,238 @@
|
|
|
1
|
+
#!/usr/bin/env bun
|
|
2
|
+
/**
|
|
3
|
+
* Dev Dashboard — Plugin Deploy & Gateway Control
|
|
4
|
+
*
|
|
5
|
+
* Automates the plugin dev loop: deploy plugin → restart gateway → reset sessions.
|
|
6
|
+
* Run: bun plugin/dev-dashboard.ts
|
|
7
|
+
* Open: http://localhost:3333
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { $ } from "bun";
|
|
11
|
+
import { readFileSync } from "node:fs";
|
|
12
|
+
import { homedir } from "node:os";
|
|
13
|
+
import { join } from "node:path";
|
|
14
|
+
|
|
15
|
+
const PORT = 3333;
|
|
16
|
+
const PLUGIN_SRC = join(import.meta.dir, "index.ts");
|
|
17
|
+
const PLUGIN_DEST = join(homedir(), ".openclaw/extensions/tenazitas/index.ts");
|
|
18
|
+
const OPENCLAW_DIR = join(import.meta.dir, "..", "..", "openclaw");
|
|
19
|
+
const OPENCLAW = "pnpm openclaw";
|
|
20
|
+
|
|
21
|
+
// ── Helpers ──────────────────────────────────────────────────────────────
|
|
22
|
+
|
|
23
|
+
async function run(cmd: string, cwd?: string): Promise<{ ok: boolean; output: string }> {
|
|
24
|
+
try {
|
|
25
|
+
const result = await $`bash -lc ${cmd}`.cwd(cwd ?? OPENCLAW_DIR).quiet();
|
|
26
|
+
return { ok: true, output: result.text() };
|
|
27
|
+
} catch (e: any) {
|
|
28
|
+
return { ok: false, output: e?.stderr?.toString() || e?.stdout?.toString() || String(e) };
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
async function getAgents(): Promise<string[]> {
|
|
33
|
+
const result = await run(`${OPENCLAW} gateway call agents.list --json`);
|
|
34
|
+
if (!result.ok) return [];
|
|
35
|
+
try {
|
|
36
|
+
// The JSON output may have preamble lines (doctor warnings etc). Find the JSON object.
|
|
37
|
+
const jsonStart = result.output.indexOf("{");
|
|
38
|
+
if (jsonStart === -1) return [];
|
|
39
|
+
const parsed = JSON.parse(result.output.slice(jsonStart));
|
|
40
|
+
return (parsed.agents ?? []).map((a: { id: string }) => a.id);
|
|
41
|
+
} catch {
|
|
42
|
+
return [];
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
async function deploy(): Promise<{ ok: boolean; output: string }> {
|
|
47
|
+
return run(`mkdir -p ${JSON.stringify(join(PLUGIN_DEST, ".."))} && cp ${JSON.stringify(PLUGIN_SRC)} ${JSON.stringify(PLUGIN_DEST)}`);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
async function restart(): Promise<{ ok: boolean; output: string }> {
|
|
51
|
+
await run("pkill -f 'openclaw gateway' || true");
|
|
52
|
+
// Small delay to let the process die
|
|
53
|
+
await Bun.sleep(500);
|
|
54
|
+
const result = await run(
|
|
55
|
+
`nohup ${OPENCLAW} gateway run --bind loopback --port 18789 --force > /tmp/openclaw-gateway.log 2>&1 &`,
|
|
56
|
+
);
|
|
57
|
+
return { ok: true, output: result.output || "Gateway restart initiated. Check /tmp/openclaw-gateway.log" };
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
async function resetSessions(): Promise<{ ok: boolean; output: string }> {
|
|
61
|
+
const agents = await getAgents();
|
|
62
|
+
if (agents.length === 0) {
|
|
63
|
+
return { ok: false, output: "No agents found. Is the gateway running?" };
|
|
64
|
+
}
|
|
65
|
+
const results: string[] = [];
|
|
66
|
+
for (const agentId of agents) {
|
|
67
|
+
const key = `agent:${agentId}:main`;
|
|
68
|
+
const r = await run(
|
|
69
|
+
`${OPENCLAW} gateway call sessions.reset --params '${JSON.stringify({ key })}'`,
|
|
70
|
+
);
|
|
71
|
+
results.push(`${agentId}: ${r.ok ? "reset" : "failed"}`);
|
|
72
|
+
}
|
|
73
|
+
return { ok: true, output: results.join("\n") };
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
async function waitForGateway(maxWaitMs = 30_000): Promise<boolean> {
|
|
77
|
+
const start = Date.now();
|
|
78
|
+
while (Date.now() - start < maxWaitMs) {
|
|
79
|
+
const r = await run(`${OPENCLAW} gateway call agents.list --json`);
|
|
80
|
+
if (r.ok && r.output.includes('"agents"')) return true;
|
|
81
|
+
await Bun.sleep(2000);
|
|
82
|
+
}
|
|
83
|
+
return false;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
async function allInOne(): Promise<{ ok: boolean; output: string }> {
|
|
87
|
+
const logs: string[] = [];
|
|
88
|
+
|
|
89
|
+
const d = await deploy();
|
|
90
|
+
logs.push(`[deploy] ${d.ok ? "OK" : "FAIL"}: ${d.output.trim()}`);
|
|
91
|
+
if (!d.ok) return { ok: false, output: logs.join("\n") };
|
|
92
|
+
|
|
93
|
+
const r = await restart();
|
|
94
|
+
logs.push(`[restart] ${r.ok ? "OK" : "FAIL"}: ${r.output.trim()}`);
|
|
95
|
+
|
|
96
|
+
logs.push("[wait] Polling gateway readiness (up to 30s)...");
|
|
97
|
+
const ready = await waitForGateway();
|
|
98
|
+
if (!ready) {
|
|
99
|
+
logs.push("[wait] FAIL: Gateway did not become ready in 30s");
|
|
100
|
+
return { ok: false, output: logs.join("\n") };
|
|
101
|
+
}
|
|
102
|
+
logs.push("[wait] Gateway is ready");
|
|
103
|
+
|
|
104
|
+
const s = await resetSessions();
|
|
105
|
+
logs.push(`[reset] ${s.ok ? "OK" : "FAIL"}:\n${s.output.trim()}`);
|
|
106
|
+
|
|
107
|
+
return { ok: d.ok && r.ok && s.ok, output: logs.join("\n") };
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// ── HTML ─────────────────────────────────────────────────────────────────
|
|
111
|
+
|
|
112
|
+
const HTML = `<!DOCTYPE html>
|
|
113
|
+
<html lang="en">
|
|
114
|
+
<head>
|
|
115
|
+
<meta charset="utf-8">
|
|
116
|
+
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
117
|
+
<title>Tenazitas Dev Dashboard</title>
|
|
118
|
+
<style>
|
|
119
|
+
* { box-sizing: border-box; margin: 0; padding: 0; }
|
|
120
|
+
body {
|
|
121
|
+
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, monospace;
|
|
122
|
+
background: #0d1117;
|
|
123
|
+
color: #e6edf3;
|
|
124
|
+
padding: 2rem;
|
|
125
|
+
max-width: 720px;
|
|
126
|
+
margin: 0 auto;
|
|
127
|
+
}
|
|
128
|
+
h1 { font-size: 1.4rem; margin-bottom: 1.5rem; color: #58a6ff; }
|
|
129
|
+
.grid { display: grid; grid-template-columns: 1fr 1fr; gap: 0.75rem; margin-bottom: 1rem; }
|
|
130
|
+
.full { grid-column: 1 / -1; }
|
|
131
|
+
button {
|
|
132
|
+
padding: 0.75rem 1rem;
|
|
133
|
+
border: 1px solid #30363d;
|
|
134
|
+
border-radius: 8px;
|
|
135
|
+
background: #161b22;
|
|
136
|
+
color: #e6edf3;
|
|
137
|
+
font-size: 0.9rem;
|
|
138
|
+
font-weight: 500;
|
|
139
|
+
cursor: pointer;
|
|
140
|
+
transition: background 0.15s, border-color 0.15s;
|
|
141
|
+
}
|
|
142
|
+
button:hover { background: #1f2937; border-color: #58a6ff; }
|
|
143
|
+
button:disabled { opacity: 0.5; cursor: not-allowed; }
|
|
144
|
+
button.all { background: #1a3a2a; border-color: #238636; }
|
|
145
|
+
button.all:hover { background: #244a34; }
|
|
146
|
+
button.running { border-color: #d29922; }
|
|
147
|
+
#output {
|
|
148
|
+
background: #010409;
|
|
149
|
+
border: 1px solid #30363d;
|
|
150
|
+
border-radius: 8px;
|
|
151
|
+
padding: 1rem;
|
|
152
|
+
font-family: "SF Mono", "Fira Code", monospace;
|
|
153
|
+
font-size: 0.8rem;
|
|
154
|
+
line-height: 1.5;
|
|
155
|
+
white-space: pre-wrap;
|
|
156
|
+
word-break: break-word;
|
|
157
|
+
min-height: 120px;
|
|
158
|
+
max-height: 400px;
|
|
159
|
+
overflow-y: auto;
|
|
160
|
+
color: #8b949e;
|
|
161
|
+
}
|
|
162
|
+
.paths {
|
|
163
|
+
margin-top: 1.5rem;
|
|
164
|
+
font-size: 0.75rem;
|
|
165
|
+
color: #484f58;
|
|
166
|
+
line-height: 1.8;
|
|
167
|
+
}
|
|
168
|
+
.paths code { color: #7d8590; }
|
|
169
|
+
</style>
|
|
170
|
+
</head>
|
|
171
|
+
<body>
|
|
172
|
+
<h1>Tenazitas Dev Dashboard</h1>
|
|
173
|
+
<div class="grid">
|
|
174
|
+
<button onclick="fire('deploy')">Deploy Plugin</button>
|
|
175
|
+
<button onclick="fire('restart')">Restart Gateway</button>
|
|
176
|
+
<button onclick="fire('reset')">Reset Sessions</button>
|
|
177
|
+
<button class="all full" onclick="fire('all')">All-in-One (Deploy + Restart + Reset)</button>
|
|
178
|
+
</div>
|
|
179
|
+
<div id="output">Ready.</div>
|
|
180
|
+
<div class="paths">
|
|
181
|
+
<div>Plugin: <code>${PLUGIN_SRC.replace(homedir(), "~")}</code></div>
|
|
182
|
+
<div>Target: <code>${PLUGIN_DEST.replace(homedir(), "~")}</code></div>
|
|
183
|
+
<div>Gateway log: <code>/tmp/openclaw-gateway.log</code></div>
|
|
184
|
+
</div>
|
|
185
|
+
<script>
|
|
186
|
+
async function fire(action) {
|
|
187
|
+
const out = document.getElementById("output");
|
|
188
|
+
const btns = document.querySelectorAll("button");
|
|
189
|
+
btns.forEach(b => { b.disabled = true; b.classList.add("running"); });
|
|
190
|
+
out.textContent = "Running " + action + "...\\n";
|
|
191
|
+
try {
|
|
192
|
+
const res = await fetch("/api/" + action, { method: "POST" });
|
|
193
|
+
const data = await res.json();
|
|
194
|
+
out.textContent = (data.ok ? "OK" : "FAILED") + "\\n\\n" + data.output;
|
|
195
|
+
} catch (e) {
|
|
196
|
+
out.textContent = "Error: " + e.message;
|
|
197
|
+
} finally {
|
|
198
|
+
btns.forEach(b => { b.disabled = false; b.classList.remove("running"); });
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
</script>
|
|
202
|
+
</body>
|
|
203
|
+
</html>`;
|
|
204
|
+
|
|
205
|
+
// ── Server ───────────────────────────────────────────────────────────────
|
|
206
|
+
|
|
207
|
+
const handlers: Record<string, () => Promise<{ ok: boolean; output: string }>> = {
|
|
208
|
+
deploy,
|
|
209
|
+
restart,
|
|
210
|
+
reset: resetSessions,
|
|
211
|
+
all: allInOne,
|
|
212
|
+
};
|
|
213
|
+
|
|
214
|
+
Bun.serve({
|
|
215
|
+
port: PORT,
|
|
216
|
+
idleTimeout: 120,
|
|
217
|
+
async fetch(req) {
|
|
218
|
+
const url = new URL(req.url);
|
|
219
|
+
|
|
220
|
+
if (url.pathname === "/" || url.pathname === "") {
|
|
221
|
+
return new Response(HTML, { headers: { "content-type": "text/html" } });
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
if (req.method === "POST" && url.pathname.startsWith("/api/")) {
|
|
225
|
+
const action = url.pathname.replace("/api/", "");
|
|
226
|
+
const handler = handlers[action];
|
|
227
|
+
if (!handler) {
|
|
228
|
+
return Response.json({ ok: false, output: `Unknown action: ${action}` }, { status: 404 });
|
|
229
|
+
}
|
|
230
|
+
const result = await handler();
|
|
231
|
+
return Response.json(result);
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
return new Response("Not found", { status: 404 });
|
|
235
|
+
},
|
|
236
|
+
});
|
|
237
|
+
|
|
238
|
+
console.log(`\n Tenazitas Dev Dashboard → http://localhost:${PORT}\n`);
|