@songsid/agend 2.0.11-beta.16 → 2.0.11-beta.18
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/dist/channel/mcp-tools.js +3 -0
- package/dist/channel/mcp-tools.js.map +1 -1
- package/dist/channel/tool-router.js +29 -2
- package/dist/channel/tool-router.js.map +1 -1
- package/dist/cli.js +51 -0
- package/dist/cli.js.map +1 -1
- package/dist/config-validator.d.ts +20 -0
- package/dist/config-validator.js +146 -0
- package/dist/config-validator.js.map +1 -0
- package/dist/fleet-manager.js +13 -1
- package/dist/fleet-manager.js.map +1 -1
- package/dist/outbound-schemas.d.ts +1 -0
- package/dist/outbound-schemas.js +1 -0
- package/dist/outbound-schemas.js.map +1 -1
- package/dist/quickstart.js +5 -2
- package/dist/quickstart.js.map +1 -1
- package/dist/settings-api.d.ts +33 -0
- package/dist/settings-api.js +235 -0
- package/dist/settings-api.js.map +1 -0
- package/dist/ui/settings.html +217 -0
- package/package.json +1 -1
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="utf-8">
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
6
|
+
<title>AgEnD Settings</title>
|
|
7
|
+
<style>
|
|
8
|
+
* { margin: 0; padding: 0; box-sizing: border-box; }
|
|
9
|
+
:root { --bg:#0d1117; --panel:#161b22; --border:#21262d; --fg:#c9d1d9; --dim:#8b949e; --accent:#58a6ff; --err:#f85149; --warn:#d29922; --ok:#3fb950; }
|
|
10
|
+
html, body { height: 100%; }
|
|
11
|
+
body { background: var(--bg); color: var(--fg); font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; padding: 0 0 60px; }
|
|
12
|
+
header { display: flex; align-items: center; gap: 16px; padding: 14px 20px; background: var(--panel); border-bottom: 1px solid var(--border); position: sticky; top: 0; z-index: 10; }
|
|
13
|
+
header h1 { font-size: 15px; color: var(--accent); font-weight: 600; letter-spacing: .5px; }
|
|
14
|
+
.tabs { display: flex; gap: 4px; margin-left: 8px; }
|
|
15
|
+
.tab { padding: 6px 14px; border-radius: 6px; cursor: pointer; font-size: 13px; color: var(--dim); border: 1px solid transparent; }
|
|
16
|
+
.tab:hover { color: var(--fg); }
|
|
17
|
+
.tab.active { background: var(--bg); color: var(--accent); border-color: var(--border); }
|
|
18
|
+
.spacer { flex: 1; }
|
|
19
|
+
button { background: #21262d; color: var(--fg); border: 1px solid var(--border); border-radius: 6px; padding: 6px 12px; font-size: 13px; cursor: pointer; }
|
|
20
|
+
button:hover { border-color: var(--accent); }
|
|
21
|
+
button.primary { background: var(--accent); color: #05070a; border-color: var(--accent); font-weight: 600; }
|
|
22
|
+
button.danger { color: var(--err); }
|
|
23
|
+
main { max-width: 900px; margin: 0 auto; padding: 20px; }
|
|
24
|
+
.panel { display: none; }
|
|
25
|
+
.panel.active { display: block; }
|
|
26
|
+
.card { background: var(--panel); border: 1px solid var(--border); border-radius: 8px; padding: 16px; margin-bottom: 16px; }
|
|
27
|
+
.card h2 { font-size: 13px; color: var(--accent); margin-bottom: 4px; }
|
|
28
|
+
.card .hint { font-size: 12px; color: var(--dim); margin-bottom: 10px; }
|
|
29
|
+
.row { display: flex; align-items: center; justify-content: space-between; gap: 10px; margin-bottom: 8px; }
|
|
30
|
+
.row .name { font-size: 14px; font-weight: 600; }
|
|
31
|
+
.row .meta { font-size: 12px; color: var(--dim); }
|
|
32
|
+
textarea { width: 100%; background: var(--bg); color: var(--fg); border: 1px solid var(--border); border-radius: 6px; padding: 10px; font-family: ui-monospace, SFMono-Regular, Menlo, monospace; font-size: 12px; line-height: 1.5; resize: vertical; min-height: 90px; }
|
|
33
|
+
input[type=text] { background: var(--bg); color: var(--fg); border: 1px solid var(--border); border-radius: 6px; padding: 7px 10px; font-size: 13px; }
|
|
34
|
+
.actions { display: flex; gap: 8px; margin-top: 8px; align-items: center; }
|
|
35
|
+
.msg { font-size: 12px; margin-top: 6px; white-space: pre-wrap; }
|
|
36
|
+
.msg .e { color: var(--err); }
|
|
37
|
+
.msg .w { color: var(--warn); }
|
|
38
|
+
.msg .s { color: var(--ok); }
|
|
39
|
+
.banner { padding: 10px 20px; font-size: 13px; background: rgba(210,153,34,.12); border-bottom: 1px solid var(--border); color: var(--warn); display: none; }
|
|
40
|
+
</style>
|
|
41
|
+
</head>
|
|
42
|
+
<body>
|
|
43
|
+
<header>
|
|
44
|
+
<h1>⚙ AgEnD Settings</h1>
|
|
45
|
+
<div class="tabs">
|
|
46
|
+
<div class="tab active" data-tab="fleet">Fleet</div>
|
|
47
|
+
<div class="tab" data-tab="instances">Instances</div>
|
|
48
|
+
<div class="tab" data-tab="classic">ClassicBot</div>
|
|
49
|
+
</div>
|
|
50
|
+
<div class="spacer"></div>
|
|
51
|
+
<button id="reloadBtn" title="Apply config changes to running instances (SIGHUP)">↻ Reload fleet</button>
|
|
52
|
+
</header>
|
|
53
|
+
<div class="banner" id="banner"></div>
|
|
54
|
+
<main>
|
|
55
|
+
<!-- Fleet -->
|
|
56
|
+
<section class="panel active" id="panel-fleet">
|
|
57
|
+
<div class="card">
|
|
58
|
+
<h2>Defaults</h2>
|
|
59
|
+
<div class="hint">Fleet-wide defaults (backend, model, cost_guard, …). Edited fields are merged.</div>
|
|
60
|
+
<textarea id="fleetDefaults"></textarea>
|
|
61
|
+
<div class="actions"><button class="primary" onclick="saveDefaults()">Save defaults</button><span class="msg" id="msg-defaults"></span></div>
|
|
62
|
+
</div>
|
|
63
|
+
<div class="card">
|
|
64
|
+
<h2>Channels</h2>
|
|
65
|
+
<div class="hint">Adapter list — each needs <code>type</code> + <code>bot_token_env</code>; give each an <code>id</code> when you run more than one. Full array is replaced on save.</div>
|
|
66
|
+
<textarea id="fleetChannels" style="min-height:160px"></textarea>
|
|
67
|
+
<div class="actions"><button class="primary" onclick="saveChannels()">Save channels</button><span class="msg" id="msg-channels"></span></div>
|
|
68
|
+
</div>
|
|
69
|
+
</section>
|
|
70
|
+
|
|
71
|
+
<!-- Instances -->
|
|
72
|
+
<section class="panel" id="panel-instances">
|
|
73
|
+
<div class="card">
|
|
74
|
+
<h2>Add instance</h2>
|
|
75
|
+
<div class="actions">
|
|
76
|
+
<input type="text" id="newInstName" placeholder="instance name" style="flex:0 0 220px">
|
|
77
|
+
<button class="primary" onclick="addInstance()">Create</button>
|
|
78
|
+
<span class="msg" id="msg-newinst"></span>
|
|
79
|
+
</div>
|
|
80
|
+
</div>
|
|
81
|
+
<div id="instList"></div>
|
|
82
|
+
</section>
|
|
83
|
+
|
|
84
|
+
<!-- ClassicBot -->
|
|
85
|
+
<section class="panel" id="panel-classic">
|
|
86
|
+
<div class="card">
|
|
87
|
+
<h2>ClassicBot defaults</h2>
|
|
88
|
+
<div class="hint">backend, admin_users, allowed_guilds, allowed_groups, allowed_users. Merged on save.</div>
|
|
89
|
+
<textarea id="classicDefaults" style="min-height:160px"></textarea>
|
|
90
|
+
<div class="actions"><button class="primary" onclick="saveClassicDefaults()">Save classic defaults</button><span class="msg" id="msg-classic"></span></div>
|
|
91
|
+
</div>
|
|
92
|
+
</section>
|
|
93
|
+
</main>
|
|
94
|
+
<script>
|
|
95
|
+
(() => {
|
|
96
|
+
const token = new URLSearchParams(location.search).get("token") || "";
|
|
97
|
+
const q = (id) => document.getElementById(id);
|
|
98
|
+
let fleet = {}, classic = {};
|
|
99
|
+
|
|
100
|
+
async function api(path, opts = {}) {
|
|
101
|
+
const headers = Object.assign({ "X-Agend-Token": token }, opts.headers || {});
|
|
102
|
+
if (opts.body) headers["Content-Type"] = "application/json";
|
|
103
|
+
const r = await fetch(path, Object.assign({}, opts, { headers }));
|
|
104
|
+
let body = null; try { body = await r.json(); } catch {}
|
|
105
|
+
return { ok: r.ok, status: r.status, body };
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// Render a validation result into a message span.
|
|
109
|
+
function showMsg(el, res) {
|
|
110
|
+
const b = res.body || {};
|
|
111
|
+
let html = "";
|
|
112
|
+
if (b.errors && b.errors.length) html += b.errors.map(e => `<span class="e">✗ ${e.path ? e.path + ": " : ""}${escapeHtml(e.message)}</span>`).join("\n");
|
|
113
|
+
if (b.warnings && b.warnings.length) html += (html ? "\n" : "") + b.warnings.map(w => `<span class="w">⚠ ${w.path ? w.path + ": " : ""}${escapeHtml(w.message)}</span>`).join("\n");
|
|
114
|
+
if (res.ok && !(b.warnings && b.warnings.length)) html = `<span class="s">✓ Saved</span>`;
|
|
115
|
+
else if (res.ok && b.warnings && b.warnings.length) html = `<span class="s">✓ Saved</span>\n` + html;
|
|
116
|
+
else if (!res.ok && !html) html = `<span class="e">✗ ${escapeHtml((b && b.error) || ("HTTP " + res.status))}</span>`;
|
|
117
|
+
el.innerHTML = html;
|
|
118
|
+
}
|
|
119
|
+
const escapeHtml = (s) => String(s).replace(/[&<>]/g, c => ({ "&":"&", "<":"<", ">":">" }[c]));
|
|
120
|
+
const parseJson = (id) => { try { return { v: JSON.parse(q(id).value || "{}") }; } catch (e) { return { err: e.message }; } };
|
|
121
|
+
|
|
122
|
+
// ── Tabs ──
|
|
123
|
+
document.querySelectorAll(".tab").forEach(t => t.onclick = () => {
|
|
124
|
+
document.querySelectorAll(".tab").forEach(x => x.classList.toggle("active", x === t));
|
|
125
|
+
document.querySelectorAll(".panel").forEach(p => p.classList.toggle("active", p.id === "panel-" + t.dataset.tab));
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
// ── Load ──
|
|
129
|
+
async function loadAll() {
|
|
130
|
+
const f = await api("/api/settings/fleet");
|
|
131
|
+
if (!f.ok) { q("banner").style.display = "block"; q("banner").textContent = "Failed to load fleet config (check the token in the URL)."; return; }
|
|
132
|
+
fleet = f.body || {};
|
|
133
|
+
q("fleetDefaults").value = JSON.stringify(fleet.defaults || {}, null, 2);
|
|
134
|
+
q("fleetChannels").value = JSON.stringify(fleet.channels || (fleet.channel ? [fleet.channel] : []), null, 2);
|
|
135
|
+
renderInstances();
|
|
136
|
+
const c = await api("/api/settings/classic");
|
|
137
|
+
classic = c.body || {};
|
|
138
|
+
q("classicDefaults").value = JSON.stringify(classic.defaults || {}, null, 2);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
function renderInstances() {
|
|
142
|
+
const list = q("instList");
|
|
143
|
+
list.innerHTML = "";
|
|
144
|
+
const insts = fleet.instances || {};
|
|
145
|
+
for (const name of Object.keys(insts)) {
|
|
146
|
+
const card = document.createElement("div");
|
|
147
|
+
card.className = "card";
|
|
148
|
+
const inst = insts[name];
|
|
149
|
+
const tags = [inst.general_topic ? "general" : null, inst.backend || null, inst.channel_id ? "→" + inst.channel_id : null].filter(Boolean).join(" · ");
|
|
150
|
+
card.innerHTML = `
|
|
151
|
+
<div class="row"><span class="name">${escapeHtml(name)}</span><span class="meta">${escapeHtml(tags)}</span></div>
|
|
152
|
+
<textarea id="inst-${cssId(name)}"></textarea>
|
|
153
|
+
<div class="actions">
|
|
154
|
+
<button class="primary" data-act="save" data-name="${escapeHtml(name)}">Save</button>
|
|
155
|
+
<button class="danger" data-act="del" data-name="${escapeHtml(name)}">Delete</button>
|
|
156
|
+
<span class="msg" id="msg-inst-${cssId(name)}"></span>
|
|
157
|
+
</div>`;
|
|
158
|
+
list.appendChild(card);
|
|
159
|
+
q("inst-" + cssId(name)).value = JSON.stringify(inst, null, 2);
|
|
160
|
+
}
|
|
161
|
+
list.querySelectorAll("button[data-act]").forEach(btn => {
|
|
162
|
+
const name = btn.dataset.name;
|
|
163
|
+
btn.onclick = () => btn.dataset.act === "save" ? saveInstance(name) : deleteInstance(name);
|
|
164
|
+
});
|
|
165
|
+
}
|
|
166
|
+
const cssId = (s) => s.replace(/[^a-zA-Z0-9_-]/g, "_");
|
|
167
|
+
|
|
168
|
+
// ── Saves ──
|
|
169
|
+
window.saveDefaults = async () => {
|
|
170
|
+
const p = parseJson("fleetDefaults");
|
|
171
|
+
if (p.err) return q("msg-defaults").innerHTML = `<span class="e">✗ ${escapeHtml(p.err)}</span>`;
|
|
172
|
+
showMsg(q("msg-defaults"), await api("/api/settings/fleet/defaults", { method: "PUT", body: JSON.stringify(p.v) }));
|
|
173
|
+
};
|
|
174
|
+
window.saveChannels = async () => {
|
|
175
|
+
const p = parseJson("fleetChannels");
|
|
176
|
+
if (p.err) return q("msg-channels").innerHTML = `<span class="e">✗ ${escapeHtml(p.err)}</span>`;
|
|
177
|
+
const res = await api("/api/settings/fleet/channels", { method: "PUT", body: JSON.stringify(p.v) });
|
|
178
|
+
showMsg(q("msg-channels"), res);
|
|
179
|
+
if (res.ok) loadAll();
|
|
180
|
+
};
|
|
181
|
+
window.saveClassicDefaults = async () => {
|
|
182
|
+
const p = parseJson("classicDefaults");
|
|
183
|
+
if (p.err) return q("msg-classic").innerHTML = `<span class="e">✗ ${escapeHtml(p.err)}</span>`;
|
|
184
|
+
showMsg(q("msg-classic"), await api("/api/settings/classic/defaults", { method: "PUT", body: JSON.stringify(p.v) }));
|
|
185
|
+
};
|
|
186
|
+
window.saveInstance = async (name) => {
|
|
187
|
+
const p = parseJson("inst-" + cssId(name));
|
|
188
|
+
const el = q("msg-inst-" + cssId(name));
|
|
189
|
+
if (p.err) return el.innerHTML = `<span class="e">✗ ${escapeHtml(p.err)}</span>`;
|
|
190
|
+
const res = await api(`/api/settings/fleet/instances/${encodeURIComponent(name)}`, { method: "PATCH", body: JSON.stringify(p.v) });
|
|
191
|
+
showMsg(el, res);
|
|
192
|
+
if (res.ok) { const f = await api("/api/settings/fleet"); fleet = f.body || fleet; }
|
|
193
|
+
};
|
|
194
|
+
window.deleteInstance = async (name) => {
|
|
195
|
+
if (!confirm(`Delete instance "${name}"? (does not stop a running daemon until you Reload)`)) return;
|
|
196
|
+
const res = await api(`/api/settings/fleet/instances/${encodeURIComponent(name)}`, { method: "DELETE" });
|
|
197
|
+
if (res.ok) { await loadAll(); } else { q("msg-inst-" + cssId(name)).innerHTML = `<span class="e">✗ ${escapeHtml((res.body && res.body.error) || res.status)}</span>`; }
|
|
198
|
+
};
|
|
199
|
+
window.addInstance = async () => {
|
|
200
|
+
const name = q("newInstName").value.trim();
|
|
201
|
+
if (!name) return q("msg-newinst").innerHTML = `<span class="e">✗ name required</span>`;
|
|
202
|
+
const res = await api(`/api/settings/fleet/instances/${encodeURIComponent(name)}`, { method: "POST", body: JSON.stringify({ working_directory: "" }) });
|
|
203
|
+
showMsg(q("msg-newinst"), res);
|
|
204
|
+
if (res.ok) { q("newInstName").value = ""; await loadAll(); }
|
|
205
|
+
};
|
|
206
|
+
q("reloadBtn").onclick = async () => {
|
|
207
|
+
const res = await api("/api/settings/reload", { method: "POST" });
|
|
208
|
+
q("banner").style.display = "block";
|
|
209
|
+
q("banner").textContent = res.ok ? "✓ Reload signal sent — instances reconciling." : "Reload failed.";
|
|
210
|
+
setTimeout(() => q("banner").style.display = "none", 4000);
|
|
211
|
+
};
|
|
212
|
+
|
|
213
|
+
loadAll();
|
|
214
|
+
})();
|
|
215
|
+
</script>
|
|
216
|
+
</body>
|
|
217
|
+
</html>
|