@log0u7/opencode-mesh 0.6.0 → 0.7.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/CHANGELOG.md +12 -0
- package/dist/rpc.d.ts +2 -0
- package/dist/rpc.d.ts.map +1 -1
- package/dist/rpc.js +12 -0
- package/dist/rpc.js.map +1 -1
- package/dist/tui.js +153 -40
- package/dist/tui.tsx +112 -32
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,18 @@ All notable changes to this project are documented in this file.
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
6
6
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
7
|
|
|
8
|
+
## [0.7.0] - 2026-11-06
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
|
|
12
|
+
- Themed mesh sidebar: bold header and muted peer lines using the active
|
|
13
|
+
theme's text colors.
|
|
14
|
+
- Sidebar collapse: the `/mesh` toggle keybind persists its state in durable
|
|
15
|
+
storage, so the collapsed sidebar survives TUI restarts.
|
|
16
|
+
- Event refresh: the sidebar refreshes immediately (debounced) on server
|
|
17
|
+
events, with the 15s polling as fallback.
|
|
18
|
+
- `/mesh` now opens a dialog with the full snapshot instead of a toast.
|
|
19
|
+
|
|
8
20
|
## [0.6.0] - 2026-11-06
|
|
9
21
|
|
|
10
22
|
### Added
|
package/dist/rpc.d.ts
CHANGED
|
@@ -25,6 +25,8 @@ export type SnapshotInput = {
|
|
|
25
25
|
export declare function snapshotFromMesh(data: SnapshotInput): MeshSnapshot;
|
|
26
26
|
/** Sidebar lines: the node, one line per paired peer, the active lock count. */
|
|
27
27
|
export declare function formatMeshLines(s: MeshSnapshot): string[];
|
|
28
|
+
/** Collapsible sidebar: when collapsed, one header line with the peer count (plus locks). */
|
|
29
|
+
export declare function formatMeshSidebar(s: MeshSnapshot, collapsed: boolean): string[];
|
|
28
30
|
export declare const MeshRpc: {
|
|
29
31
|
readonly id: "mesh";
|
|
30
32
|
readonly methods: {
|
package/dist/rpc.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"rpc.d.ts","sourceRoot":"","sources":["../src/rpc.ts"],"names":[],"mappings":"AAMA,MAAM,MAAM,YAAY,GAAG;IACzB,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,MAAM,CAAC;IACjB,sEAAsE;IACtE,KAAK,EAAE,KAAK,CAAC;QAAE,OAAO,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IACpD,YAAY,EAAE,MAAM,CAAC;CACtB,CAAC;AAEF,0FAA0F;AAC1F,MAAM,MAAM,aAAa,GAAG;IAC1B,QAAQ,EAAE;QAAE,OAAO,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAA;KAAE,CAAC;IAChD,KAAK,EAAE,aAAa,CAAC;QAAE,OAAO,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,OAAO,CAAA;KAAE,CAAC,CAAC;IAC7E,KAAK,EAAE,aAAa,CAAC,OAAO,CAAC,CAAC;IAC9B,MAAM,EAAE,MAAM,CAAC;CAChB,CAAC;AAEF,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,aAAa,GAAG,YAAY,CASlE;AAED,gFAAgF;AAChF,wBAAgB,eAAe,CAAC,CAAC,EAAE,YAAY,GAAG,MAAM,EAAE,CAczD;AAED,eAAO,MAAM,OAAO;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA+BlB,CAAC"}
|
|
1
|
+
{"version":3,"file":"rpc.d.ts","sourceRoot":"","sources":["../src/rpc.ts"],"names":[],"mappings":"AAMA,MAAM,MAAM,YAAY,GAAG;IACzB,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,MAAM,CAAC;IACjB,sEAAsE;IACtE,KAAK,EAAE,KAAK,CAAC;QAAE,OAAO,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IACpD,YAAY,EAAE,MAAM,CAAC;CACtB,CAAC;AAEF,0FAA0F;AAC1F,MAAM,MAAM,aAAa,GAAG;IAC1B,QAAQ,EAAE;QAAE,OAAO,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAA;KAAE,CAAC;IAChD,KAAK,EAAE,aAAa,CAAC;QAAE,OAAO,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,OAAO,CAAA;KAAE,CAAC,CAAC;IAC7E,KAAK,EAAE,aAAa,CAAC,OAAO,CAAC,CAAC;IAC9B,MAAM,EAAE,MAAM,CAAC;CAChB,CAAC;AAEF,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,aAAa,GAAG,YAAY,CASlE;AAED,gFAAgF;AAChF,wBAAgB,eAAe,CAAC,CAAC,EAAE,YAAY,GAAG,MAAM,EAAE,CAczD;AAED,6FAA6F;AAC7F,wBAAgB,iBAAiB,CAAC,CAAC,EAAE,YAAY,EAAE,SAAS,EAAE,OAAO,GAAG,MAAM,EAAE,CAU/E;AAED,eAAO,MAAM,OAAO;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA+BlB,CAAC"}
|
package/dist/rpc.js
CHANGED
|
@@ -26,6 +26,18 @@ export function formatMeshLines(s) {
|
|
|
26
26
|
}
|
|
27
27
|
return lines;
|
|
28
28
|
}
|
|
29
|
+
/** Collapsible sidebar: when collapsed, one header line with the peer count (plus locks). */
|
|
30
|
+
export function formatMeshSidebar(s, collapsed) {
|
|
31
|
+
if (!collapsed) {
|
|
32
|
+
return formatMeshLines(s);
|
|
33
|
+
}
|
|
34
|
+
const self = s.hostname.length > 0 ? s.hostname : s.node_id;
|
|
35
|
+
const lines = [`mesh: ${self} (${s.peers.length} peers)`];
|
|
36
|
+
if (s.active_locks > 0) {
|
|
37
|
+
lines.push(`${s.active_locks} active lock(s)`);
|
|
38
|
+
}
|
|
39
|
+
return lines;
|
|
40
|
+
}
|
|
29
41
|
export const MeshRpc = Rpc.define({
|
|
30
42
|
id: "mesh",
|
|
31
43
|
methods: {
|
package/dist/rpc.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"rpc.js","sourceRoot":"","sources":["../src/rpc.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,GAAG,EAAE,MAAM,sBAAsB,CAAC;AAsB3C,MAAM,UAAU,gBAAgB,CAAC,IAAmB;IAClD,OAAO;QACL,OAAO,EAAE,IAAI,CAAC,QAAQ,CAAC,OAAO;QAC9B,QAAQ,EAAE,IAAI,CAAC,QAAQ,CAAC,QAAQ;QAChC,KAAK,EAAE,IAAI,CAAC,KAAK;aACd,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,MAAM,CAAC;aACvB,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,EAAE,OAAO,EAAE,CAAC,CAAC,OAAO,EAAE,QAAQ,EAAE,CAAC,CAAC,QAAQ,EAAE,CAAC,CAAC;QAC7D,YAAY,EAAE,IAAI,CAAC,KAAK,CAAC,MAAM;KAChC,CAAC;AACJ,CAAC;AAED,gFAAgF;AAChF,MAAM,UAAU,eAAe,CAAC,CAAe;IAC7C,MAAM,IAAI,GAAG,CAAC,CAAC,QAAQ,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC;IAC5D,MAAM,KAAK,GAAG,CAAC,SAAS,IAAI,EAAE,CAAC,CAAC;IAChC,IAAI,CAAC,CAAC,KAAK,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACzB,KAAK,CAAC,IAAI,CAAC,iBAAiB,CAAC,CAAC;IAChC,CAAC;SAAM,CAAC;QACN,KAAK,MAAM,IAAI,IAAI,CAAC,CAAC,KAAK,EAAE,CAAC;YAC3B,KAAK,CAAC,IAAI,CAAC,KAAK,IAAI,CAAC,QAAQ,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,OAAO,EAAE,CAAC,CAAC;QAC7E,CAAC;IACH,CAAC;IACD,IAAI,CAAC,CAAC,YAAY,GAAG,CAAC,EAAE,CAAC;QACvB,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,YAAY,iBAAiB,CAAC,CAAC;IACjD,CAAC;IACD,OAAO,KAAK,CAAC;AACf,CAAC;AAED,MAAM,CAAC,MAAM,OAAO,GAAG,GAAG,CAAC,MAAM,CAAC;IAChC,EAAE,EAAE,MAAM;IACV,OAAO,EAAE;QACP,QAAQ,EAAE;YACR,KAAK,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,UAAU,EAAE,EAAE,EAAE,oBAAoB,EAAE,KAAK,EAAE;YACtE,MAAM,EAAE;gBACN,IAAI,EAAE,QAAQ;gBACd,UAAU,EAAE;oBACV,OAAO,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,WAAW,EAAE,gBAAgB,EAAE;oBAC1D,QAAQ,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,WAAW,EAAE,sBAAsB,EAAE;oBACjE,KAAK,EAAE;wBACL,IAAI,EAAE,OAAO;wBACb,KAAK,EAAE;4BACL,IAAI,EAAE,QAAQ;4BACd,UAAU,EAAE;gCACV,OAAO,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE;gCAC3B,QAAQ,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE;6BAC7B;4BACD,QAAQ,EAAE,CAAC,SAAS,EAAE,UAAU,CAAC;4BACjC,oBAAoB,EAAE,KAAK;yBAC5B;wBACD,WAAW,EAAE,cAAc;qBAC5B;oBACD,YAAY,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,WAAW,EAAE,yBAAyB,EAAE;iBACzE;gBACD,QAAQ,EAAE,CAAC,SAAS,EAAE,UAAU,EAAE,OAAO,EAAE,cAAc,CAAC;gBAC1D,oBAAoB,EAAE,KAAK;aAC5B;SACF;KACF;IACD,MAAM,EAAE,EAAE;CACX,CAAC,CAAC"}
|
|
1
|
+
{"version":3,"file":"rpc.js","sourceRoot":"","sources":["../src/rpc.ts"],"names":[],"mappings":"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"}
|
package/dist/tui.js
CHANGED
|
@@ -1,3 +1,8 @@
|
|
|
1
|
+
import { memo as _$memo } from "@opentui/solid";
|
|
2
|
+
import { createTextNode as _$createTextNode } from "@opentui/solid";
|
|
3
|
+
import { insertNode as _$insertNode } from "@opentui/solid";
|
|
4
|
+
import { effect as _$effect } from "@opentui/solid";
|
|
5
|
+
import { setProp as _$setProp } from "@opentui/solid";
|
|
1
6
|
import { insert as _$insert } from "@opentui/solid";
|
|
2
7
|
import { createElement as _$createElement } from "@opentui/solid";
|
|
3
8
|
import { createComponent as _$createComponent } from "@opentui/solid";
|
|
@@ -5,72 +10,180 @@ import { createComponent as _$createComponent } from "@opentui/solid";
|
|
|
5
10
|
|
|
6
11
|
import { Plugin } from "@opencode/plugin/tui";
|
|
7
12
|
import { For, Show, createSignal } from "solid-js";
|
|
8
|
-
import { MeshRpc,
|
|
13
|
+
import { MeshRpc, formatMeshSidebar } from "./rpc.js";
|
|
9
14
|
const REFRESH_MS = 15_000;
|
|
15
|
+
/** quota-style event refresh: debounced immediate refresh after activity. */
|
|
16
|
+
const EVENT_DEBOUNCE_MS = 150;
|
|
17
|
+
function themedSidebar(theme, lines) {
|
|
18
|
+
return _$createComponent(Show, {
|
|
19
|
+
get when() {
|
|
20
|
+
return lines();
|
|
21
|
+
},
|
|
22
|
+
keyed: true,
|
|
23
|
+
children: current => (() => {
|
|
24
|
+
var _el$ = _$createElement("box");
|
|
25
|
+
_$insert(_el$, _$createComponent(For, {
|
|
26
|
+
each: current,
|
|
27
|
+
children: (line, index) => (() => {
|
|
28
|
+
var _el$2 = _$createElement("text");
|
|
29
|
+
_$setProp(_el$2, "wrapMode", "none");
|
|
30
|
+
_$insert(_el$2, line);
|
|
31
|
+
_$effect(_$p => _$setProp(_el$2, "fg", index() === 0 ? theme.text.base : theme.text.muted, _$p));
|
|
32
|
+
return _el$2;
|
|
33
|
+
})()
|
|
34
|
+
}));
|
|
35
|
+
return _el$;
|
|
36
|
+
})()
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
function dialogBody(theme, snapshot) {
|
|
40
|
+
const self = snapshot.hostname.length > 0 ? snapshot.hostname : snapshot.node_id;
|
|
41
|
+
const peers = snapshot.peers.map(p => p.hostname.length > 0 ? p.hostname : p.node_id);
|
|
42
|
+
return (() => {
|
|
43
|
+
var _el$3 = _$createElement("box"),
|
|
44
|
+
_el$4 = _$createElement("text"),
|
|
45
|
+
_el$5 = _$createElement("b"),
|
|
46
|
+
_el$7 = _$createElement("text"),
|
|
47
|
+
_el$8 = _$createTextNode(`Node: `),
|
|
48
|
+
_el$9 = _$createElement("text"),
|
|
49
|
+
_el$0 = _$createTextNode(`Paired peers: `),
|
|
50
|
+
_el$1 = _$createElement("text"),
|
|
51
|
+
_el$10 = _$createTextNode(`Active locks: `);
|
|
52
|
+
_$insertNode(_el$3, _el$4);
|
|
53
|
+
_$insertNode(_el$3, _el$7);
|
|
54
|
+
_$insertNode(_el$3, _el$9);
|
|
55
|
+
_$insertNode(_el$3, _el$1);
|
|
56
|
+
_$setProp(_el$3, "gap", 1);
|
|
57
|
+
_$setProp(_el$3, "paddingLeft", 2);
|
|
58
|
+
_$setProp(_el$3, "paddingRight", 2);
|
|
59
|
+
_$setProp(_el$3, "paddingBottom", 1);
|
|
60
|
+
_$insertNode(_el$4, _el$5);
|
|
61
|
+
_$insertNode(_el$5, _$createTextNode(`Mesh — coordination`));
|
|
62
|
+
_$insertNode(_el$7, _el$8);
|
|
63
|
+
_$insert(_el$7, self, null);
|
|
64
|
+
_$insertNode(_el$9, _el$0);
|
|
65
|
+
_$insert(_el$9, (() => {
|
|
66
|
+
var _c$ = _$memo(() => peers.length === 0);
|
|
67
|
+
return () => _c$() ? "none" : peers.join(", ");
|
|
68
|
+
})(), null);
|
|
69
|
+
_$insertNode(_el$1, _el$10);
|
|
70
|
+
_$insert(_el$1, () => snapshot.active_locks, null);
|
|
71
|
+
_$effect(_p$ => {
|
|
72
|
+
var _v$ = theme.text.base,
|
|
73
|
+
_v$2 = theme.text.muted,
|
|
74
|
+
_v$3 = theme.text.muted,
|
|
75
|
+
_v$4 = theme.text.muted;
|
|
76
|
+
_v$ !== _p$.e && (_p$.e = _$setProp(_el$4, "fg", _v$, _p$.e));
|
|
77
|
+
_v$2 !== _p$.t && (_p$.t = _$setProp(_el$7, "fg", _v$2, _p$.t));
|
|
78
|
+
_v$3 !== _p$.a && (_p$.a = _$setProp(_el$9, "fg", _v$3, _p$.a));
|
|
79
|
+
_v$4 !== _p$.o && (_p$.o = _$setProp(_el$1, "fg", _v$4, _p$.o));
|
|
80
|
+
return _p$;
|
|
81
|
+
}, {
|
|
82
|
+
e: undefined,
|
|
83
|
+
t: undefined,
|
|
84
|
+
a: undefined,
|
|
85
|
+
o: undefined
|
|
86
|
+
});
|
|
87
|
+
return _el$3;
|
|
88
|
+
})();
|
|
89
|
+
}
|
|
10
90
|
export default Plugin.define({
|
|
11
91
|
id: "@log0u7/opencode-mesh.tui",
|
|
12
92
|
setup(context) {
|
|
93
|
+
const theme = context.theme;
|
|
13
94
|
context.ui.toast.show({
|
|
14
95
|
message: "opencode-mesh loaded",
|
|
15
96
|
variant: "info",
|
|
16
97
|
duration: 2000
|
|
17
98
|
});
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
palette: true,
|
|
28
|
-
run: async () => {
|
|
29
|
-
context.ui.toast.show({
|
|
30
|
-
title: "Mesh",
|
|
31
|
-
message: "Multi-session coordination available",
|
|
32
|
-
variant: "info"
|
|
33
|
-
});
|
|
34
|
-
}
|
|
35
|
-
}]
|
|
36
|
-
}));
|
|
99
|
+
|
|
100
|
+
// Durable collapse state: persisted to disk, survives restarts and hot reloads.
|
|
101
|
+
const [store, mutate] = context.storage.store("mesh-sidebar", {
|
|
102
|
+
initial: {
|
|
103
|
+
collapsed: false
|
|
104
|
+
}
|
|
105
|
+
});
|
|
106
|
+
const [snapshot, setSnapshot] = createSignal(null);
|
|
107
|
+
const [lines, setLines] = createSignal(null);
|
|
37
108
|
|
|
38
109
|
// Live sidebar: poll the server-side snapshot over RPC. On a v1 host (no
|
|
39
110
|
// RPC) or while the server is unreachable, the slot stays hidden.
|
|
40
|
-
const [lines, setLines] = createSignal(null);
|
|
41
111
|
const mesh = context.client.rpc(MeshRpc);
|
|
42
112
|
const refresh = async () => {
|
|
43
113
|
try {
|
|
44
114
|
const snap = await mesh.snapshot({});
|
|
45
|
-
|
|
115
|
+
setSnapshot(snap);
|
|
116
|
+
setLines(formatMeshSidebar(snap, store.collapsed));
|
|
46
117
|
} catch {
|
|
47
118
|
setLines(null);
|
|
48
119
|
}
|
|
49
120
|
};
|
|
50
121
|
void refresh();
|
|
51
122
|
const timer = setInterval(() => void refresh(), REFRESH_MS);
|
|
123
|
+
|
|
124
|
+
// quota-style event refresh: debounced immediate refresh after activity.
|
|
125
|
+
let debounce;
|
|
126
|
+
const schedule = () => {
|
|
127
|
+
if (debounce) clearTimeout(debounce);
|
|
128
|
+
debounce = setTimeout(() => void refresh(), EVENT_DEBOUNCE_MS);
|
|
129
|
+
};
|
|
130
|
+
// context.client.event is an AsyncIterable of V2Event, not an EventEmitter.
|
|
131
|
+
const events = context.client.event.subscribe();
|
|
132
|
+
void (async () => {
|
|
133
|
+
try {
|
|
134
|
+
for await (const _event of events) {
|
|
135
|
+
schedule();
|
|
136
|
+
}
|
|
137
|
+
} catch {
|
|
138
|
+
// stream ended or errored: polling fallback stays active.
|
|
139
|
+
}
|
|
140
|
+
})();
|
|
52
141
|
const unregister = context.ui.slot({
|
|
53
142
|
append: "sidebar.content",
|
|
54
|
-
render: () =>
|
|
55
|
-
get when() {
|
|
56
|
-
return lines();
|
|
57
|
-
},
|
|
58
|
-
keyed: true,
|
|
59
|
-
children: current => (() => {
|
|
60
|
-
var _el$ = _$createElement("box");
|
|
61
|
-
_$insert(_el$, _$createComponent(For, {
|
|
62
|
-
each: current,
|
|
63
|
-
children: line => (() => {
|
|
64
|
-
var _el$2 = _$createElement("text");
|
|
65
|
-
_$insert(_el$2, line);
|
|
66
|
-
return _el$2;
|
|
67
|
-
})()
|
|
68
|
-
}));
|
|
69
|
-
return _el$;
|
|
70
|
-
})()
|
|
71
|
-
})
|
|
143
|
+
render: () => themedSidebar(theme, lines)
|
|
72
144
|
});
|
|
145
|
+
|
|
146
|
+
// Keymap layers are disposed with the plugin; no manual disposer here.
|
|
147
|
+
context.keymap.layer(() => ({
|
|
148
|
+
mode: "global",
|
|
149
|
+
commands: [{
|
|
150
|
+
id: "mesh.status",
|
|
151
|
+
title: "Mesh status",
|
|
152
|
+
group: "Mesh",
|
|
153
|
+
slash: {
|
|
154
|
+
name: "mesh"
|
|
155
|
+
},
|
|
156
|
+
palette: true,
|
|
157
|
+
run: async () => {
|
|
158
|
+
await refresh();
|
|
159
|
+
const current = snapshot();
|
|
160
|
+
if (!current) {
|
|
161
|
+
context.ui.toast.show({
|
|
162
|
+
title: "Mesh",
|
|
163
|
+
message: "Server unreachable",
|
|
164
|
+
variant: "info"
|
|
165
|
+
});
|
|
166
|
+
return;
|
|
167
|
+
}
|
|
168
|
+
context.ui.dialog.show(() => dialogBody(theme, current));
|
|
169
|
+
context.ui.dialog.set({
|
|
170
|
+
size: "medium"
|
|
171
|
+
});
|
|
172
|
+
}
|
|
173
|
+
}, {
|
|
174
|
+
id: "mesh.toggleSidebar",
|
|
175
|
+
title: "Toggle mesh sidebar",
|
|
176
|
+
group: "Mesh",
|
|
177
|
+
run: async () => {
|
|
178
|
+
await mutate(draft => {
|
|
179
|
+
draft.collapsed = !draft.collapsed;
|
|
180
|
+
});
|
|
181
|
+
void refresh();
|
|
182
|
+
}
|
|
183
|
+
}]
|
|
184
|
+
}));
|
|
73
185
|
return () => {
|
|
186
|
+
clearTimeout(debounce);
|
|
74
187
|
clearInterval(timer);
|
|
75
188
|
unregister();
|
|
76
189
|
};
|
package/dist/tui.tsx
CHANGED
|
@@ -1,44 +1,74 @@
|
|
|
1
1
|
/** @jsxImportSource @opentui/solid */
|
|
2
2
|
|
|
3
3
|
import { Plugin } from "@opencode/plugin/tui";
|
|
4
|
+
import type { JSX } from "@opentui/solid";
|
|
4
5
|
import { For, Show, createSignal } from "solid-js";
|
|
5
|
-
|
|
6
|
+
|
|
7
|
+
type Context = Plugin.Context;
|
|
8
|
+
type Theme = Context["theme"];
|
|
9
|
+
|
|
10
|
+
import { MeshRpc, formatMeshSidebar, type MeshSnapshot } from "./rpc.js";
|
|
6
11
|
|
|
7
12
|
const REFRESH_MS = 15_000;
|
|
13
|
+
/** quota-style event refresh: debounced immediate refresh after activity. */
|
|
14
|
+
const EVENT_DEBOUNCE_MS = 150;
|
|
15
|
+
|
|
16
|
+
function themedSidebar(theme: Theme, lines: () => string[] | null): JSX.Element {
|
|
17
|
+
return (
|
|
18
|
+
<Show when={lines()} keyed>
|
|
19
|
+
{(current) => (
|
|
20
|
+
<box>
|
|
21
|
+
<For each={current}>
|
|
22
|
+
{(line, index) => (
|
|
23
|
+
<text fg={index() === 0 ? theme.text.base : theme.text.muted} wrapMode="none">
|
|
24
|
+
{line}
|
|
25
|
+
</text>
|
|
26
|
+
)}
|
|
27
|
+
</For>
|
|
28
|
+
</box>
|
|
29
|
+
)}
|
|
30
|
+
</Show>
|
|
31
|
+
);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function dialogBody(theme: Theme, snapshot: MeshSnapshot): JSX.Element {
|
|
35
|
+
const self = snapshot.hostname.length > 0 ? snapshot.hostname : snapshot.node_id;
|
|
36
|
+
const peers = snapshot.peers.map((p) => (p.hostname.length > 0 ? p.hostname : p.node_id));
|
|
37
|
+
return (
|
|
38
|
+
<box gap={1} paddingLeft={2} paddingRight={2} paddingBottom={1}>
|
|
39
|
+
<text fg={theme.text.base}>
|
|
40
|
+
<b>Mesh — coordination</b>
|
|
41
|
+
</text>
|
|
42
|
+
<text fg={theme.text.muted}>Node: {self}</text>
|
|
43
|
+
<text fg={theme.text.muted}>
|
|
44
|
+
Paired peers: {peers.length === 0 ? "none" : peers.join(", ")}
|
|
45
|
+
</text>
|
|
46
|
+
<text fg={theme.text.muted}>Active locks: {snapshot.active_locks}</text>
|
|
47
|
+
</box>
|
|
48
|
+
);
|
|
49
|
+
}
|
|
8
50
|
|
|
9
51
|
export default Plugin.define({
|
|
10
52
|
id: "@log0u7/opencode-mesh.tui",
|
|
11
|
-
setup(context) {
|
|
53
|
+
setup(context: Context) {
|
|
54
|
+
const theme = context.theme;
|
|
12
55
|
context.ui.toast.show({ message: "opencode-mesh loaded", variant: "info", duration: 2000 });
|
|
13
56
|
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
group: "Mesh",
|
|
21
|
-
slash: { name: "mesh" },
|
|
22
|
-
palette: true,
|
|
23
|
-
run: async () => {
|
|
24
|
-
context.ui.toast.show({
|
|
25
|
-
title: "Mesh",
|
|
26
|
-
message: "Multi-session coordination available",
|
|
27
|
-
variant: "info",
|
|
28
|
-
});
|
|
29
|
-
},
|
|
30
|
-
},
|
|
31
|
-
],
|
|
32
|
-
}));
|
|
57
|
+
// Durable collapse state: persisted to disk, survives restarts and hot reloads.
|
|
58
|
+
const [store, mutate] = context.storage.store("mesh-sidebar", {
|
|
59
|
+
initial: { collapsed: false },
|
|
60
|
+
});
|
|
61
|
+
const [snapshot, setSnapshot] = createSignal<MeshSnapshot | null>(null);
|
|
62
|
+
const [lines, setLines] = createSignal<string[] | null>(null);
|
|
33
63
|
|
|
34
64
|
// Live sidebar: poll the server-side snapshot over RPC. On a v1 host (no
|
|
35
65
|
// RPC) or while the server is unreachable, the slot stays hidden.
|
|
36
|
-
const [lines, setLines] = createSignal<string[] | null>(null);
|
|
37
66
|
const mesh = context.client.rpc(MeshRpc);
|
|
38
67
|
const refresh = async () => {
|
|
39
68
|
try {
|
|
40
69
|
const snap = (await mesh.snapshot({})) as MeshSnapshot;
|
|
41
|
-
|
|
70
|
+
setSnapshot(snap);
|
|
71
|
+
setLines(formatMeshSidebar(snap, store.collapsed));
|
|
42
72
|
} catch {
|
|
43
73
|
setLines(null);
|
|
44
74
|
}
|
|
@@ -46,20 +76,70 @@ export default Plugin.define({
|
|
|
46
76
|
void refresh();
|
|
47
77
|
const timer = setInterval(() => void refresh(), REFRESH_MS);
|
|
48
78
|
|
|
79
|
+
// quota-style event refresh: debounced immediate refresh after activity.
|
|
80
|
+
let debounce: ReturnType<typeof setTimeout> | undefined;
|
|
81
|
+
const schedule = () => {
|
|
82
|
+
if (debounce) clearTimeout(debounce);
|
|
83
|
+
debounce = setTimeout(() => void refresh(), EVENT_DEBOUNCE_MS);
|
|
84
|
+
};
|
|
85
|
+
// context.client.event is an AsyncIterable of V2Event, not an EventEmitter.
|
|
86
|
+
const events = context.client.event.subscribe();
|
|
87
|
+
void (async () => {
|
|
88
|
+
try {
|
|
89
|
+
for await (const _event of events) {
|
|
90
|
+
schedule();
|
|
91
|
+
}
|
|
92
|
+
} catch {
|
|
93
|
+
// stream ended or errored: polling fallback stays active.
|
|
94
|
+
}
|
|
95
|
+
})();
|
|
96
|
+
|
|
49
97
|
const unregister = context.ui.slot({
|
|
50
98
|
append: "sidebar.content",
|
|
51
|
-
render: () => (
|
|
52
|
-
<Show when={lines()} keyed>
|
|
53
|
-
{(current) => (
|
|
54
|
-
<box>
|
|
55
|
-
<For each={current}>{(line) => <text>{line}</text>}</For>
|
|
56
|
-
</box>
|
|
57
|
-
)}
|
|
58
|
-
</Show>
|
|
59
|
-
),
|
|
99
|
+
render: () => themedSidebar(theme, lines),
|
|
60
100
|
});
|
|
61
101
|
|
|
102
|
+
// Keymap layers are disposed with the plugin; no manual disposer here.
|
|
103
|
+
context.keymap.layer(() => ({
|
|
104
|
+
mode: "global",
|
|
105
|
+
commands: [
|
|
106
|
+
{
|
|
107
|
+
id: "mesh.status",
|
|
108
|
+
title: "Mesh status",
|
|
109
|
+
group: "Mesh",
|
|
110
|
+
slash: { name: "mesh" },
|
|
111
|
+
palette: true,
|
|
112
|
+
run: async () => {
|
|
113
|
+
await refresh();
|
|
114
|
+
const current = snapshot();
|
|
115
|
+
if (!current) {
|
|
116
|
+
context.ui.toast.show({
|
|
117
|
+
title: "Mesh",
|
|
118
|
+
message: "Server unreachable",
|
|
119
|
+
variant: "info",
|
|
120
|
+
});
|
|
121
|
+
return;
|
|
122
|
+
}
|
|
123
|
+
context.ui.dialog.show(() => dialogBody(theme, current));
|
|
124
|
+
context.ui.dialog.set({ size: "medium" });
|
|
125
|
+
},
|
|
126
|
+
},
|
|
127
|
+
{
|
|
128
|
+
id: "mesh.toggleSidebar",
|
|
129
|
+
title: "Toggle mesh sidebar",
|
|
130
|
+
group: "Mesh",
|
|
131
|
+
run: async () => {
|
|
132
|
+
await mutate((draft) => {
|
|
133
|
+
draft.collapsed = !draft.collapsed;
|
|
134
|
+
});
|
|
135
|
+
void refresh();
|
|
136
|
+
},
|
|
137
|
+
},
|
|
138
|
+
],
|
|
139
|
+
}));
|
|
140
|
+
|
|
62
141
|
return () => {
|
|
142
|
+
clearTimeout(debounce);
|
|
63
143
|
clearInterval(timer);
|
|
64
144
|
unregister();
|
|
65
145
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@log0u7/opencode-mesh",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.7.0",
|
|
4
4
|
"description": "Multi-session coordination for OpenCode: session coordination on one machine and a peer pool across LAN or Tailscale, with pairing, mailbox, and worktree locks.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "Apache-2.0",
|