@cotal-ai/cli 0.4.0 → 0.5.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/dist/commands/completion.d.ts +1 -1
- package/dist/commands/completion.d.ts.map +1 -1
- package/dist/commands/completion.js +79 -7
- package/dist/commands/completion.js.map +1 -1
- package/dist/commands/personas.d.ts.map +1 -1
- package/dist/commands/personas.js +77 -7
- package/dist/commands/personas.js.map +1 -1
- package/dist/commands/send.d.ts +10 -0
- package/dist/commands/send.d.ts.map +1 -1
- package/dist/commands/send.js +29 -44
- package/dist/commands/send.js.map +1 -1
- package/dist/commands/setup.d.ts.map +1 -1
- package/dist/commands/setup.js +28 -0
- package/dist/commands/setup.js.map +1 -1
- package/dist/commands/spawn.d.ts.map +1 -1
- package/dist/commands/spawn.js +14 -8
- package/dist/commands/spawn.js.map +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +6 -4
- package/dist/index.js.map +1 -1
- package/dist/lib/personas.d.ts +8 -0
- package/dist/lib/personas.d.ts.map +1 -1
- package/dist/lib/personas.js +31 -0
- package/dist/lib/personas.js.map +1 -1
- package/dist/lib/transient.d.ts +27 -0
- package/dist/lib/transient.d.ts.map +1 -0
- package/dist/lib/transient.js +48 -0
- package/dist/lib/transient.js.map +1 -0
- package/dist/web/app.js +143 -44
- package/dist/web/index.html +45 -1
- package/package.json +2 -2
package/dist/web/app.js
CHANGED
|
@@ -41,6 +41,42 @@ function ago(ts) {
|
|
|
41
41
|
const agoShort = (ts) => (ago(ts) === "just now" ? "now" : ago(ts));
|
|
42
42
|
const plural = (n, w) => `${n} ${w}${n === 1 ? "" : "s"}`;
|
|
43
43
|
|
|
44
|
+
// ── Harness (host connector) branding ─────────────────────────────────────────
|
|
45
|
+
// A brand colour + logo (drawn in currentColor) per connector, keyed by the card's meta.connector.
|
|
46
|
+
// Claude and OpenCode use their official marks (public-domain SVG data from Simple Icons, CC0);
|
|
47
|
+
// Hermes/Nous Research has no clean official mark, so it gets a custom messenger glyph. Unknown
|
|
48
|
+
// connectors degrade to a neutral badge with the raw name (the compact roster form omits the icon).
|
|
49
|
+
// Icons are inline SVG — no network, no extra files.
|
|
50
|
+
const HARNESS = {
|
|
51
|
+
claude: {
|
|
52
|
+
label: "Claude Code",
|
|
53
|
+
color: "#d97757", // official Claude clay
|
|
54
|
+
icon: `<svg viewBox="0 0 24 24" fill="currentColor"><path d="m4.7144 15.9555 4.7174-2.6471.079-.2307-.079-.1275h-.2307l-.7893-.0486-2.6956-.0729-2.3375-.0971-2.2646-.1214-.5707-.1215-.5343-.7042.0546-.3522.4797-.3218.686.0608 1.5179.1032 2.2767.1578 1.6514.0972 2.4468.255h.3886l.0546-.1579-.1336-.0971-.1032-.0972L6.973 9.8356l-2.55-1.6879-1.3356-.9714-.7225-.4918-.3643-.4614-.1578-1.0078.6557-.7225.8803.0607.2246.0607.8925.686 1.9064 1.4754 2.4893 1.8336.3643.3035.1457-.1032.0182-.0728-.164-.2733-1.3539-2.4467-1.445-2.4893-.6435-1.032-.17-.6194c-.0607-.255-.1032-.4674-.1032-.7285L6.287.1335 6.6997 0l.9957.1336.419.3642.6192 1.4147 1.0018 2.2282 1.5543 3.0296.4553.8985.2429.8318.091.255h.1579v-.1457l.1275-1.706.2368-2.0947.2307-2.6957.0789-.7589.3764-.9107.7468-.4918.5828.2793.4797.686-.0668.4433-.2853 1.8517-.5586 2.9021-.3643 1.9429h.2125l.2429-.2429.9835-1.3053 1.6514-2.0643.7286-.8196.85-.9046.5464-.4311h1.0321l.759 1.1293-.34 1.1657-1.0625 1.3478-.8804 1.1414-1.2628 1.7-.7893 1.36.0729.1093.1882-.0183 2.8535-.607 1.5421-.2794 1.8396-.3157.8318.3886.091.3946-.3278.8075-1.967.4857-2.3072.4614-3.4364.8136-.0425.0304.0486.0607 1.5482.1457.6618.0364h1.621l3.0175.2247.7892.522.4736.6376-.079.4857-1.2142.6193-1.6393-.3886-3.825-.9107-1.3113-.3279h-.1822v.1093l1.0929 1.0686 2.0035 1.8092 2.5075 2.3314.1275.5768-.3218.4554-.34-.0486-2.2039-1.6575-.85-.7468-1.9246-1.621h-.1275v.17l.4432.6496 2.3436 3.5214.1214 1.0807-.17.3521-.6071.2125-.6679-.1214-1.3721-1.9246L14.38 17.959l-1.1414-1.9428-.1397.079-.674 7.2552-.3156.3703-.7286.2793-.6071-.4614-.3218-.7468.3218-1.4753.3886-1.9246.3157-1.53.2853-1.9004.17-.6314-.0121-.0425-.1397.0182-1.4328 1.9672-2.1796 2.9446-1.7243 1.8456-.4128.164-.7164-.3704.0667-.6618.4008-.5889 2.386-3.0357 1.4389-1.882.929-1.0868-.0062-.1579h-.0546l-6.3385 4.1164-1.1293.1457-.4857-.4554.0608-.7467.2307-.2429 1.9064-1.3114Z"/></svg>`,
|
|
55
|
+
},
|
|
56
|
+
opencode: {
|
|
57
|
+
label: "OpenCode",
|
|
58
|
+
color: "#cdd6e0", // OpenCode is monochrome by brand; rendered light on the dark UI
|
|
59
|
+
icon: `<svg viewBox="0 0 24 24" fill="currentColor"><path d="M22 24H2V0h20zM17 4.8H7v14.4h10z"/></svg>`,
|
|
60
|
+
},
|
|
61
|
+
hermes: {
|
|
62
|
+
label: "Hermes",
|
|
63
|
+
color: "#a78bfa", // no official mark — custom messenger glyph, violet
|
|
64
|
+
icon: `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M21 3 3 10.5l7 2.5 2.5 7L21 3Z"/><path d="M21 3 10 13"/></svg>`,
|
|
65
|
+
},
|
|
66
|
+
};
|
|
67
|
+
// Render the harness badge: a branded pill (icon + label) by default, or `{compact:true}` for a
|
|
68
|
+
// bare colour icon (the roster row). The inline style only ever takes a value from HARNESS, never
|
|
69
|
+
// raw card input, so meta.connector can't inject CSS.
|
|
70
|
+
function harnessBadge(connector, opts = {}) {
|
|
71
|
+
if (!connector) return "";
|
|
72
|
+
const h = HARNESS[connector];
|
|
73
|
+
const color = h ? h.color : "var(--dim)";
|
|
74
|
+
const text = h ? h.label : connector;
|
|
75
|
+
if (opts.compact)
|
|
76
|
+
return h ? `<span class="harness-ico" style="color:${color}" title="harness · ${esc(text)}">${h.icon}</span>` : "";
|
|
77
|
+
return `<span class="harness-badge" style="--hc:${color}" title="agent harness · ${esc(text)}">${h ? h.icon : ""}<span class="hl">${esc(text)}</span></span>`;
|
|
78
|
+
}
|
|
79
|
+
|
|
44
80
|
function setConn(live) {
|
|
45
81
|
const el = $("conn");
|
|
46
82
|
el.className = "pill" + (live ? "" : " down");
|
|
@@ -68,11 +104,15 @@ function renderTiles(counts, oldest) {
|
|
|
68
104
|
|
|
69
105
|
// ── Sidebar: roster ───────────────────────────────────────────────────────────
|
|
70
106
|
function peerRow(p) {
|
|
71
|
-
|
|
107
|
+
// A peer with an id is a click-through into its Agent Detail card; demo rows have no id.
|
|
108
|
+
const nav = p.id ? ` nav${p.id === agentSel ? " sel" : ""}` : "";
|
|
109
|
+
const attrs = p.id ? ` data-agent="${esc(p.id)}" tabindex="0" role="button"` : "";
|
|
110
|
+
return `<div class="peer ${p.status}${nav}"${attrs}>
|
|
72
111
|
<span class="dot ${p.status}">${GLYPH[p.status] ?? "○"}</span>
|
|
73
112
|
<div class="c">
|
|
74
113
|
<div class="l1">
|
|
75
114
|
<span class="name">${esc(p.name)}</span>
|
|
115
|
+
${p.harness ? harnessBadge(p.harness, { compact: true }) : ""}
|
|
76
116
|
${p.role ? `<span class="role">${esc(p.role)}</span>` : ""}
|
|
77
117
|
${p.tag ? `<span class="tag">${esc(p.tag)}</span>` : ""}
|
|
78
118
|
</div>
|
|
@@ -84,6 +124,33 @@ function renderRoster(list) {
|
|
|
84
124
|
$("roster").innerHTML = list.length
|
|
85
125
|
? list.map(peerRow).join("")
|
|
86
126
|
: `<div class="empty">no peers</div>`;
|
|
127
|
+
for (const el of $("roster").querySelectorAll(".peer[data-agent]")) {
|
|
128
|
+
el.onclick = () => selectAgent(el.dataset.agent);
|
|
129
|
+
el.onkeydown = (e) => {
|
|
130
|
+
if (e.key === "Enter" || e.key === " ") (e.preventDefault(), selectAgent(el.dataset.agent));
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
// The online roster, shaped for the sidebar: offline peers drop out (their count still rides the
|
|
135
|
+
// header tiles), live peers sort by status then name. Re-rendered on its own when a selection
|
|
136
|
+
// changes so the row highlight tracks the open Agent Detail.
|
|
137
|
+
function rosterRows() {
|
|
138
|
+
return [...roster]
|
|
139
|
+
.filter((p) => p.status !== "offline")
|
|
140
|
+
.sort(
|
|
141
|
+
(a, b) =>
|
|
142
|
+
STATUS.indexOf(a.status) - STATUS.indexOf(b.status) ||
|
|
143
|
+
a.card.name.localeCompare(b.card.name),
|
|
144
|
+
)
|
|
145
|
+
.map((p) => ({
|
|
146
|
+
id: p.card.id,
|
|
147
|
+
name: p.card.name,
|
|
148
|
+
role: p.card.role,
|
|
149
|
+
status: p.status,
|
|
150
|
+
act: p.activity,
|
|
151
|
+
harness: p.card.meta?.connector,
|
|
152
|
+
tag: p.status === "waiting" ? "needs input" : null,
|
|
153
|
+
}));
|
|
87
154
|
}
|
|
88
155
|
|
|
89
156
|
// ── Sidebar: channels ─────────────────────────────────────────────────────────
|
|
@@ -122,21 +189,31 @@ function rosterStatus(name) {
|
|
|
122
189
|
const r = roster.find((x) => x.card?.name === name);
|
|
123
190
|
return r ? r.status : "offline";
|
|
124
191
|
}
|
|
192
|
+
// id→name resolution shared by the DM lens and the all-activity feed. `from` carries a full
|
|
193
|
+
// card (id+name), but a unicast `to` is the bare recipient identity id (a pubkey) — without this
|
|
194
|
+
// it renders raw. Sources: live roster cards, DM senders, and feed senders we've seen.
|
|
195
|
+
function nameIndex() {
|
|
196
|
+
const idName = new Map();
|
|
197
|
+
for (const p of roster) if (p.card?.id) idName.set(p.card.id, p.card.name);
|
|
198
|
+
for (const m of dms) if (m.from?.id && m.from?.name) idName.set(m.from.id, m.from.name);
|
|
199
|
+
for (const e of activity) if (e.msg?.from?.id && e.msg.from.name) idName.set(e.msg.from.id, e.msg.from.name);
|
|
200
|
+
return idName;
|
|
201
|
+
}
|
|
202
|
+
// Resolve an EndpointRef object or a bare id to a display name; an unknown id shrinks to a short
|
|
203
|
+
// prefix, and a string that isn't an identity (already a name) passes through unchanged.
|
|
204
|
+
function displayNameOf(x, idx) {
|
|
205
|
+
if (!x) return "?";
|
|
206
|
+
if (typeof x === "object") return x.name || displayNameOf(x.id, idx);
|
|
207
|
+
if (idx.has(x)) return idx.get(x);
|
|
208
|
+
return /^[A-Z2-7]{32,}$/.test(x) ? x.slice(0, 6) + "…" : x; // unknown identity → short id
|
|
209
|
+
}
|
|
210
|
+
|
|
125
211
|
// Group raw DMs into per-peer rows; each peer lists its counterparties (conversations).
|
|
126
212
|
// O(peers-with-DMs), never the n² pair cross-product — only pairs that actually talked.
|
|
127
213
|
function dmPeers() {
|
|
128
214
|
if (isDemo) return DEMO.dmPeers;
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
const idName = new Map();
|
|
132
|
-
for (const p of roster) if (p.card?.id) idName.set(p.card.id, p.card.name);
|
|
133
|
-
for (const m of dms) if (m.from?.id && m.from?.name) idName.set(m.from.id, m.from.name);
|
|
134
|
-
const nameOf = (x) => {
|
|
135
|
-
if (!x) return "?";
|
|
136
|
-
if (typeof x === "object") return x.name || nameOf(x.id);
|
|
137
|
-
if (idName.has(x)) return idName.get(x);
|
|
138
|
-
return /^[A-Z2-7]{32,}$/.test(x) ? x.slice(0, 6) + "…" : x; // unknown identity → short id
|
|
139
|
-
};
|
|
215
|
+
const idx = nameIndex();
|
|
216
|
+
const nameOf = (x) => displayNameOf(x, idx);
|
|
140
217
|
const conv = new Map();
|
|
141
218
|
for (const m of dms) {
|
|
142
219
|
const a = nameOf(m.from),
|
|
@@ -242,12 +319,12 @@ function rowHTML(e) {
|
|
|
242
319
|
</div>
|
|
243
320
|
</div>`;
|
|
244
321
|
}
|
|
245
|
-
function liveEntry(mode, msg) {
|
|
322
|
+
function liveEntry(mode, msg, idx) {
|
|
246
323
|
const target =
|
|
247
324
|
mode === "chat"
|
|
248
325
|
? `#${msg.channel ?? ""}`
|
|
249
326
|
: mode === "unicast"
|
|
250
|
-
? `→ ${msg.to
|
|
327
|
+
? `→ ${displayNameOf(msg.to, idx)}`
|
|
251
328
|
: `→ @${msg.toService ?? ""}`;
|
|
252
329
|
return {
|
|
253
330
|
type: "msg",
|
|
@@ -264,7 +341,8 @@ function renderAllActivity() {
|
|
|
264
341
|
const center = $("center");
|
|
265
342
|
const prev = center.querySelector(".feed");
|
|
266
343
|
const atBottom = prev ? prev.scrollHeight - prev.scrollTop - prev.clientHeight < 40 : true;
|
|
267
|
-
const
|
|
344
|
+
const idx = nameIndex();
|
|
345
|
+
const rows = (isDemo ? DEMO.activity : activity.map((e) => liveEntry(e.mode, e.msg, idx))).filter(
|
|
268
346
|
(e) => !e.mode || modes.has(e.mode),
|
|
269
347
|
);
|
|
270
348
|
const sub = isDemo ? "112 recent · live" : `${rows.length} recent · live`;
|
|
@@ -449,27 +527,58 @@ function renderRail() {
|
|
|
449
527
|
el.onclick = () => selectAgent(el.dataset.agent);
|
|
450
528
|
}
|
|
451
529
|
|
|
452
|
-
// ── Agent Detail drill-down (centre) —
|
|
530
|
+
// ── Agent Detail drill-down (centre) — per-agent frame, rendered from the peer's card (docs/web.md) ──
|
|
453
531
|
function selectAgent(id) {
|
|
454
532
|
agentSel = id;
|
|
455
533
|
dmSel = null;
|
|
456
534
|
selected = null;
|
|
457
535
|
renderSidebarNav();
|
|
536
|
+
renderRoster(rosterRows()); // light up the clicked peer (only reached live — demo rows have no id)
|
|
458
537
|
renderCenter();
|
|
459
538
|
renderRail();
|
|
460
539
|
}
|
|
461
540
|
function renderAgentDetail() {
|
|
462
541
|
const p = roster.find((x) => x.card.id === agentSel);
|
|
463
542
|
if (!p) {
|
|
464
|
-
$("center").innerHTML = `<div class="detail"><div class="empty">agent no longer present — pick another from NEEDS YOU.</div></div>`;
|
|
543
|
+
$("center").innerHTML = `<div class="detail"><div class="empty">agent no longer present — pick another from the roster or NEEDS YOU.</div></div>`;
|
|
465
544
|
return;
|
|
466
545
|
}
|
|
546
|
+
// The AgentCard is the legibility contract: who, in what role, what it can do, on what harness.
|
|
547
|
+
// Render only fields the card actually carries (skills/protocolVersion aren't populated yet).
|
|
548
|
+
const card = p.card;
|
|
549
|
+
const meta = card.meta || {};
|
|
467
550
|
const waiting = p.status === "waiting";
|
|
468
|
-
const who =
|
|
551
|
+
const who = card.role ? `${esc(card.name)}<span class="crole">${esc(card.role)}</span>` : esc(card.name);
|
|
469
552
|
const since = waiting ? `waiting ${esc(ago(p.ts))}` : `${esc(p.status)} · ${esc(ago(p.ts))}`;
|
|
553
|
+
|
|
554
|
+
const badges = [
|
|
555
|
+
card.kind ? `<span class="d-badge">${esc(card.kind)}</span>` : "",
|
|
556
|
+
harnessBadge(meta.connector),
|
|
557
|
+
meta.model ? `<span class="d-badge model" title="model">${esc(meta.model)}</span>` : "",
|
|
558
|
+
].join("");
|
|
559
|
+
const desc = card.description ? `<div class="d-desc">${esc(card.description)}</div>` : "";
|
|
470
560
|
const blocked = waiting
|
|
471
561
|
? `<div class="d-label">Blocked on</div><div class="d-block">${esc(p.activity || "waiting for input")}</div>`
|
|
472
|
-
: `<div class="d-block muted">${esc(p.activity || "no current activity")}</div>`;
|
|
562
|
+
: `<div class="d-label">Activity</div><div class="d-block muted">${esc(p.activity || "no current activity")}</div>`;
|
|
563
|
+
|
|
564
|
+
const sec = (label, body) => (body ? `<div class="d-sec"><div class="d-label">${esc(label)}</div>${body}</div>` : "");
|
|
565
|
+
const tags = (card.tags || []).length
|
|
566
|
+
? `<div class="d-chips">${card.tags.map((t) => `<span class="d-chip">${esc(t)}</span>`).join("")}</div>`
|
|
567
|
+
: "";
|
|
568
|
+
const skills = (card.skills || []).length
|
|
569
|
+
? `<div class="d-skills">${card.skills
|
|
570
|
+
.map((s) => `<div class="d-skill"><span class="nm">${esc(s.name || s.id)}</span>${s.description ? `<span class="dsc">${esc(s.description)}</span>` : ""}</div>`)
|
|
571
|
+
.join("")}</div>`
|
|
572
|
+
: "";
|
|
573
|
+
// Any other meta beyond the badges (connector → harness, model), generically rendered (escaped, key-sorted).
|
|
574
|
+
const extra = Object.entries(meta).filter(([k]) => k !== "connector" && k !== "model").sort(([a], [b]) => a.localeCompare(b));
|
|
575
|
+
const metaKv = extra.length
|
|
576
|
+
? `<div class="d-kv">${extra
|
|
577
|
+
.map(([k, v]) => `<div class="row"><span class="k">${esc(k)}</span><span class="v">${esc(typeof v === "string" ? v : JSON.stringify(v))}</span></div>`)
|
|
578
|
+
.join("")}</div>`
|
|
579
|
+
: "";
|
|
580
|
+
const proto = card.protocolVersion ? `<span class="d-foot-item">protocol ${esc(card.protocolVersion)}</span>` : "";
|
|
581
|
+
|
|
473
582
|
$("center").innerHTML = `
|
|
474
583
|
<div class="detail${waiting ? " amber" : ""}">
|
|
475
584
|
<div class="d-head">
|
|
@@ -478,8 +587,13 @@ function renderAgentDetail() {
|
|
|
478
587
|
<span class="d-age">${since}</span>
|
|
479
588
|
</div>
|
|
480
589
|
<div class="d-who">${who}</div>
|
|
481
|
-
|
|
590
|
+
${badges ? `<div class="d-badges">${badges}</div>` : ""}
|
|
591
|
+
${desc}
|
|
482
592
|
${blocked}
|
|
593
|
+
${sec("Tags", tags)}
|
|
594
|
+
${sec("Skills", skills)}
|
|
595
|
+
${sec("Metadata", metaKv)}
|
|
596
|
+
<div class="d-foot"><span class="d-foot-item id">${esc(card.id)}</span>${proto}</div>
|
|
483
597
|
</div>`;
|
|
484
598
|
}
|
|
485
599
|
|
|
@@ -498,24 +612,7 @@ function refreshDerived() {
|
|
|
498
612
|
const oldest = waiting.length ? agoShort(Math.min(...waiting.map((p) => p.ts))) : "—";
|
|
499
613
|
renderTiles(counts, oldest);
|
|
500
614
|
$("online-c").textContent = roster.filter((p) => p.status !== "offline").length;
|
|
501
|
-
//
|
|
502
|
-
// in the header tiles). They reappear here the moment presence flips them back on.
|
|
503
|
-
renderRoster(
|
|
504
|
-
[...roster]
|
|
505
|
-
.filter((p) => p.status !== "offline")
|
|
506
|
-
.sort(
|
|
507
|
-
(a, b) =>
|
|
508
|
-
STATUS.indexOf(a.status) - STATUS.indexOf(b.status) ||
|
|
509
|
-
a.card.name.localeCompare(b.card.name),
|
|
510
|
-
)
|
|
511
|
-
.map((p) => ({
|
|
512
|
-
name: p.card.name,
|
|
513
|
-
role: p.card.role,
|
|
514
|
-
status: p.status,
|
|
515
|
-
act: p.activity,
|
|
516
|
-
tag: p.status === "waiting" ? "needs input" : null,
|
|
517
|
-
})),
|
|
518
|
-
);
|
|
615
|
+
renderRoster(rosterRows()); // online list; offline peers drop out but still ride the header tiles
|
|
519
616
|
renderDMs(); // peer statuses may have changed
|
|
520
617
|
renderRail();
|
|
521
618
|
if (agentSel) renderCenter(); // keep an open Agent Detail live as the peer's status/activity changes
|
|
@@ -528,6 +625,7 @@ async function select(key) {
|
|
|
528
625
|
selected = key;
|
|
529
626
|
if (key !== "*") unread.set(key, 0);
|
|
530
627
|
renderSidebarNav();
|
|
628
|
+
if (!isDemo) renderRoster(rosterRows()); // clear any stale Agent Detail highlight
|
|
531
629
|
if (isDemo) return (renderCenter(), renderRail());
|
|
532
630
|
if (key !== "*") {
|
|
533
631
|
const seq = ++loadSeq;
|
|
@@ -546,6 +644,7 @@ function selectDM(peer, w) {
|
|
|
546
644
|
dmSel = { peer, with: w || (pe && pe.conversations[0] ? pe.conversations[0].with : null) };
|
|
547
645
|
selected = null;
|
|
548
646
|
renderSidebarNav();
|
|
647
|
+
if (!isDemo) renderRoster(rosterRows()); // clear any stale Agent Detail highlight
|
|
549
648
|
renderCenter();
|
|
550
649
|
renderRail();
|
|
551
650
|
}
|
|
@@ -624,12 +723,12 @@ const bd = [
|
|
|
624
723
|
const lm = [{ ts: "10:15", who: "maya", status: "idle", body: "sent the NATS v3 notes your way" }];
|
|
625
724
|
const DEMO = {
|
|
626
725
|
roster: [
|
|
627
|
-
{ name: "alice", role: "planner", status: "waiting", tag: "needs input", act: "blocked — needs OPENAI_API_KEY" },
|
|
628
|
-
{ name: "linus", role: "reviewer", status: "working", act: "reviewing PR #42 · auth guards" },
|
|
629
|
-
{ name: "bob", role: "builder", status: "working", act: "writing tests · channels.ts" },
|
|
630
|
-
{ name: "dave", role: "builder", status: "working", act: "refactoring endpoint.ts" },
|
|
631
|
-
{ name: "maya", role: "researcher", status: "idle", act: "—" },
|
|
632
|
-
{ name: "scout", role: "observer", status: "idle", act: "watching #team.>" },
|
|
726
|
+
{ name: "alice", role: "planner", status: "waiting", tag: "needs input", act: "blocked — needs OPENAI_API_KEY", harness: "claude" },
|
|
727
|
+
{ name: "linus", role: "reviewer", status: "working", act: "reviewing PR #42 · auth guards", harness: "opencode" },
|
|
728
|
+
{ name: "bob", role: "builder", status: "working", act: "writing tests · channels.ts", harness: "claude" },
|
|
729
|
+
{ name: "dave", role: "builder", status: "working", act: "refactoring endpoint.ts", harness: "hermes" },
|
|
730
|
+
{ name: "maya", role: "researcher", status: "idle", act: "—", harness: "opencode" },
|
|
731
|
+
{ name: "scout", role: "observer", status: "idle", act: "watching #team.>", harness: "claude" },
|
|
633
732
|
],
|
|
634
733
|
activity: [
|
|
635
734
|
{ type: "sys", text: "— scout joined · observer —" },
|
package/dist/web/index.html
CHANGED
|
@@ -96,6 +96,11 @@
|
|
|
96
96
|
font-size: 9.5px; font-weight: 600; padding: 1px 6px; border-radius: 4px;
|
|
97
97
|
white-space: nowrap; background: var(--tag-amber); color: var(--amber);
|
|
98
98
|
}
|
|
99
|
+
/* A peer row is a click-through into its Agent Detail card. */
|
|
100
|
+
.peer.nav { cursor: pointer; }
|
|
101
|
+
.peer.nav:hover { background: #ffffff06; }
|
|
102
|
+
.peer.nav.sel { background: var(--sel); }
|
|
103
|
+
.peer.nav:focus-visible { outline: 2px solid var(--blue); outline-offset: -2px; }
|
|
99
104
|
|
|
100
105
|
/* ── Channels ── */
|
|
101
106
|
.chan {
|
|
@@ -263,12 +268,51 @@
|
|
|
263
268
|
.detail .d-age { margin-left: auto; font-size: 11px; color: var(--faint); }
|
|
264
269
|
.detail .d-who { font-size: 20px; font-weight: 700; color: var(--fg); display: flex; align-items: baseline; }
|
|
265
270
|
.detail .d-who .crole { font-size: 12px; font-weight: 500; color: var(--faint); margin-left: 8px; }
|
|
266
|
-
.detail .d-id { font-size: 11px; color: var(--faint); font-family: ui-monospace, monospace; margin-top: -7px; }
|
|
267
271
|
.detail .d-label { font-size: 10.5px; font-weight: 600; letter-spacing: .4px; color: var(--faint); text-transform: uppercase; }
|
|
268
272
|
.detail .d-block { font-size: 13px; color: var(--fg); line-height: 1.5; padding: 12px 14px; border-radius: 8px;
|
|
269
273
|
background: var(--tile); border: 1px solid var(--line); white-space: pre-wrap; word-break: break-word; }
|
|
270
274
|
.detail.amber .d-block { border-color: var(--amber); background: var(--tint-amber); }
|
|
271
275
|
.detail .d-block.muted { color: var(--dim); }
|
|
276
|
+
.detail .d-badges { display: flex; flex-wrap: wrap; gap: 6px; margin-top: -6px; }
|
|
277
|
+
.detail .d-badge {
|
|
278
|
+
font-size: 10.5px; font-weight: 600; padding: 2px 8px; border-radius: 5px;
|
|
279
|
+
background: var(--tile); border: 1px solid var(--line); color: var(--dim);
|
|
280
|
+
}
|
|
281
|
+
.detail .d-badge.model { font-family: ui-monospace, monospace; color: var(--fg); letter-spacing: -.2px; }
|
|
282
|
+
/* Harness (host connector) badge — brand-coloured via --hc; used in the detail view (pill
|
|
283
|
+
with label) and the roster row (bare icon). */
|
|
284
|
+
.harness-badge {
|
|
285
|
+
display: inline-flex; align-items: center; gap: 6px;
|
|
286
|
+
font-size: 11px; font-weight: 600; padding: 3px 10px 3px 8px; border-radius: 999px;
|
|
287
|
+
color: var(--hc); border: 1px solid color-mix(in srgb, var(--hc) 45%, transparent);
|
|
288
|
+
background: color-mix(in srgb, var(--hc) 15%, transparent);
|
|
289
|
+
}
|
|
290
|
+
.harness-ico { display: inline-flex; align-items: center; flex: none; }
|
|
291
|
+
.harness-badge svg, .harness-ico svg { width: 13px; height: 13px; display: block; }
|
|
292
|
+
.detail .d-desc { font-size: 13px; color: var(--fg); line-height: 1.5; }
|
|
293
|
+
.detail .d-sec { display: flex; flex-direction: column; gap: 7px; }
|
|
294
|
+
.detail .d-chips { display: flex; flex-wrap: wrap; gap: 6px; }
|
|
295
|
+
.detail .d-chip {
|
|
296
|
+
font-size: 11px; padding: 2px 9px; border-radius: 12px;
|
|
297
|
+
background: var(--tile); border: 1px solid var(--line); color: var(--dim);
|
|
298
|
+
}
|
|
299
|
+
.detail .d-skills { display: flex; flex-direction: column; gap: 8px; }
|
|
300
|
+
.detail .d-skill {
|
|
301
|
+
display: flex; flex-direction: column; gap: 1px; padding: 8px 12px;
|
|
302
|
+
border-radius: 8px; background: var(--tile); border: 1px solid var(--line);
|
|
303
|
+
}
|
|
304
|
+
.detail .d-skill .nm { font-size: 12.5px; font-weight: 600; color: var(--fg); }
|
|
305
|
+
.detail .d-skill .dsc { font-size: 11.5px; color: var(--dim); }
|
|
306
|
+
.detail .d-kv { display: flex; flex-direction: column; gap: 4px; }
|
|
307
|
+
.detail .d-kv .row { display: flex; gap: 10px; font-size: 12px; }
|
|
308
|
+
.detail .d-kv .k { color: var(--faint); min-width: 90px; flex: none; }
|
|
309
|
+
.detail .d-kv .v { color: var(--fg); word-break: break-word; }
|
|
310
|
+
.detail .d-foot {
|
|
311
|
+
display: flex; flex-wrap: wrap; gap: 6px 14px; margin-top: 4px;
|
|
312
|
+
padding-top: 11px; border-top: 1px solid var(--line);
|
|
313
|
+
}
|
|
314
|
+
.detail .d-foot-item { font-size: 10.5px; color: var(--faint); }
|
|
315
|
+
.detail .d-foot-item.id { font-family: ui-monospace, monospace; word-break: break-all; }
|
|
272
316
|
</style>
|
|
273
317
|
</head>
|
|
274
318
|
<body>
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cotal-ai/cli",
|
|
3
3
|
"description": "Cotal mesh CLI: up, join, watch, console, web, spawn, mint, channels, history.",
|
|
4
|
-
"version": "0.
|
|
4
|
+
"version": "0.5.0",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"repository": {
|
|
7
7
|
"type": "git",
|
|
@@ -21,7 +21,7 @@
|
|
|
21
21
|
"@clack/prompts": "^1.0.0",
|
|
22
22
|
"ink": "^6.0.0",
|
|
23
23
|
"react": "^19.0.0",
|
|
24
|
-
"@cotal-ai/core": "0.
|
|
24
|
+
"@cotal-ai/core": "0.5.0"
|
|
25
25
|
},
|
|
26
26
|
"optionalDependencies": {
|
|
27
27
|
"@eplightning/nats-server-darwin-arm64": "^2.14.0",
|