@cluesmith/codev 2.0.0-rc.69 → 2.0.0-rc.70
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/dashboard/dist/assets/{index-CG7nUttd.js → index-CDAINZKT.js} +21 -21
- package/dashboard/dist/assets/{index-CG7nUttd.js.map → index-CDAINZKT.js.map} +1 -1
- package/dashboard/dist/index.html +1 -1
- package/dist/agent-farm/cli.js +2 -2
- package/dist/agent-farm/cli.js.map +1 -1
- package/dist/agent-farm/commands/architect.d.ts +3 -3
- package/dist/agent-farm/commands/architect.d.ts.map +1 -1
- package/dist/agent-farm/commands/architect.js +20 -142
- package/dist/agent-farm/commands/architect.js.map +1 -1
- package/dist/agent-farm/commands/attach.d.ts.map +1 -1
- package/dist/agent-farm/commands/attach.js +13 -50
- package/dist/agent-farm/commands/attach.js.map +1 -1
- package/dist/agent-farm/commands/cleanup.d.ts.map +1 -1
- package/dist/agent-farm/commands/cleanup.js +1 -11
- package/dist/agent-farm/commands/cleanup.js.map +1 -1
- package/dist/agent-farm/commands/send.d.ts +1 -1
- package/dist/agent-farm/commands/send.d.ts.map +1 -1
- package/dist/agent-farm/commands/send.js +35 -92
- package/dist/agent-farm/commands/send.js.map +1 -1
- package/dist/agent-farm/commands/spawn.d.ts.map +1 -1
- package/dist/agent-farm/commands/spawn.js +26 -58
- package/dist/agent-farm/commands/spawn.js.map +1 -1
- package/dist/agent-farm/commands/status.js +1 -1
- package/dist/agent-farm/commands/status.js.map +1 -1
- package/dist/agent-farm/commands/stop.d.ts.map +1 -1
- package/dist/agent-farm/commands/stop.js +9 -44
- package/dist/agent-farm/commands/stop.js.map +1 -1
- package/dist/agent-farm/commands/tower-cloud.d.ts.map +1 -1
- package/dist/agent-farm/commands/tower-cloud.js +18 -1
- package/dist/agent-farm/commands/tower-cloud.js.map +1 -1
- package/dist/agent-farm/db/index.d.ts.map +1 -1
- package/dist/agent-farm/db/index.js +61 -0
- package/dist/agent-farm/db/index.js.map +1 -1
- package/dist/agent-farm/db/migrate.d.ts.map +1 -1
- package/dist/agent-farm/db/migrate.js +6 -9
- package/dist/agent-farm/db/migrate.js.map +1 -1
- package/dist/agent-farm/db/schema.d.ts +2 -2
- package/dist/agent-farm/db/schema.d.ts.map +1 -1
- package/dist/agent-farm/db/schema.js +3 -4
- package/dist/agent-farm/db/schema.js.map +1 -1
- package/dist/agent-farm/db/types.d.ts +0 -3
- package/dist/agent-farm/db/types.d.ts.map +1 -1
- package/dist/agent-farm/db/types.js +0 -3
- package/dist/agent-farm/db/types.js.map +1 -1
- package/dist/agent-farm/lib/tower-client.d.ts +4 -0
- package/dist/agent-farm/lib/tower-client.d.ts.map +1 -1
- package/dist/agent-farm/lib/tower-client.js +10 -0
- package/dist/agent-farm/lib/tower-client.js.map +1 -1
- package/dist/agent-farm/lib/tunnel-client.d.ts.map +1 -1
- package/dist/agent-farm/lib/tunnel-client.js +6 -13
- package/dist/agent-farm/lib/tunnel-client.js.map +1 -1
- package/dist/agent-farm/servers/tower-server.js +482 -494
- package/dist/agent-farm/servers/tower-server.js.map +1 -1
- package/dist/agent-farm/state.d.ts.map +1 -1
- package/dist/agent-farm/state.js +6 -10
- package/dist/agent-farm/state.js.map +1 -1
- package/dist/agent-farm/types.d.ts +0 -7
- package/dist/agent-farm/types.d.ts.map +1 -1
- package/dist/agent-farm/utils/deps.d.ts.map +1 -1
- package/dist/agent-farm/utils/deps.js +0 -16
- package/dist/agent-farm/utils/deps.js.map +1 -1
- package/dist/agent-farm/utils/gate-watcher.js +4 -4
- package/dist/agent-farm/utils/gate-watcher.js.map +1 -1
- package/dist/agent-farm/utils/session.d.ts +6 -6
- package/dist/agent-farm/utils/session.d.ts.map +1 -1
- package/dist/agent-farm/utils/session.js +4 -4
- package/dist/agent-farm/utils/session.js.map +1 -1
- package/dist/agent-farm/utils/shell.d.ts +4 -4
- package/dist/agent-farm/utils/shell.js +4 -4
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +2 -0
- package/dist/cli.js.map +1 -1
- package/dist/commands/consult/index.d.ts +1 -0
- package/dist/commands/consult/index.d.ts.map +1 -1
- package/dist/commands/consult/index.js +19 -5
- package/dist/commands/consult/index.js.map +1 -1
- package/dist/commands/doctor.d.ts.map +1 -1
- package/dist/commands/doctor.js +0 -12
- package/dist/commands/doctor.js.map +1 -1
- package/dist/commands/porch/next.d.ts.map +1 -1
- package/dist/commands/porch/next.js +90 -8
- package/dist/commands/porch/next.js.map +1 -1
- package/dist/commands/porch/prompts.d.ts.map +1 -1
- package/dist/commands/porch/prompts.js +26 -4
- package/dist/commands/porch/prompts.js.map +1 -1
- package/dist/commands/porch/verdict.d.ts +0 -8
- package/dist/commands/porch/verdict.d.ts.map +1 -1
- package/dist/commands/porch/verdict.js +0 -13
- package/dist/commands/porch/verdict.js.map +1 -1
- package/dist/terminal/pty-manager.d.ts +9 -0
- package/dist/terminal/pty-manager.d.ts.map +1 -1
- package/dist/terminal/pty-manager.js +45 -2
- package/dist/terminal/pty-manager.js.map +1 -1
- package/dist/terminal/pty-session.d.ts +27 -4
- package/dist/terminal/pty-session.d.ts.map +1 -1
- package/dist/terminal/pty-session.js +112 -4
- package/dist/terminal/pty-session.js.map +1 -1
- package/dist/terminal/ring-buffer.d.ts +10 -3
- package/dist/terminal/ring-buffer.d.ts.map +1 -1
- package/dist/terminal/ring-buffer.js +25 -5
- package/dist/terminal/ring-buffer.js.map +1 -1
- package/dist/terminal/session-manager.d.ts +115 -0
- package/dist/terminal/session-manager.d.ts.map +1 -0
- package/dist/terminal/session-manager.js +582 -0
- package/dist/terminal/session-manager.js.map +1 -0
- package/dist/terminal/shepherd-client.d.ts +58 -0
- package/dist/terminal/shepherd-client.d.ts.map +1 -0
- package/dist/terminal/shepherd-client.js +212 -0
- package/dist/terminal/shepherd-client.js.map +1 -0
- package/dist/terminal/shepherd-main.d.ts +19 -0
- package/dist/terminal/shepherd-main.d.ts.map +1 -0
- package/dist/terminal/shepherd-main.js +153 -0
- package/dist/terminal/shepherd-main.js.map +1 -0
- package/dist/terminal/shepherd-process.d.ts +75 -0
- package/dist/terminal/shepherd-process.d.ts.map +1 -0
- package/dist/terminal/shepherd-process.js +279 -0
- package/dist/terminal/shepherd-process.js.map +1 -0
- package/dist/terminal/shepherd-protocol.d.ts +115 -0
- package/dist/terminal/shepherd-protocol.d.ts.map +1 -0
- package/dist/terminal/shepherd-protocol.js +214 -0
- package/dist/terminal/shepherd-protocol.js.map +1 -0
- package/dist/terminal/shepherd-replay-buffer.d.ts +38 -0
- package/dist/terminal/shepherd-replay-buffer.d.ts.map +1 -0
- package/dist/terminal/shepherd-replay-buffer.js +94 -0
- package/dist/terminal/shepherd-replay-buffer.js.map +1 -0
- package/package.json +1 -1
- package/skeleton/.claude/skills/codev/SKILL.md +1 -1
- package/skeleton/DEPENDENCIES.md +2 -34
- package/skeleton/consult-types/impl-review.md +9 -9
- package/skeleton/consult-types/integration-review.md +1 -1
- package/skeleton/consult-types/plan-review.md +1 -1
- package/skeleton/consult-types/pr-ready.md +1 -1
- package/skeleton/consult-types/spec-review.md +1 -1
- package/skeleton/protocols/bugfix/prompts/pr.md +23 -4
- package/skeleton/protocols/maintain/protocol.md +3 -3
- package/skeleton/resources/commands/agent-farm.md +2 -3
- package/skeleton/resources/commands/codev.md +0 -2
- package/skeleton/roles/architect.md +1 -1
- package/skeleton/roles/consultant.md +6 -6
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ShepherdClient: Tower's connection to a single shepherd process.
|
|
3
|
+
*
|
|
4
|
+
* Connects to a shepherd via Unix socket, performs HELLO/WELCOME handshake,
|
|
5
|
+
* and provides a typed API for sending/receiving frames. Emits events for
|
|
6
|
+
* data, exit, replay, and errors.
|
|
7
|
+
*
|
|
8
|
+
* Usage:
|
|
9
|
+
* const client = new ShepherdClient('/path/to/shepherd.sock');
|
|
10
|
+
* const welcome = await client.connect();
|
|
11
|
+
* client.on('data', (buf) => { ... });
|
|
12
|
+
* client.write('ls\n');
|
|
13
|
+
* client.disconnect();
|
|
14
|
+
*/
|
|
15
|
+
import net from 'node:net';
|
|
16
|
+
import { EventEmitter } from 'node:events';
|
|
17
|
+
import { FrameType, PROTOCOL_VERSION, createFrameParser, encodeHello, encodeData, encodeResize, encodeSignal, encodeSpawn, encodePing, encodePong, parseJsonPayload, isKnownFrameType, } from './shepherd-protocol.js';
|
|
18
|
+
export class ShepherdClient extends EventEmitter {
|
|
19
|
+
socketPath;
|
|
20
|
+
socket = null;
|
|
21
|
+
_connected = false;
|
|
22
|
+
replayData = null;
|
|
23
|
+
constructor(socketPath) {
|
|
24
|
+
super();
|
|
25
|
+
this.socketPath = socketPath;
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Emit an 'error' event only if listeners are attached.
|
|
29
|
+
* Prevents Node.js from throwing on unhandled 'error' events,
|
|
30
|
+
* which would crash Tower.
|
|
31
|
+
*/
|
|
32
|
+
safeEmitError(err) {
|
|
33
|
+
if (this.listenerCount('error') > 0) {
|
|
34
|
+
this.emit('error', err);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
get connected() {
|
|
38
|
+
return this._connected;
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Connect to the shepherd, perform HELLO/WELCOME handshake.
|
|
42
|
+
* Resolves with the WelcomeMessage on success.
|
|
43
|
+
* Rejects on connection error or handshake failure.
|
|
44
|
+
*/
|
|
45
|
+
connect() {
|
|
46
|
+
return new Promise((resolve, reject) => {
|
|
47
|
+
if (this._connected) {
|
|
48
|
+
reject(new Error('Already connected'));
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
const socket = net.createConnection(this.socketPath);
|
|
52
|
+
this.socket = socket;
|
|
53
|
+
let handshakeResolved = false;
|
|
54
|
+
const parser = createFrameParser();
|
|
55
|
+
const onError = (err) => {
|
|
56
|
+
if (!handshakeResolved) {
|
|
57
|
+
handshakeResolved = true;
|
|
58
|
+
reject(err);
|
|
59
|
+
}
|
|
60
|
+
else {
|
|
61
|
+
this.safeEmitError(err);
|
|
62
|
+
}
|
|
63
|
+
this.cleanup();
|
|
64
|
+
};
|
|
65
|
+
socket.on('error', onError);
|
|
66
|
+
parser.on('error', (err) => {
|
|
67
|
+
this.safeEmitError(err);
|
|
68
|
+
this.cleanup();
|
|
69
|
+
});
|
|
70
|
+
socket.on('connect', () => {
|
|
71
|
+
socket.pipe(parser);
|
|
72
|
+
// Send HELLO to initiate handshake
|
|
73
|
+
socket.write(encodeHello({ version: PROTOCOL_VERSION }));
|
|
74
|
+
});
|
|
75
|
+
socket.on('close', () => {
|
|
76
|
+
const wasConnected = this._connected;
|
|
77
|
+
this.cleanup();
|
|
78
|
+
if (wasConnected) {
|
|
79
|
+
this.emit('close');
|
|
80
|
+
}
|
|
81
|
+
if (!handshakeResolved) {
|
|
82
|
+
handshakeResolved = true;
|
|
83
|
+
reject(new Error('Connection closed during handshake'));
|
|
84
|
+
}
|
|
85
|
+
});
|
|
86
|
+
// Buffer frames that arrive before WELCOME (e.g., DATA from PTY output
|
|
87
|
+
// that the shepherd forwards immediately on connection)
|
|
88
|
+
const preWelcomeBuffer = [];
|
|
89
|
+
parser.on('data', (frame) => {
|
|
90
|
+
if (!handshakeResolved) {
|
|
91
|
+
if (frame.type === FrameType.WELCOME) {
|
|
92
|
+
try {
|
|
93
|
+
const welcome = parseJsonPayload(frame.payload);
|
|
94
|
+
// Version mismatch handling per spec:
|
|
95
|
+
// - shepherd version < Tower version → disconnect (stale shepherd)
|
|
96
|
+
// - shepherd version > Tower version → warn but continue
|
|
97
|
+
const shepherdVersion = welcome.version ?? 0;
|
|
98
|
+
if (shepherdVersion < PROTOCOL_VERSION) {
|
|
99
|
+
handshakeResolved = true;
|
|
100
|
+
reject(new Error(`Shepherd protocol version ${shepherdVersion} is older than Tower version ${PROTOCOL_VERSION}`));
|
|
101
|
+
this.cleanup();
|
|
102
|
+
return;
|
|
103
|
+
}
|
|
104
|
+
if (shepherdVersion > PROTOCOL_VERSION) {
|
|
105
|
+
// Newer shepherd — log warning but continue (forward compatible)
|
|
106
|
+
this.emit('version-warning', shepherdVersion, PROTOCOL_VERSION);
|
|
107
|
+
}
|
|
108
|
+
handshakeResolved = true;
|
|
109
|
+
this._connected = true;
|
|
110
|
+
// Replay any buffered frames received before WELCOME
|
|
111
|
+
for (const buffered of preWelcomeBuffer) {
|
|
112
|
+
this.handleFrame(buffered);
|
|
113
|
+
}
|
|
114
|
+
resolve(welcome);
|
|
115
|
+
}
|
|
116
|
+
catch {
|
|
117
|
+
handshakeResolved = true;
|
|
118
|
+
reject(new Error('Invalid WELCOME payload'));
|
|
119
|
+
this.cleanup();
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
else {
|
|
123
|
+
// Buffer non-WELCOME frames for replay after handshake
|
|
124
|
+
preWelcomeBuffer.push(frame);
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
else {
|
|
128
|
+
// Post-handshake: dispatch frames
|
|
129
|
+
this.handleFrame(frame);
|
|
130
|
+
}
|
|
131
|
+
});
|
|
132
|
+
});
|
|
133
|
+
}
|
|
134
|
+
handleFrame(frame) {
|
|
135
|
+
if (!isKnownFrameType(frame.type)) {
|
|
136
|
+
// Unknown types silently ignored (forward compatibility)
|
|
137
|
+
return;
|
|
138
|
+
}
|
|
139
|
+
switch (frame.type) {
|
|
140
|
+
case FrameType.DATA:
|
|
141
|
+
this.emit('data', frame.payload);
|
|
142
|
+
break;
|
|
143
|
+
case FrameType.EXIT: {
|
|
144
|
+
try {
|
|
145
|
+
const exit = parseJsonPayload(frame.payload);
|
|
146
|
+
this.emit('exit', exit);
|
|
147
|
+
}
|
|
148
|
+
catch {
|
|
149
|
+
this.safeEmitError(new Error('Invalid EXIT payload'));
|
|
150
|
+
}
|
|
151
|
+
break;
|
|
152
|
+
}
|
|
153
|
+
case FrameType.REPLAY:
|
|
154
|
+
this.replayData = frame.payload;
|
|
155
|
+
this.emit('replay', frame.payload);
|
|
156
|
+
break;
|
|
157
|
+
case FrameType.PING:
|
|
158
|
+
this.socket?.write(encodePong());
|
|
159
|
+
break;
|
|
160
|
+
case FrameType.PONG:
|
|
161
|
+
this.emit('pong');
|
|
162
|
+
break;
|
|
163
|
+
case FrameType.WELCOME:
|
|
164
|
+
// Duplicate WELCOME after handshake — ignore
|
|
165
|
+
break;
|
|
166
|
+
default:
|
|
167
|
+
// Other frame types (HELLO, RESIZE, SIGNAL, SPAWN) are shepherd-bound,
|
|
168
|
+
// not expected from shepherd → Tower
|
|
169
|
+
break;
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
disconnect() {
|
|
173
|
+
this.cleanup();
|
|
174
|
+
}
|
|
175
|
+
cleanup() {
|
|
176
|
+
this._connected = false;
|
|
177
|
+
if (this.socket && !this.socket.destroyed) {
|
|
178
|
+
this.socket.destroy();
|
|
179
|
+
}
|
|
180
|
+
this.socket = null;
|
|
181
|
+
}
|
|
182
|
+
write(data) {
|
|
183
|
+
if (!this._connected || !this.socket)
|
|
184
|
+
return;
|
|
185
|
+
this.socket.write(encodeData(data));
|
|
186
|
+
}
|
|
187
|
+
resize(cols, rows) {
|
|
188
|
+
if (!this._connected || !this.socket)
|
|
189
|
+
return;
|
|
190
|
+
this.socket.write(encodeResize({ cols, rows }));
|
|
191
|
+
}
|
|
192
|
+
signal(sig) {
|
|
193
|
+
if (!this._connected || !this.socket)
|
|
194
|
+
return;
|
|
195
|
+
this.socket.write(encodeSignal({ signal: sig }));
|
|
196
|
+
}
|
|
197
|
+
spawn(msg) {
|
|
198
|
+
if (!this._connected || !this.socket)
|
|
199
|
+
return;
|
|
200
|
+
this.socket.write(encodeSpawn(msg));
|
|
201
|
+
}
|
|
202
|
+
ping() {
|
|
203
|
+
if (!this._connected || !this.socket)
|
|
204
|
+
return;
|
|
205
|
+
this.socket.write(encodePing());
|
|
206
|
+
}
|
|
207
|
+
/** Get the last received replay data, or null if none. */
|
|
208
|
+
getReplayData() {
|
|
209
|
+
return this.replayData;
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
//# sourceMappingURL=shepherd-client.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"shepherd-client.js","sourceRoot":"","sources":["../../src/terminal/shepherd-client.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH,OAAO,GAAG,MAAM,UAAU,CAAC;AAC3B,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAC3C,OAAO,EACL,SAAS,EACT,gBAAgB,EAChB,iBAAiB,EACjB,WAAW,EACX,UAAU,EACV,YAAY,EACZ,YAAY,EACZ,WAAW,EACX,UAAU,EACV,UAAU,EACV,gBAAgB,EAChB,gBAAgB,GAKjB,MAAM,wBAAwB,CAAC;AAchC,MAAM,OAAO,cAAe,SAAQ,YAAY;IAKjB;IAJrB,MAAM,GAAsB,IAAI,CAAC;IACjC,UAAU,GAAG,KAAK,CAAC;IACnB,UAAU,GAAkB,IAAI,CAAC;IAEzC,YAA6B,UAAkB;QAC7C,KAAK,EAAE,CAAC;QADmB,eAAU,GAAV,UAAU,CAAQ;IAE/C,CAAC;IAED;;;;OAIG;IACK,aAAa,CAAC,GAAU;QAC9B,IAAI,IAAI,CAAC,aAAa,CAAC,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC;YACpC,IAAI,CAAC,IAAI,CAAC,OAAO,EAAE,GAAG,CAAC,CAAC;QAC1B,CAAC;IACH,CAAC;IAED,IAAI,SAAS;QACX,OAAO,IAAI,CAAC,UAAU,CAAC;IACzB,CAAC;IAED;;;;OAIG;IACH,OAAO;QACL,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;YACrC,IAAI,IAAI,CAAC,UAAU,EAAE,CAAC;gBACpB,MAAM,CAAC,IAAI,KAAK,CAAC,mBAAmB,CAAC,CAAC,CAAC;gBACvC,OAAO;YACT,CAAC;YAED,MAAM,MAAM,GAAG,GAAG,CAAC,gBAAgB,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC;YACrD,IAAI,CAAC,MAAM,GAAG,MAAM,CAAC;YAErB,IAAI,iBAAiB,GAAG,KAAK,CAAC;YAC9B,MAAM,MAAM,GAAG,iBAAiB,EAAE,CAAC;YAEnC,MAAM,OAAO,GAAG,CAAC,GAAU,EAAE,EAAE;gBAC7B,IAAI,CAAC,iBAAiB,EAAE,CAAC;oBACvB,iBAAiB,GAAG,IAAI,CAAC;oBACzB,MAAM,CAAC,GAAG,CAAC,CAAC;gBACd,CAAC;qBAAM,CAAC;oBACN,IAAI,CAAC,aAAa,CAAC,GAAG,CAAC,CAAC;gBAC1B,CAAC;gBACD,IAAI,CAAC,OAAO,EAAE,CAAC;YACjB,CAAC,CAAC;YAEF,MAAM,CAAC,EAAE,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;YAC5B,MAAM,CAAC,EAAE,CAAC,OAAO,EAAE,CAAC,GAAG,EAAE,EAAE;gBACzB,IAAI,CAAC,aAAa,CAAC,GAAG,CAAC,CAAC;gBACxB,IAAI,CAAC,OAAO,EAAE,CAAC;YACjB,CAAC,CAAC,CAAC;YAEH,MAAM,CAAC,EAAE,CAAC,SAAS,EAAE,GAAG,EAAE;gBACxB,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;gBACpB,mCAAmC;gBACnC,MAAM,CAAC,KAAK,CAAC,WAAW,CAAC,EAAE,OAAO,EAAE,gBAAgB,EAAE,CAAC,CAAC,CAAC;YAC3D,CAAC,CAAC,CAAC;YAEH,MAAM,CAAC,EAAE,CAAC,OAAO,EAAE,GAAG,EAAE;gBACtB,MAAM,YAAY,GAAG,IAAI,CAAC,UAAU,CAAC;gBACrC,IAAI,CAAC,OAAO,EAAE,CAAC;gBACf,IAAI,YAAY,EAAE,CAAC;oBACjB,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;gBACrB,CAAC;gBACD,IAAI,CAAC,iBAAiB,EAAE,CAAC;oBACvB,iBAAiB,GAAG,IAAI,CAAC;oBACzB,MAAM,CAAC,IAAI,KAAK,CAAC,oCAAoC,CAAC,CAAC,CAAC;gBAC1D,CAAC;YACH,CAAC,CAAC,CAAC;YAEH,uEAAuE;YACvE,wDAAwD;YACxD,MAAM,gBAAgB,GAAkB,EAAE,CAAC;YAE3C,MAAM,CAAC,EAAE,CAAC,MAAM,EAAE,CAAC,KAAkB,EAAE,EAAE;gBACvC,IAAI,CAAC,iBAAiB,EAAE,CAAC;oBACvB,IAAI,KAAK,CAAC,IAAI,KAAK,SAAS,CAAC,OAAO,EAAE,CAAC;wBACrC,IAAI,CAAC;4BACH,MAAM,OAAO,GAAG,gBAAgB,CAAiB,KAAK,CAAC,OAAO,CAAC,CAAC;4BAEhE,sCAAsC;4BACtC,mEAAmE;4BACnE,yDAAyD;4BACzD,MAAM,eAAe,GAAG,OAAO,CAAC,OAAO,IAAI,CAAC,CAAC;4BAC7C,IAAI,eAAe,GAAG,gBAAgB,EAAE,CAAC;gCACvC,iBAAiB,GAAG,IAAI,CAAC;gCACzB,MAAM,CAAC,IAAI,KAAK,CAAC,6BAA6B,eAAe,gCAAgC,gBAAgB,EAAE,CAAC,CAAC,CAAC;gCAClH,IAAI,CAAC,OAAO,EAAE,CAAC;gCACf,OAAO;4BACT,CAAC;4BACD,IAAI,eAAe,GAAG,gBAAgB,EAAE,CAAC;gCACvC,iEAAiE;gCACjE,IAAI,CAAC,IAAI,CAAC,iBAAiB,EAAE,eAAe,EAAE,gBAAgB,CAAC,CAAC;4BAClE,CAAC;4BAED,iBAAiB,GAAG,IAAI,CAAC;4BACzB,IAAI,CAAC,UAAU,GAAG,IAAI,CAAC;4BACvB,qDAAqD;4BACrD,KAAK,MAAM,QAAQ,IAAI,gBAAgB,EAAE,CAAC;gCACxC,IAAI,CAAC,WAAW,CAAC,QAAQ,CAAC,CAAC;4BAC7B,CAAC;4BACD,OAAO,CAAC,OAAO,CAAC,CAAC;wBACnB,CAAC;wBAAC,MAAM,CAAC;4BACP,iBAAiB,GAAG,IAAI,CAAC;4BACzB,MAAM,CAAC,IAAI,KAAK,CAAC,yBAAyB,CAAC,CAAC,CAAC;4BAC7C,IAAI,CAAC,OAAO,EAAE,CAAC;wBACjB,CAAC;oBACH,CAAC;yBAAM,CAAC;wBACN,uDAAuD;wBACvD,gBAAgB,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;oBAC/B,CAAC;gBACH,CAAC;qBAAM,CAAC;oBACN,kCAAkC;oBAClC,IAAI,CAAC,WAAW,CAAC,KAAK,CAAC,CAAC;gBAC1B,CAAC;YACH,CAAC,CAAC,CAAC;QACL,CAAC,CAAC,CAAC;IACL,CAAC;IAEO,WAAW,CAAC,KAAkB;QACpC,IAAI,CAAC,gBAAgB,CAAC,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC;YAClC,yDAAyD;YACzD,OAAO;QACT,CAAC;QAED,QAAQ,KAAK,CAAC,IAAI,EAAE,CAAC;YACnB,KAAK,SAAS,CAAC,IAAI;gBACjB,IAAI,CAAC,IAAI,CAAC,MAAM,EAAE,KAAK,CAAC,OAAO,CAAC,CAAC;gBACjC,MAAM;YACR,KAAK,SAAS,CAAC,IAAI,CAAC,CAAC,CAAC;gBACpB,IAAI,CAAC;oBACH,MAAM,IAAI,GAAG,gBAAgB,CAAc,KAAK,CAAC,OAAO,CAAC,CAAC;oBAC1D,IAAI,CAAC,IAAI,CAAC,MAAM,EAAE,IAAI,CAAC,CAAC;gBAC1B,CAAC;gBAAC,MAAM,CAAC;oBACP,IAAI,CAAC,aAAa,CAAC,IAAI,KAAK,CAAC,sBAAsB,CAAC,CAAC,CAAC;gBACxD,CAAC;gBACD,MAAM;YACR,CAAC;YACD,KAAK,SAAS,CAAC,MAAM;gBACnB,IAAI,CAAC,UAAU,GAAG,KAAK,CAAC,OAAO,CAAC;gBAChC,IAAI,CAAC,IAAI,CAAC,QAAQ,EAAE,KAAK,CAAC,OAAO,CAAC,CAAC;gBACnC,MAAM;YACR,KAAK,SAAS,CAAC,IAAI;gBACjB,IAAI,CAAC,MAAM,EAAE,KAAK,CAAC,UAAU,EAAE,CAAC,CAAC;gBACjC,MAAM;YACR,KAAK,SAAS,CAAC,IAAI;gBACjB,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;gBAClB,MAAM;YACR,KAAK,SAAS,CAAC,OAAO;gBACpB,6CAA6C;gBAC7C,MAAM;YACR;gBACE,uEAAuE;gBACvE,qCAAqC;gBACrC,MAAM;QACV,CAAC;IACH,CAAC;IAED,UAAU;QACR,IAAI,CAAC,OAAO,EAAE,CAAC;IACjB,CAAC;IAEO,OAAO;QACb,IAAI,CAAC,UAAU,GAAG,KAAK,CAAC;QACxB,IAAI,IAAI,CAAC,MAAM,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,SAAS,EAAE,CAAC;YAC1C,IAAI,CAAC,MAAM,CAAC,OAAO,EAAE,CAAC;QACxB,CAAC;QACD,IAAI,CAAC,MAAM,GAAG,IAAI,CAAC;IACrB,CAAC;IAED,KAAK,CAAC,IAAqB;QACzB,IAAI,CAAC,IAAI,CAAC,UAAU,IAAI,CAAC,IAAI,CAAC,MAAM;YAAE,OAAO;QAC7C,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,UAAU,CAAC,IAAI,CAAC,CAAC,CAAC;IACtC,CAAC;IAED,MAAM,CAAC,IAAY,EAAE,IAAY;QAC/B,IAAI,CAAC,IAAI,CAAC,UAAU,IAAI,CAAC,IAAI,CAAC,MAAM;YAAE,OAAO;QAC7C,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,YAAY,CAAC,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC;IAClD,CAAC;IAED,MAAM,CAAC,GAAW;QAChB,IAAI,CAAC,IAAI,CAAC,UAAU,IAAI,CAAC,IAAI,CAAC,MAAM;YAAE,OAAO;QAC7C,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,YAAY,CAAC,EAAE,MAAM,EAAE,GAAG,EAAE,CAAC,CAAC,CAAC;IACnD,CAAC;IAED,KAAK,CAAC,GAAiB;QACrB,IAAI,CAAC,IAAI,CAAC,UAAU,IAAI,CAAC,IAAI,CAAC,MAAM;YAAE,OAAO;QAC7C,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,WAAW,CAAC,GAAG,CAAC,CAAC,CAAC;IACtC,CAAC;IAED,IAAI;QACF,IAAI,CAAC,IAAI,CAAC,UAAU,IAAI,CAAC,IAAI,CAAC,MAAM;YAAE,OAAO;QAC7C,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,UAAU,EAAE,CAAC,CAAC;IAClC,CAAC;IAED,0DAA0D;IAC1D,aAAa;QACX,OAAO,IAAI,CAAC,UAAU,CAAC;IACzB,CAAC;CACF"}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Shepherd main: standalone entry point spawned by Tower as a detached process.
|
|
4
|
+
*
|
|
5
|
+
* Usage:
|
|
6
|
+
* node shepherd-main.js <json-config>
|
|
7
|
+
*
|
|
8
|
+
* Where json-config is a JSON string with:
|
|
9
|
+
* { command, args, cwd, env, cols, rows, socketPath }
|
|
10
|
+
*
|
|
11
|
+
* On startup:
|
|
12
|
+
* 1. Creates socket directory with 0700 permissions
|
|
13
|
+
* 2. Spawns PTY with requested command
|
|
14
|
+
* 3. Writes { pid, startTime } to stdout as JSON, then closes stdout
|
|
15
|
+
* 4. Listens on Unix socket at socketPath
|
|
16
|
+
* 5. Handles SIGTERM gracefully
|
|
17
|
+
*/
|
|
18
|
+
export {};
|
|
19
|
+
//# sourceMappingURL=shepherd-main.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"shepherd-main.d.ts","sourceRoot":"","sources":["../../src/terminal/shepherd-main.ts"],"names":[],"mappings":";AAEA;;;;;;;;;;;;;;;GAeG"}
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Shepherd main: standalone entry point spawned by Tower as a detached process.
|
|
4
|
+
*
|
|
5
|
+
* Usage:
|
|
6
|
+
* node shepherd-main.js <json-config>
|
|
7
|
+
*
|
|
8
|
+
* Where json-config is a JSON string with:
|
|
9
|
+
* { command, args, cwd, env, cols, rows, socketPath }
|
|
10
|
+
*
|
|
11
|
+
* On startup:
|
|
12
|
+
* 1. Creates socket directory with 0700 permissions
|
|
13
|
+
* 2. Spawns PTY with requested command
|
|
14
|
+
* 3. Writes { pid, startTime } to stdout as JSON, then closes stdout
|
|
15
|
+
* 4. Listens on Unix socket at socketPath
|
|
16
|
+
* 5. Handles SIGTERM gracefully
|
|
17
|
+
*/
|
|
18
|
+
import fs from 'node:fs';
|
|
19
|
+
import path from 'node:path';
|
|
20
|
+
import { createRequire } from 'node:module';
|
|
21
|
+
import { ShepherdProcess } from './shepherd-process.js';
|
|
22
|
+
// createRequire enables importing native/CJS modules (like node-pty) from ESM.
|
|
23
|
+
// The package uses "type": "module", so bare `require()` is not available.
|
|
24
|
+
const require = createRequire(import.meta.url);
|
|
25
|
+
function createRealPty() {
|
|
26
|
+
let ptyModule = null;
|
|
27
|
+
let ptyInstance = null;
|
|
28
|
+
let dataCallback = null;
|
|
29
|
+
let exitCallback = null;
|
|
30
|
+
return {
|
|
31
|
+
spawn(command, args, options) {
|
|
32
|
+
// Load node-pty at spawn time via createRequire (defined at module level).
|
|
33
|
+
// node-pty is a native CJS module; createRequire handles ESM→CJS interop.
|
|
34
|
+
ptyModule = require('node-pty');
|
|
35
|
+
ptyInstance = ptyModule.spawn(command, args, {
|
|
36
|
+
name: options.name ?? 'xterm-256color',
|
|
37
|
+
cols: options.cols,
|
|
38
|
+
rows: options.rows,
|
|
39
|
+
cwd: options.cwd,
|
|
40
|
+
env: options.env,
|
|
41
|
+
});
|
|
42
|
+
if (dataCallback) {
|
|
43
|
+
ptyInstance.onData(dataCallback);
|
|
44
|
+
}
|
|
45
|
+
if (exitCallback) {
|
|
46
|
+
ptyInstance.onExit(exitCallback);
|
|
47
|
+
}
|
|
48
|
+
},
|
|
49
|
+
write(data) {
|
|
50
|
+
ptyInstance?.write(data);
|
|
51
|
+
},
|
|
52
|
+
resize(cols, rows) {
|
|
53
|
+
ptyInstance?.resize(cols, rows);
|
|
54
|
+
},
|
|
55
|
+
kill(signal) {
|
|
56
|
+
if (ptyInstance) {
|
|
57
|
+
try {
|
|
58
|
+
process.kill(ptyInstance.pid, signal ?? 15);
|
|
59
|
+
}
|
|
60
|
+
catch {
|
|
61
|
+
// Process already dead
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
},
|
|
65
|
+
onData(callback) {
|
|
66
|
+
dataCallback = callback;
|
|
67
|
+
if (ptyInstance) {
|
|
68
|
+
ptyInstance.onData(callback);
|
|
69
|
+
}
|
|
70
|
+
},
|
|
71
|
+
onExit(callback) {
|
|
72
|
+
exitCallback = callback;
|
|
73
|
+
if (ptyInstance) {
|
|
74
|
+
ptyInstance.onExit(callback);
|
|
75
|
+
}
|
|
76
|
+
},
|
|
77
|
+
get pid() {
|
|
78
|
+
return ptyInstance?.pid ?? -1;
|
|
79
|
+
},
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
async function main() {
|
|
83
|
+
const configJson = process.argv[2];
|
|
84
|
+
if (!configJson) {
|
|
85
|
+
process.stderr.write('Usage: shepherd-main.js <json-config>\n');
|
|
86
|
+
process.exit(1);
|
|
87
|
+
}
|
|
88
|
+
let config;
|
|
89
|
+
try {
|
|
90
|
+
config = JSON.parse(configJson);
|
|
91
|
+
}
|
|
92
|
+
catch {
|
|
93
|
+
process.stderr.write('Invalid JSON config\n');
|
|
94
|
+
process.exit(1);
|
|
95
|
+
}
|
|
96
|
+
// Ensure socket directory exists with 0700 permissions
|
|
97
|
+
const socketDir = path.dirname(config.socketPath);
|
|
98
|
+
fs.mkdirSync(socketDir, { recursive: true, mode: 0o700 });
|
|
99
|
+
// Remove stale socket file if it exists
|
|
100
|
+
try {
|
|
101
|
+
const stat = fs.lstatSync(config.socketPath);
|
|
102
|
+
if (stat.isSocket()) {
|
|
103
|
+
fs.unlinkSync(config.socketPath);
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
catch {
|
|
107
|
+
// File doesn't exist — fine
|
|
108
|
+
}
|
|
109
|
+
const shepherd = new ShepherdProcess(createRealPty, config.socketPath, config.replayBufferLines ?? 10_000);
|
|
110
|
+
// Start the shepherd (spawns PTY and listens on socket)
|
|
111
|
+
await shepherd.start(config.command, config.args, config.cwd, config.env, config.cols, config.rows);
|
|
112
|
+
// Write PID and start time to stdout, then close stdout
|
|
113
|
+
const info = JSON.stringify({
|
|
114
|
+
pid: process.pid,
|
|
115
|
+
startTime: shepherd.getStartTime(),
|
|
116
|
+
});
|
|
117
|
+
process.stdout.write(info, () => {
|
|
118
|
+
// Close stdout after write completes (Tower reads this, then we detach)
|
|
119
|
+
try {
|
|
120
|
+
fs.closeSync(1);
|
|
121
|
+
}
|
|
122
|
+
catch {
|
|
123
|
+
// Already closed or not available
|
|
124
|
+
}
|
|
125
|
+
});
|
|
126
|
+
// Handle SIGTERM: graceful shutdown
|
|
127
|
+
process.on('SIGTERM', () => {
|
|
128
|
+
shepherd.shutdown();
|
|
129
|
+
// Clean up socket file
|
|
130
|
+
try {
|
|
131
|
+
fs.unlinkSync(config.socketPath);
|
|
132
|
+
}
|
|
133
|
+
catch {
|
|
134
|
+
// Already gone
|
|
135
|
+
}
|
|
136
|
+
process.exit(0);
|
|
137
|
+
});
|
|
138
|
+
// When the child process exits and no connection is active, exit the shepherd
|
|
139
|
+
shepherd.on('exit', () => {
|
|
140
|
+
// Don't exit immediately — Tower might send a SPAWN frame to restart.
|
|
141
|
+
// The shepherd stays alive as long as the socket server is running.
|
|
142
|
+
// If Tower never reconnects, the shepherd will be cleaned up by stale
|
|
143
|
+
// socket cleanup on next Tower startup.
|
|
144
|
+
});
|
|
145
|
+
shepherd.on('error', (err) => {
|
|
146
|
+
process.stderr.write(`Shepherd error: ${err.message}\n`);
|
|
147
|
+
});
|
|
148
|
+
}
|
|
149
|
+
main().catch((err) => {
|
|
150
|
+
process.stderr.write(`Shepherd fatal: ${err.message}\n`);
|
|
151
|
+
process.exit(1);
|
|
152
|
+
});
|
|
153
|
+
//# sourceMappingURL=shepherd-main.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"shepherd-main.js","sourceRoot":"","sources":["../../src/terminal/shepherd-main.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ShepherdProcess: the testable core logic of the shepherd daemon.
|
|
3
|
+
*
|
|
4
|
+
* Owns a single PTY via the IShepherdPty interface (injected for testability).
|
|
5
|
+
* Listens on a Unix socket for a single Tower connection. Handles the binary
|
|
6
|
+
* wire protocol: HELLO/WELCOME handshake, DATA forwarding, RESIZE, SIGNAL,
|
|
7
|
+
* SPAWN, PING/PONG, and EXIT lifecycle.
|
|
8
|
+
*
|
|
9
|
+
* The shepherd accepts only one Tower connection at a time. A new connection
|
|
10
|
+
* replaces the old one (handles rapid Tower restarts cleanly).
|
|
11
|
+
*/
|
|
12
|
+
import net from 'node:net';
|
|
13
|
+
import { EventEmitter } from 'node:events';
|
|
14
|
+
export interface PtyOptions {
|
|
15
|
+
name?: string;
|
|
16
|
+
cols: number;
|
|
17
|
+
rows: number;
|
|
18
|
+
cwd: string;
|
|
19
|
+
env: Record<string, string>;
|
|
20
|
+
}
|
|
21
|
+
export interface IShepherdPty {
|
|
22
|
+
spawn(command: string, args: string[], options: PtyOptions): void;
|
|
23
|
+
write(data: string): void;
|
|
24
|
+
resize(cols: number, rows: number): void;
|
|
25
|
+
kill(signal?: number): void;
|
|
26
|
+
onData(callback: (data: string) => void): void;
|
|
27
|
+
onExit(callback: (exitInfo: {
|
|
28
|
+
exitCode: number;
|
|
29
|
+
signal?: number;
|
|
30
|
+
}) => void): void;
|
|
31
|
+
pid: number;
|
|
32
|
+
}
|
|
33
|
+
export declare class ShepherdProcess extends EventEmitter {
|
|
34
|
+
private readonly ptyFactory;
|
|
35
|
+
private readonly socketPath;
|
|
36
|
+
private pty;
|
|
37
|
+
private server;
|
|
38
|
+
private currentConnection;
|
|
39
|
+
private replayBuffer;
|
|
40
|
+
private cols;
|
|
41
|
+
private rows;
|
|
42
|
+
private startTime;
|
|
43
|
+
private exited;
|
|
44
|
+
constructor(ptyFactory: () => IShepherdPty, socketPath: string, replayBufferLines?: number);
|
|
45
|
+
/**
|
|
46
|
+
* Start the shepherd: spawn the PTY and begin listening on the Unix socket.
|
|
47
|
+
*/
|
|
48
|
+
start(command: string, args: string[], cwd: string, env: Record<string, string>, cols: number, rows: number): Promise<void>;
|
|
49
|
+
private spawnPty;
|
|
50
|
+
private listen;
|
|
51
|
+
/**
|
|
52
|
+
* Handle an incoming Tower connection. Only one connection is active at a
|
|
53
|
+
* time — a new connection closes the previous one.
|
|
54
|
+
*/
|
|
55
|
+
handleConnection(socket: net.Socket): void;
|
|
56
|
+
private handleFrame;
|
|
57
|
+
private handleHello;
|
|
58
|
+
private handleData;
|
|
59
|
+
private handleResize;
|
|
60
|
+
private handleSignal;
|
|
61
|
+
private handleSpawn;
|
|
62
|
+
/** Get the current replay buffer data. */
|
|
63
|
+
getReplayData(): Buffer;
|
|
64
|
+
/** Get the process start time (epoch ms). */
|
|
65
|
+
getStartTime(): number;
|
|
66
|
+
/** Get the current PTY PID. */
|
|
67
|
+
getPid(): number;
|
|
68
|
+
/** Whether the child process has exited. */
|
|
69
|
+
get hasExited(): boolean;
|
|
70
|
+
/**
|
|
71
|
+
* Graceful shutdown: kill child process, close socket server, clean up.
|
|
72
|
+
*/
|
|
73
|
+
shutdown(): void;
|
|
74
|
+
}
|
|
75
|
+
//# sourceMappingURL=shepherd-process.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"shepherd-process.d.ts","sourceRoot":"","sources":["../../src/terminal/shepherd-process.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAGH,OAAO,GAAG,MAAM,UAAU,CAAC;AAC3B,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAwB3C,MAAM,WAAW,UAAU;IACzB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,GAAG,EAAE,MAAM,CAAC;IACZ,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CAC7B;AAED,MAAM,WAAW,YAAY;IAC3B,KAAK,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,EAAE,OAAO,EAAE,UAAU,GAAG,IAAI,CAAC;IAClE,KAAK,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,MAAM,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IACzC,IAAI,CAAC,MAAM,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,MAAM,CAAC,QAAQ,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,GAAG,IAAI,CAAC;IAC/C,MAAM,CAAC,QAAQ,EAAE,CAAC,QAAQ,EAAE;QAAE,QAAQ,EAAE,MAAM,CAAC;QAAC,MAAM,CAAC,EAAE,MAAM,CAAA;KAAE,KAAK,IAAI,GAAG,IAAI,CAAC;IAClF,GAAG,EAAE,MAAM,CAAC;CACb;AAID,qBAAa,eAAgB,SAAQ,YAAY;IAW7C,OAAO,CAAC,QAAQ,CAAC,UAAU;IAC3B,OAAO,CAAC,QAAQ,CAAC,UAAU;IAX7B,OAAO,CAAC,GAAG,CAA6B;IACxC,OAAO,CAAC,MAAM,CAA2B;IACzC,OAAO,CAAC,iBAAiB,CAA2B;IACpD,OAAO,CAAC,YAAY,CAAuB;IAC3C,OAAO,CAAC,IAAI,CAAM;IAClB,OAAO,CAAC,IAAI,CAAM;IAClB,OAAO,CAAC,SAAS,CAAsB;IACvC,OAAO,CAAC,MAAM,CAAS;gBAGJ,UAAU,EAAE,MAAM,YAAY,EAC9B,UAAU,EAAE,MAAM,EACnC,iBAAiB,GAAE,MAAe;IAMpC;;OAEG;IACG,KAAK,CACT,OAAO,EAAE,MAAM,EACf,IAAI,EAAE,MAAM,EAAE,EACd,GAAG,EAAE,MAAM,EACX,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,EAC3B,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,MAAM,GACX,OAAO,CAAC,IAAI,CAAC;IAShB,OAAO,CAAC,QAAQ;IAmDhB,OAAO,CAAC,MAAM;IAwBd;;;OAGG;IACH,gBAAgB,CAAC,MAAM,EAAE,GAAG,CAAC,MAAM,GAAG,IAAI;IAiC1C,OAAO,CAAC,WAAW;IAkCnB,OAAO,CAAC,WAAW;IA2BnB,OAAO,CAAC,UAAU;IAMlB,OAAO,CAAC,YAAY;IAcpB,OAAO,CAAC,YAAY;IAgBpB,OAAO,CAAC,WAAW;IAqBnB,0CAA0C;IAC1C,aAAa,IAAI,MAAM;IAIvB,6CAA6C;IAC7C,YAAY,IAAI,MAAM;IAItB,+BAA+B;IAC/B,MAAM,IAAI,MAAM;IAIhB,4CAA4C;IAC5C,IAAI,SAAS,IAAI,OAAO,CAEvB;IAED;;OAEG;IACH,QAAQ,IAAI,IAAI;CAiBjB"}
|