@source-repo/rpc-cli 4.5.0 → 5.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (55) hide show
  1. package/README.md +1 -0
  2. package/dist/console.d.ts +15 -2
  3. package/dist/console.d.ts.map +1 -1
  4. package/dist/console.js +49 -15
  5. package/dist/console.js.map +1 -1
  6. package/dist/console.types.json +483 -0
  7. package/dist/credentials.d.ts +77 -0
  8. package/dist/credentials.d.ts.map +1 -0
  9. package/dist/credentials.js +118 -0
  10. package/dist/credentials.js.map +1 -0
  11. package/dist/enrolment.d.ts +91 -0
  12. package/dist/enrolment.d.ts.map +1 -0
  13. package/dist/enrolment.js +94 -0
  14. package/dist/enrolment.js.map +1 -0
  15. package/dist/extract.d.ts.map +1 -1
  16. package/dist/extract.js +97 -3
  17. package/dist/extract.js.map +1 -1
  18. package/dist/grants.d.ts +25 -0
  19. package/dist/grants.d.ts.map +1 -0
  20. package/dist/grants.js +62 -0
  21. package/dist/grants.js.map +1 -0
  22. package/dist/index.js +254 -41
  23. package/dist/index.js.map +1 -1
  24. package/dist/mcp.d.ts +5 -0
  25. package/dist/mcp.d.ts.map +1 -1
  26. package/dist/mcp.js +329 -2
  27. package/dist/mcp.js.map +1 -1
  28. package/dist/network.d.ts +47 -1
  29. package/dist/network.d.ts.map +1 -1
  30. package/dist/network.js +17 -1
  31. package/dist/network.js.map +1 -1
  32. package/dist/node.d.ts +15 -0
  33. package/dist/node.d.ts.map +1 -1
  34. package/dist/node.js +14 -1
  35. package/dist/node.js.map +1 -1
  36. package/dist/pairing.d.ts +52 -0
  37. package/dist/pairing.d.ts.map +1 -0
  38. package/dist/pairing.js +57 -0
  39. package/dist/pairing.js.map +1 -0
  40. package/dist/scripting.d.ts +10 -0
  41. package/dist/scripting.d.ts.map +1 -1
  42. package/dist/scripting.js +2 -2
  43. package/dist/scripting.js.map +1 -1
  44. package/dist/scripts.d.ts +27 -3
  45. package/dist/scripts.d.ts.map +1 -1
  46. package/dist/scripts.js +31 -8
  47. package/dist/scripts.js.map +1 -1
  48. package/dist/tasks.d.ts +189 -0
  49. package/dist/tasks.d.ts.map +1 -0
  50. package/dist/tasks.js +485 -0
  51. package/dist/tasks.js.map +1 -0
  52. package/dist/web/app.css +1 -1
  53. package/dist/web/app.js +12 -12
  54. package/dist/web/app.js.map +1 -1
  55. package/package.json +7 -5
package/dist/tasks.js ADDED
@@ -0,0 +1,485 @@
1
+ import { randomBytes } from 'node:crypto';
2
+ import { readFileSync } from 'node:fs';
3
+ import { dirname, resolve } from 'node:path';
4
+ import { defaultSecureWebPort, defaultWebPort } from '@source-repo/rpc';
5
+ import { startConsole } from './console.js';
6
+ import { loadAuthFile, loadSigningKeys, loadTls, readableByOthers, scriptCredentials, signingFrom } from './credentials.js';
7
+ import { looksLikeSchema, startFake } from './fake.js';
8
+ import { grantLines, loadAiGrants } from './grants.js';
9
+ import { startNode } from './node.js';
10
+ /**
11
+ * What `run` starts when nothing is named, and what `run --init` writes when nothing is named.
12
+ *
13
+ * Looked for in the working directory and nowhere else. Walking up the tree the way `package.json`
14
+ * is found would mean that running this three directories deep silently starts the roles described
15
+ * somewhere above — with that file's signing secrets, under that file's peer names, on that file's
16
+ * bus. A task file is an identity, so which one ran must never depend on where the shell happened
17
+ * to be. Named for what it is rather than `tasks.json`, which is already several other things.
18
+ */
19
+ export const defaultTaskFile = 'source-rpc.tasks.json';
20
+ const objectAt = (value, where) => {
21
+ if (!value || typeof value !== 'object' || Array.isArray(value))
22
+ throw new Error(`${where} must be an object`);
23
+ return value;
24
+ };
25
+ const rejectUnknown = (object, allowed, where) => {
26
+ const unknown = Object.keys(object).find((key) => !allowed.includes(key));
27
+ if (unknown)
28
+ throw new Error(`${where} has unknown field "${unknown}"`);
29
+ };
30
+ const optionalString = (object, key, where) => {
31
+ const value = object[key];
32
+ if (value === undefined)
33
+ return undefined;
34
+ if (typeof value !== 'string' || !value)
35
+ throw new Error(`${where}.${key} must be a non-empty string`);
36
+ return value;
37
+ };
38
+ const requiredString = (object, key, where) => {
39
+ const value = optionalString(object, key, where);
40
+ if (value === undefined)
41
+ throw new Error(`${where}.${key} is required`);
42
+ return value;
43
+ };
44
+ const optionalBoolean = (object, key, where) => {
45
+ const value = object[key];
46
+ if (value === undefined)
47
+ return undefined;
48
+ if (typeof value !== 'boolean')
49
+ throw new Error(`${where}.${key} must be true or false`);
50
+ return value;
51
+ };
52
+ const optionalPositiveNumber = (object, key, where) => {
53
+ const value = object[key];
54
+ if (value === undefined)
55
+ return undefined;
56
+ if (typeof value !== 'number' || !Number.isFinite(value) || value <= 0)
57
+ throw new Error(`${where}.${key} must be a positive number`);
58
+ return value;
59
+ };
60
+ const stringList = (object, key, where) => {
61
+ const value = object[key];
62
+ if (!Array.isArray(value) || !value.length || value.some((item) => typeof item !== 'string' || !item))
63
+ throw new Error(`${where}.${key} must be a non-empty array of peer names`);
64
+ return value;
65
+ };
66
+ /**
67
+ * The broker's own credentials, which are not a peer's identity: MQTT authenticates a *connection*,
68
+ * and every task connecting to the same broker with the same account is still a separate peer with
69
+ * a separate signing secret.
70
+ *
71
+ * A block with neither field is refused rather than ignored, because it would silently turn off the
72
+ * environment fallback below it and connect anonymously to a broker somebody meant to authenticate to.
73
+ */
74
+ const parseMqttAuth = (value, where) => {
75
+ const object = objectAt(value, where);
76
+ rejectUnknown(object, ['username', 'password'], where);
77
+ const username = optionalString(object, 'username', where);
78
+ const password = optionalString(object, 'password', where);
79
+ if (!username && !password)
80
+ throw new Error(`${where} needs a username, a password, or both`);
81
+ return { ...(username ? { username } : {}), ...(password ? { password } : {}) };
82
+ };
83
+ const parseNetwork = (value, where) => {
84
+ const object = objectAt(value, where);
85
+ rejectUnknown(object, ['broker', 'hub', 'prefix', 'timeout', 'insecureTls', 'mqtt'], where);
86
+ const broker = optionalString(object, 'broker', where);
87
+ const hub = optionalString(object, 'hub', where);
88
+ const prefix = optionalString(object, 'prefix', where);
89
+ const timeout = optionalPositiveNumber(object, 'timeout', where);
90
+ const insecureTls = optionalBoolean(object, 'insecureTls', where);
91
+ const mqtt = object.mqtt === undefined ? undefined : parseMqttAuth(object.mqtt, `${where}.mqtt`);
92
+ return {
93
+ ...(broker ? { broker } : {}),
94
+ ...(hub ? { hub } : {}),
95
+ ...(prefix ? { prefix } : {}),
96
+ ...(timeout ? { timeout } : {}),
97
+ ...(insecureTls !== undefined ? { insecureTls } : {}),
98
+ ...(mqtt ? { mqtt } : {})
99
+ };
100
+ };
101
+ /**
102
+ * `sign` is a path or the keys themselves, and the shape decides which. One field rather than two
103
+ * because two would need a rule for what happens when both are given, and there is no answer to
104
+ * that which is not a surprise to somebody.
105
+ */
106
+ const parseSign = (value, where) => {
107
+ if (typeof value === 'string') {
108
+ if (!value)
109
+ throw new Error(`${where}.sign must be a path or an object with a "secret"`);
110
+ return value;
111
+ }
112
+ const object = objectAt(value, `${where}.sign`);
113
+ rejectUnknown(object, ['name', 'secret', 'peers'], `${where}.sign`);
114
+ // signingFrom does the rest of the validation, and does it identically for a key file.
115
+ return signingFrom(object, `${where}.sign`).keys;
116
+ };
117
+ const parseAuth = (value, where) => {
118
+ if (typeof value === 'string') {
119
+ if (!value)
120
+ throw new Error(`${where}.auth must be a path or an object with a "token" or a "derive"`);
121
+ return value;
122
+ }
123
+ const object = objectAt(value, `${where}.auth`);
124
+ for (const busField of ['tokens', 'issuers'])
125
+ if (busField in object)
126
+ throw new Error(`${where}.auth."${busField}" belongs to the bus that checks credentials, not to a host role that presents them`);
127
+ rejectUnknown(object, ['token', 'derive'], `${where}.auth`);
128
+ const token = optionalString(object, 'token', `${where}.auth`);
129
+ const derive = optionalString(object, 'derive', `${where}.auth`);
130
+ if (!token && !derive)
131
+ throw new Error(`${where}.auth needs a "token", a "derive", or both`);
132
+ return { ...(token ? { token } : {}), ...(derive ? { derive } : {}) };
133
+ };
134
+ const COMMON_FIELDS = ['id', 'type', 'network', 'name', 'sign', 'auth'];
135
+ const parseTask = (value, index) => {
136
+ const where = `tasks[${index}]`;
137
+ const object = objectAt(value, where);
138
+ const id = requiredString(object, 'id', where);
139
+ const type = requiredString(object, 'type', where);
140
+ if (type !== 'console' && type !== 'node' && type !== 'serve')
141
+ throw new Error(`${where}.type must be console, node, or serve`);
142
+ const network = object.network === undefined ? undefined : parseNetwork(object.network, `${where}.network`);
143
+ const name = optionalString(object, 'name', where);
144
+ const sign = object.sign === undefined ? undefined : parseSign(object.sign, where);
145
+ const auth = object.auth === undefined ? undefined : parseAuth(object.auth, where);
146
+ const common = { id, type, ...(network ? { network } : {}), ...(name ? { name } : {}), ...(sign ? { sign } : {}), ...(auth ? { auth } : {}) };
147
+ if (type === 'console') {
148
+ rejectUnknown(object, [...COMMON_FIELDS, 'host', 'port', 'basePath', 'cert', 'key'], where);
149
+ const host = optionalString(object, 'host', where);
150
+ const port = optionalPositiveNumber(object, 'port', where);
151
+ if (port !== undefined && !Number.isInteger(port))
152
+ throw new Error(`${where}.port must be an integer`);
153
+ const basePath = optionalString(object, 'basePath', where);
154
+ const cert = optionalString(object, 'cert', where);
155
+ const key = optionalString(object, 'key', where);
156
+ // One without the other opens a port that listens and then fails every handshake, which is
157
+ // a console that looks like it started and answers nobody.
158
+ if (!!cert !== !!key)
159
+ throw new Error(`${where}.cert and ${where}.key go together; got only ${cert ? 'cert' : 'key'}`);
160
+ return {
161
+ ...common,
162
+ type,
163
+ ...(host ? { host } : {}),
164
+ ...(port ? { port } : {}),
165
+ ...(basePath ? { basePath } : {}),
166
+ ...(cert && key ? { cert, key } : {})
167
+ };
168
+ }
169
+ if (type === 'node') {
170
+ rejectUnknown(object, [...COMMON_FIELDS, 'scripts', 'scriptableBy', 'grants'], where);
171
+ const grants = optionalString(object, 'grants', where);
172
+ return {
173
+ ...common,
174
+ type,
175
+ scripts: requiredString(object, 'scripts', where),
176
+ scriptableBy: stringList(object, 'scriptableBy', where),
177
+ ...(grants ? { grants } : {})
178
+ };
179
+ }
180
+ rejectUnknown(object, [...COMMON_FIELDS, 'contract', 'script', 'allowExec'], where);
181
+ const script = optionalString(object, 'script', where);
182
+ const allowExec = optionalBoolean(object, 'allowExec', where);
183
+ return {
184
+ ...common,
185
+ type,
186
+ contract: requiredString(object, 'contract', where),
187
+ ...(script ? { script } : {}),
188
+ ...(allowExec !== undefined ? { allowExec } : {})
189
+ };
190
+ };
191
+ /** Parse strictly so a misspelled setting is a startup error rather than an ignored intention. */
192
+ export const parseTaskFile = (value) => {
193
+ const root = objectAt(value, 'task file');
194
+ rejectUnknown(root, ['version', 'network', 'tasks'], 'task file');
195
+ if (root.version !== 1)
196
+ throw new Error('task file.version must be 1');
197
+ const network = root.network === undefined ? undefined : parseNetwork(root.network, 'task file.network');
198
+ if (!Array.isArray(root.tasks) || !root.tasks.length)
199
+ throw new Error('task file.tasks must be a non-empty array');
200
+ const tasks = root.tasks.map(parseTask);
201
+ const duplicate = tasks.find((task, index) => tasks.findIndex((candidate) => candidate.id === task.id) !== index);
202
+ if (duplicate)
203
+ throw new Error(`task file has duplicate task id "${duplicate.id}"`);
204
+ return { version: 1, ...(network ? { network } : {}), tasks };
205
+ };
206
+ const readJson = (path, what) => {
207
+ try {
208
+ return JSON.parse(readFileSync(path, 'utf8'));
209
+ }
210
+ catch (e) {
211
+ throw new Error(`cannot read ${what} from ${path}: ${e instanceof Error ? e.message : String(e)}`, { cause: e });
212
+ }
213
+ };
214
+ /** Where a task's identity came from: a file beside the task file, or the task file itself. */
215
+ const signingFor = (task, directory, warning) => {
216
+ if (!task.sign)
217
+ return undefined;
218
+ if (typeof task.sign !== 'string')
219
+ return { signing: signingFrom(task.sign, `task "${task.id}"`), where: 'this task file' };
220
+ const path = resolve(directory, task.sign);
221
+ const loaded = loadSigningKeys(path);
222
+ if (loaded.readableByOthers)
223
+ warning(`task "${task.id}": ${path} is readable by other users`);
224
+ return { signing: loaded, where: path };
225
+ };
226
+ /**
227
+ * A task's credentials, from a file or written inline.
228
+ *
229
+ * An auth *file* may carry `tokens` and `issuers` - the same file is what a `broker` reads - so what
230
+ * a host role would do with them is nothing. They are dropped here rather than refused, since one
231
+ * file describing both ends of a bus is a reasonable thing to keep.
232
+ */
233
+ const authFor = (task, directory, warning) => {
234
+ if (!task.auth)
235
+ return {};
236
+ if (typeof task.auth !== 'string')
237
+ return task.auth;
238
+ const path = resolve(directory, task.auth);
239
+ const loaded = loadAuthFile(path);
240
+ if (loaded.readableByOthers)
241
+ warning(`task "${task.id}": ${path} is readable by other users`);
242
+ return { ...(loaded.auth.token ? { token: loaded.auth.token } : {}), ...(loaded.auth.derive ? { derive: loaded.auth.derive } : {}) };
243
+ };
244
+ const prepareTasks = (file, config, callbacks) => {
245
+ const directory = dirname(file);
246
+ const warn = (message) => callbacks.warning?.(message);
247
+ const prepared = config.tasks.map((task) => {
248
+ const merged = { ...config.network, ...task.network };
249
+ if (!merged.broker && !merged.hub)
250
+ throw new Error(`task "${task.id}": network needs broker, hub, or both`);
251
+ const identity = signingFor(task, directory, warn);
252
+ const signing = identity?.signing;
253
+ if (signing?.keys.name && task.name && signing.keys.name !== task.name)
254
+ throw new Error(`task "${task.id}": name "${task.name}" does not match "${signing.keys.name}" in ${identity?.where}`);
255
+ const auth = authFor(task, directory, warn);
256
+ const network = {
257
+ ...(merged.broker ? { broker: merged.broker } : {}),
258
+ ...(merged.hub ? { hub: merged.hub } : {}),
259
+ ...(merged.prefix ? { prefix: merged.prefix } : {}),
260
+ name: task.name ?? signing?.keys.name ?? task.id,
261
+ callTimeout: merged.timeout ?? 10000,
262
+ ...(merged.insecureTls ? { insecureTls: true } : {}),
263
+ ...(merged.mqtt ? { mqttAuth: merged.mqtt } : {}),
264
+ ...(signing ? { sign: signing.sign, ...(signing.verify ? { verify: signing.verify } : {}) } : {}),
265
+ // Presented to a hub and never to a broker: MQTT authenticates at the broker, with the
266
+ // account under `network.mqtt` or in the environment, and a bearer token has no part in it.
267
+ ...(auth.token ? { hubCredentials: { token: auth.token } } : {})
268
+ };
269
+ if (auth.token && merged.broker && !merged.hub)
270
+ warn(`task "${task.id}": auth.token is presented to a hub, and this task has only a broker, so nothing will read it`);
271
+ // Set up here rather than at startup so a credential nothing will check is reported while
272
+ // the file is being read, along with every other thing wrong with it.
273
+ const credentialFor = task.type === 'node' ? scriptCredentials(auth, network.name, (message) => warn(`task "${task.id}": ${message}`)) : undefined;
274
+ // Read now rather than at startup, so a missing certificate is one of the things wrong with
275
+ // the file rather than a rollback halfway through starting it.
276
+ const tls = task.type === 'console' && task.cert && task.key ? loadTls(resolve(directory, task.cert), resolve(directory, task.key)) : undefined;
277
+ // Read with everything else, so an unreadable security policy is one of the things wrong
278
+ // with the file rather than a node that starts and then disagrees with its operator.
279
+ const aiGrants = task.type === 'node' && task.grants ? loadAiGrants(resolve(directory, task.grants)) : undefined;
280
+ if (task.type === 'serve') {
281
+ const contractPath = resolve(directory, task.contract);
282
+ const schema = readJson(contractPath, 'contract');
283
+ if (!looksLikeSchema(schema))
284
+ throw new Error(`task "${task.id}": ${contractPath} is not a Source RPC contract`);
285
+ const scriptPath = task.script ? resolve(directory, task.script) : undefined;
286
+ const script = scriptPath ? readJson(scriptPath, 'script') : undefined;
287
+ return { task, network, schema, ...(script ? { script } : {}) };
288
+ }
289
+ return { task, network, ...(credentialFor ? { credentialFor } : {}), ...(tls ? { tls } : {}), ...(aiGrants ? { aiGrants } : {}) };
290
+ });
291
+ const duplicate = prepared.find((entry, index) => prepared.findIndex((candidate) => candidate.network.name === entry.network.name) !== index);
292
+ if (duplicate)
293
+ throw new Error(`task file starts more than one peer named "${duplicate.network.name}"`);
294
+ return prepared;
295
+ };
296
+ /**
297
+ * A certificate moves the default port the same way `--cert` does, so the convention holds without
298
+ * anyone having to remember it. An explicit port always wins, since a plant with its own numbering
299
+ * has the last word.
300
+ */
301
+ export const consolePortFor = (task, secure) => task.port ?? (secure ? defaultSecureWebPort : defaultWebPort);
302
+ const closeStarted = async (started) => {
303
+ const failures = [];
304
+ for (const task of [...started].reverse()) {
305
+ try {
306
+ await task.close();
307
+ }
308
+ catch (e) {
309
+ failures.push(e);
310
+ }
311
+ }
312
+ if (failures.length)
313
+ throw new AggregateError(failures, `failed to close ${failures.length} task${failures.length === 1 ? '' : 's'}`);
314
+ };
315
+ /**
316
+ * Whether the file holds secret material of its own rather than pointing at files that do.
317
+ *
318
+ * The mode warning has to follow the secrets. A task file that only names key files is configuration
319
+ * and can be committed; one with a secret inside it is a key file, and the check that has always
320
+ * guarded those has to guard this too or moving the secrets in would quietly lose it.
321
+ */
322
+ const carriesSecrets = (config) => !!config.network?.mqtt?.password ||
323
+ config.tasks.some((task) => typeof task.sign === 'object' || typeof task.auth === 'object' || !!task.network?.mqtt?.password);
324
+ /** Start every task in one process, rolling back the earlier ones if a later task cannot start. */
325
+ export const startTaskFile = async (path, callbacks = {}) => {
326
+ const file = resolve(path);
327
+ const config = parseTaskFile(readJson(file, 'task file'));
328
+ if (carriesSecrets(config) && readableByOthers(file))
329
+ callbacks.warning?.(`${file} carries secrets and is readable by other users`);
330
+ const prepared = prepareTasks(file, config, callbacks);
331
+ const running = [];
332
+ for (const entry of prepared) {
333
+ const { task, network } = entry;
334
+ try {
335
+ if (task.type === 'console') {
336
+ const console = await startConsole({
337
+ ...network,
338
+ host: task.host ?? '127.0.0.1',
339
+ port: consolePortFor(task, !!entry.tls),
340
+ ...(entry.tls ? { tls: entry.tls } : {}),
341
+ ...(task.basePath ? { basePath: task.basePath } : {})
342
+ });
343
+ running.push({ info: { id: task.id, type: task.type, name: network.name, url: console.url }, close: console.close });
344
+ if (task.host && task.host !== '127.0.0.1' && task.host !== 'localhost')
345
+ callbacks.warning?.(`task "${task.id}": console is bound to ${task.host}; anyone who can reach it can use its granted authority`);
346
+ }
347
+ else if (task.type === 'node') {
348
+ const node = await startNode({
349
+ ...network,
350
+ scripts: resolve(dirname(file), task.scripts),
351
+ scriptableBy: task.scriptableBy,
352
+ ...(entry.credentialFor ? { credentialFor: entry.credentialFor } : {}),
353
+ ...(entry.aiGrants ? { aiGrants: entry.aiGrants } : {})
354
+ });
355
+ running.push({
356
+ info: { id: task.id, type: task.type, name: network.name },
357
+ close: node.close,
358
+ ...(task.grants
359
+ ? {
360
+ reloadGrants: () => {
361
+ const path = resolve(dirname(file), task.grants);
362
+ let next;
363
+ try {
364
+ next = loadAiGrants(path);
365
+ }
366
+ catch (e) {
367
+ callbacks.warning?.(`task "${task.id}": grants unchanged, ${e instanceof Error ? e.message : String(e)}`);
368
+ return;
369
+ }
370
+ node.setAiGrants(next);
371
+ for (const line of grantLines(next))
372
+ callbacks.warning?.(`task "${task.id}": ${line}`);
373
+ }
374
+ }
375
+ : {})
376
+ });
377
+ // Said whether or not a document was given: this node's scripts carry `ai-program`,
378
+ // so what is open is part of what an operator has just started.
379
+ for (const line of grantLines(entry.aiGrants))
380
+ callbacks.warning?.(`task "${task.id}": ${line}`);
381
+ if (network.broker && !network.sign)
382
+ callbacks.warning?.(`task "${task.id}": unsigned MQTT cannot prove a scripting caller's identity; scripting calls will be refused`);
383
+ }
384
+ else {
385
+ const fake = await startFake({
386
+ ...network,
387
+ schema: entry.schema,
388
+ ...(entry.script ? { script: entry.script } : {}),
389
+ ...(task.allowExec ? { allowExec: true } : {})
390
+ });
391
+ running.push({ info: { id: task.id, type: task.type, name: network.name, namespaces: fake.namespaces }, close: fake.close });
392
+ callbacks.warning?.(`task "${task.id}": serve is a fake, not a device`);
393
+ if (task.allowExec)
394
+ callbacks.warning?.(`task "${task.id}": allowExec runs code supplied by its script`);
395
+ }
396
+ callbacks.started?.(running.at(-1).info);
397
+ }
398
+ catch (e) {
399
+ try {
400
+ await closeStarted(running);
401
+ }
402
+ catch (rollback) {
403
+ throw new AggregateError([e, rollback], `task "${task.id}" failed to start and rollback also failed`, { cause: rollback });
404
+ }
405
+ throw new Error(`task "${task.id}" failed to start: ${e instanceof Error ? e.message : String(e)}`, { cause: e });
406
+ }
407
+ }
408
+ let closed = false;
409
+ return {
410
+ file,
411
+ tasks: running.map(({ info }) => info),
412
+ close: async () => {
413
+ if (closed)
414
+ return;
415
+ closed = true;
416
+ await closeStarted(running);
417
+ },
418
+ reloadGrants: () => {
419
+ for (const task of running)
420
+ task.reloadGrants?.();
421
+ }
422
+ };
423
+ };
424
+ /** 32 bytes from the system generator, which is what every secret in a generated file is. */
425
+ const freshSecret = () => randomBytes(32).toString('base64url');
426
+ /**
427
+ * A task file with the three roles in it, ready to edit.
428
+ *
429
+ * The secrets are real rather than placeholders, and that is the point of generating one at all: the
430
+ * shape of a task file is easy to type out from the documentation, and a secret somebody invents at
431
+ * a keyboard is the part that is reliably done badly. Every role gets its own, and each one's
432
+ * `peers` names the others, so the file that lands is a network whose members already know each
433
+ * other rather than a template with `<secret>` in five places.
434
+ *
435
+ * `controller` is in every `peers` map without being a task here, because the reason to run a
436
+ * scriptable node is that something elsewhere scripts it. Its secret is in this file for whoever
437
+ * sets that machine up to copy out - printing it would put it in a scrollback and a terminal log.
438
+ */
439
+ export const taskFileSkeleton = (options = {}) => {
440
+ const controller = options.controller ?? 'controller';
441
+ const roles = ['host-console', 'host-node', 'host-simulator'];
442
+ const secrets = Object.fromEntries([...roles, controller].map((name) => [name, freshSecret()]));
443
+ // Everyone else on this network, which is every other role here plus the peer that scripts the
444
+ // node. A name in one of these maps and in nobody's `name` is a signature that will never verify,
445
+ // so they are built from the role list rather than written out three times.
446
+ const peers = (self) => Object.fromEntries([...roles.filter((role) => role !== self), controller].map((name) => [name, secrets[name]]));
447
+ return {
448
+ version: 1,
449
+ network: {
450
+ ...(options.hub ? { hub: options.hub } : { broker: options.broker ?? 'mqtt://127.0.0.1:1883' }),
451
+ timeout: 10000
452
+ },
453
+ tasks: [
454
+ {
455
+ id: 'console',
456
+ type: 'console',
457
+ sign: { name: 'host-console', secret: secrets['host-console'], peers: peers('host-console') },
458
+ host: '127.0.0.1',
459
+ port: defaultWebPort
460
+ },
461
+ {
462
+ id: 'node',
463
+ type: 'node',
464
+ sign: { name: 'host-node', secret: secrets['host-node'], peers: peers('host-node') },
465
+ scripts: 'scripts',
466
+ scriptableBy: [controller]
467
+ },
468
+ {
469
+ id: 'simulator',
470
+ type: 'serve',
471
+ sign: { name: 'host-simulator', secret: secrets['host-simulator'], peers: peers('host-simulator') },
472
+ contract: 'contracts/host.types.json'
473
+ }
474
+ ]
475
+ };
476
+ };
477
+ /** What the generated file still needs from whoever generated it, in the order they have to do it. */
478
+ export const taskFileSkeletonNotes = (file, controller = 'controller') => [
479
+ `${file} has three roles with fresh signing secrets, and is readable only by you`,
480
+ 'set network.broker or network.hub to the bus these roles should join',
481
+ `create the scripts directory, and point the simulator's contract at a real one - or delete the tasks you do not want`,
482
+ `"${controller}" is the peer allowed to script the node: give it the secret this file lists for it under peers`,
483
+ 'a bus that authenticates also needs auth.token per task, and network.mqtt for an MQTT account'
484
+ ];
485
+ //# sourceMappingURL=tasks.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tasks.js","sourceRoot":"","sources":["../src/tasks.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,aAAa,CAAA;AACzC,OAAO,EAAE,YAAY,EAAE,MAAM,SAAS,CAAA;AAEtC,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAA;AAC5C,OAAO,EAAE,oBAAoB,EAAE,cAAc,EAAoC,MAAM,kBAAkB,CAAA;AACzG,OAAO,EAAE,YAAY,EAAE,MAAM,cAAc,CAAA;AAC3C,OAAO,EAAE,YAAY,EAAE,eAAe,EAAE,OAAO,EAAE,gBAAgB,EAAE,iBAAiB,EAAE,WAAW,EAAkC,MAAM,kBAAkB,CAAA;AAC3J,OAAO,EAAE,eAAe,EAAE,SAAS,EAAmB,MAAM,WAAW,CAAA;AACvE,OAAO,EAAE,UAAU,EAAE,YAAY,EAAE,MAAM,aAAa,CAAA;AAEtD,OAAO,EAAE,SAAS,EAAE,MAAM,WAAW,CAAA;AAErC;;;;;;;;GAQG;AACH,MAAM,CAAC,MAAM,eAAe,GAAG,uBAAuB,CAAA;AA2GtD,MAAM,QAAQ,GAAG,CAAC,KAAc,EAAE,KAAa,EAAc,EAAE;IAC3D,IAAI,CAAC,KAAK,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC;QAAE,MAAM,IAAI,KAAK,CAAC,GAAG,KAAK,oBAAoB,CAAC,CAAA;IAC9G,OAAO,KAAmB,CAAA;AAC9B,CAAC,CAAA;AAED,MAAM,aAAa,GAAG,CAAC,MAAkB,EAAE,OAA0B,EAAE,KAAa,EAAE,EAAE;IACpF,MAAM,OAAO,GAAG,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,CAAC,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAA;IACzE,IAAI,OAAO;QAAE,MAAM,IAAI,KAAK,CAAC,GAAG,KAAK,uBAAuB,OAAO,GAAG,CAAC,CAAA;AAC3E,CAAC,CAAA;AAED,MAAM,cAAc,GAAG,CAAC,MAAkB,EAAE,GAAW,EAAE,KAAa,EAAE,EAAE;IACtE,MAAM,KAAK,GAAG,MAAM,CAAC,GAAG,CAAC,CAAA;IACzB,IAAI,KAAK,KAAK,SAAS;QAAE,OAAO,SAAS,CAAA;IACzC,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,CAAC,KAAK;QAAE,MAAM,IAAI,KAAK,CAAC,GAAG,KAAK,IAAI,GAAG,6BAA6B,CAAC,CAAA;IACtG,OAAO,KAAK,CAAA;AAChB,CAAC,CAAA;AAED,MAAM,cAAc,GAAG,CAAC,MAAkB,EAAE,GAAW,EAAE,KAAa,EAAE,EAAE;IACtE,MAAM,KAAK,GAAG,cAAc,CAAC,MAAM,EAAE,GAAG,EAAE,KAAK,CAAC,CAAA;IAChD,IAAI,KAAK,KAAK,SAAS;QAAE,MAAM,IAAI,KAAK,CAAC,GAAG,KAAK,IAAI,GAAG,cAAc,CAAC,CAAA;IACvE,OAAO,KAAK,CAAA;AAChB,CAAC,CAAA;AAED,MAAM,eAAe,GAAG,CAAC,MAAkB,EAAE,GAAW,EAAE,KAAa,EAAE,EAAE;IACvE,MAAM,KAAK,GAAG,MAAM,CAAC,GAAG,CAAC,CAAA;IACzB,IAAI,KAAK,KAAK,SAAS;QAAE,OAAO,SAAS,CAAA;IACzC,IAAI,OAAO,KAAK,KAAK,SAAS;QAAE,MAAM,IAAI,KAAK,CAAC,GAAG,KAAK,IAAI,GAAG,wBAAwB,CAAC,CAAA;IACxF,OAAO,KAAK,CAAA;AAChB,CAAC,CAAA;AAED,MAAM,sBAAsB,GAAG,CAAC,MAAkB,EAAE,GAAW,EAAE,KAAa,EAAE,EAAE;IAC9E,MAAM,KAAK,GAAG,MAAM,CAAC,GAAG,CAAC,CAAA;IACzB,IAAI,KAAK,KAAK,SAAS;QAAE,OAAO,SAAS,CAAA;IACzC,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,KAAK,CAAC,IAAI,KAAK,IAAI,CAAC;QAAE,MAAM,IAAI,KAAK,CAAC,GAAG,KAAK,IAAI,GAAG,4BAA4B,CAAC,CAAA;IACpI,OAAO,KAAK,CAAA;AAChB,CAAC,CAAA;AAED,MAAM,UAAU,GAAG,CAAC,MAAkB,EAAE,GAAW,EAAE,KAAa,EAAE,EAAE;IAClE,MAAM,KAAK,GAAG,MAAM,CAAC,GAAG,CAAC,CAAA;IACzB,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,MAAM,IAAI,KAAK,CAAC,IAAI,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,OAAO,IAAI,KAAK,QAAQ,IAAI,CAAC,IAAI,CAAC;QACjG,MAAM,IAAI,KAAK,CAAC,GAAG,KAAK,IAAI,GAAG,0CAA0C,CAAC,CAAA;IAC9E,OAAO,KAAiB,CAAA;AAC5B,CAAC,CAAA;AAED;;;;;;;GAOG;AACH,MAAM,aAAa,GAAG,CAAC,KAAc,EAAE,KAAa,EAAgB,EAAE;IAClE,MAAM,MAAM,GAAG,QAAQ,CAAC,KAAK,EAAE,KAAK,CAAC,CAAA;IACrC,aAAa,CAAC,MAAM,EAAE,CAAC,UAAU,EAAE,UAAU,CAAC,EAAE,KAAK,CAAC,CAAA;IACtD,MAAM,QAAQ,GAAG,cAAc,CAAC,MAAM,EAAE,UAAU,EAAE,KAAK,CAAC,CAAA;IAC1D,MAAM,QAAQ,GAAG,cAAc,CAAC,MAAM,EAAE,UAAU,EAAE,KAAK,CAAC,CAAA;IAC1D,IAAI,CAAC,QAAQ,IAAI,CAAC,QAAQ;QAAE,MAAM,IAAI,KAAK,CAAC,GAAG,KAAK,wCAAwC,CAAC,CAAA;IAC7F,OAAO,EAAE,GAAG,CAAC,QAAQ,CAAC,CAAC,CAAC,EAAE,QAAQ,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,GAAG,CAAC,QAAQ,CAAC,CAAC,CAAC,EAAE,QAAQ,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,CAAA;AACnF,CAAC,CAAA;AAED,MAAM,YAAY,GAAG,CAAC,KAAc,EAAE,KAAa,EAAe,EAAE;IAChE,MAAM,MAAM,GAAG,QAAQ,CAAC,KAAK,EAAE,KAAK,CAAC,CAAA;IACrC,aAAa,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE,KAAK,EAAE,QAAQ,EAAE,SAAS,EAAE,aAAa,EAAE,MAAM,CAAC,EAAE,KAAK,CAAC,CAAA;IAC3F,MAAM,MAAM,GAAG,cAAc,CAAC,MAAM,EAAE,QAAQ,EAAE,KAAK,CAAC,CAAA;IACtD,MAAM,GAAG,GAAG,cAAc,CAAC,MAAM,EAAE,KAAK,EAAE,KAAK,CAAC,CAAA;IAChD,MAAM,MAAM,GAAG,cAAc,CAAC,MAAM,EAAE,QAAQ,EAAE,KAAK,CAAC,CAAA;IACtD,MAAM,OAAO,GAAG,sBAAsB,CAAC,MAAM,EAAE,SAAS,EAAE,KAAK,CAAC,CAAA;IAChE,MAAM,WAAW,GAAG,eAAe,CAAC,MAAM,EAAE,aAAa,EAAE,KAAK,CAAC,CAAA;IACjE,MAAM,IAAI,GAAG,MAAM,CAAC,IAAI,KAAK,SAAS,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,aAAa,CAAC,MAAM,CAAC,IAAI,EAAE,GAAG,KAAK,OAAO,CAAC,CAAA;IAChG,OAAO;QACH,GAAG,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;QAC7B,GAAG,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,GAAG,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;QACvB,GAAG,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;QAC7B,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,OAAO,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;QAC/B,GAAG,CAAC,WAAW,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,WAAW,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;QACrD,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;KAC5B,CAAA;AACL,CAAC,CAAA;AAED;;;;GAIG;AACH,MAAM,SAAS,GAAG,CAAC,KAAc,EAAE,KAAa,EAAwB,EAAE;IACtE,IAAI,OAAO,KAAK,KAAK,QAAQ,EAAE,CAAC;QAC5B,IAAI,CAAC,KAAK;YAAE,MAAM,IAAI,KAAK,CAAC,GAAG,KAAK,mDAAmD,CAAC,CAAA;QACxF,OAAO,KAAK,CAAA;IAChB,CAAC;IACD,MAAM,MAAM,GAAG,QAAQ,CAAC,KAAK,EAAE,GAAG,KAAK,OAAO,CAAC,CAAA;IAC/C,aAAa,CAAC,MAAM,EAAE,CAAC,MAAM,EAAE,QAAQ,EAAE,OAAO,CAAC,EAAE,GAAG,KAAK,OAAO,CAAC,CAAA;IACnE,uFAAuF;IACvF,OAAO,WAAW,CAAC,MAAM,EAAE,GAAG,KAAK,OAAO,CAAC,CAAC,IAAI,CAAA;AACpD,CAAC,CAAA;AAED,MAAM,SAAS,GAAG,CAAC,KAAc,EAAE,KAAa,EAAqB,EAAE;IACnE,IAAI,OAAO,KAAK,KAAK,QAAQ,EAAE,CAAC;QAC5B,IAAI,CAAC,KAAK;YAAE,MAAM,IAAI,KAAK,CAAC,GAAG,KAAK,gEAAgE,CAAC,CAAA;QACrG,OAAO,KAAK,CAAA;IAChB,CAAC;IACD,MAAM,MAAM,GAAG,QAAQ,CAAC,KAAK,EAAE,GAAG,KAAK,OAAO,CAAC,CAAA;IAC/C,KAAK,MAAM,QAAQ,IAAI,CAAC,QAAQ,EAAE,SAAS,CAAC;QACxC,IAAI,QAAQ,IAAI,MAAM;YAClB,MAAM,IAAI,KAAK,CAAC,GAAG,KAAK,UAAU,QAAQ,qFAAqF,CAAC,CAAA;IACxI,aAAa,CAAC,MAAM,EAAE,CAAC,OAAO,EAAE,QAAQ,CAAC,EAAE,GAAG,KAAK,OAAO,CAAC,CAAA;IAC3D,MAAM,KAAK,GAAG,cAAc,CAAC,MAAM,EAAE,OAAO,EAAE,GAAG,KAAK,OAAO,CAAC,CAAA;IAC9D,MAAM,MAAM,GAAG,cAAc,CAAC,MAAM,EAAE,QAAQ,EAAE,GAAG,KAAK,OAAO,CAAC,CAAA;IAChE,IAAI,CAAC,KAAK,IAAI,CAAC,MAAM;QAAE,MAAM,IAAI,KAAK,CAAC,GAAG,KAAK,4CAA4C,CAAC,CAAA;IAC5F,OAAO,EAAE,GAAG,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,CAAA;AACzE,CAAC,CAAA;AAED,MAAM,aAAa,GAAG,CAAC,IAAI,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,CAAU,CAAA;AAEhF,MAAM,SAAS,GAAG,CAAC,KAAc,EAAE,KAAa,EAAiB,EAAE;IAC/D,MAAM,KAAK,GAAG,SAAS,KAAK,GAAG,CAAA;IAC/B,MAAM,MAAM,GAAG,QAAQ,CAAC,KAAK,EAAE,KAAK,CAAC,CAAA;IACrC,MAAM,EAAE,GAAG,cAAc,CAAC,MAAM,EAAE,IAAI,EAAE,KAAK,CAAC,CAAA;IAC9C,MAAM,IAAI,GAAG,cAAc,CAAC,MAAM,EAAE,MAAM,EAAE,KAAK,CAAC,CAAA;IAClD,IAAI,IAAI,KAAK,SAAS,IAAI,IAAI,KAAK,MAAM,IAAI,IAAI,KAAK,OAAO;QAAE,MAAM,IAAI,KAAK,CAAC,GAAG,KAAK,uCAAuC,CAAC,CAAA;IAE/H,MAAM,OAAO,GAAG,MAAM,CAAC,OAAO,KAAK,SAAS,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,YAAY,CAAC,MAAM,CAAC,OAAO,EAAE,GAAG,KAAK,UAAU,CAAC,CAAA;IAC3G,MAAM,IAAI,GAAG,cAAc,CAAC,MAAM,EAAE,MAAM,EAAE,KAAK,CAAC,CAAA;IAClD,MAAM,IAAI,GAAG,MAAM,CAAC,IAAI,KAAK,SAAS,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,SAAS,CAAC,MAAM,CAAC,IAAI,EAAE,KAAK,CAAC,CAAA;IAClF,MAAM,IAAI,GAAG,MAAM,CAAC,IAAI,KAAK,SAAS,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,SAAS,CAAC,MAAM,CAAC,IAAI,EAAE,KAAK,CAAC,CAAA;IAClF,MAAM,MAAM,GAAG,EAAE,EAAE,EAAE,IAAI,EAAE,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,OAAO,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,CAAA;IAE7I,IAAI,IAAI,KAAK,SAAS,EAAE,CAAC;QACrB,aAAa,CAAC,MAAM,EAAE,CAAC,GAAG,aAAa,EAAE,MAAM,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,KAAK,CAAC,CAAA;QAC3F,MAAM,IAAI,GAAG,cAAc,CAAC,MAAM,EAAE,MAAM,EAAE,KAAK,CAAC,CAAA;QAClD,MAAM,IAAI,GAAG,sBAAsB,CAAC,MAAM,EAAE,MAAM,EAAE,KAAK,CAAC,CAAA;QAC1D,IAAI,IAAI,KAAK,SAAS,IAAI,CAAC,MAAM,CAAC,SAAS,CAAC,IAAI,CAAC;YAAE,MAAM,IAAI,KAAK,CAAC,GAAG,KAAK,0BAA0B,CAAC,CAAA;QACtG,MAAM,QAAQ,GAAG,cAAc,CAAC,MAAM,EAAE,UAAU,EAAE,KAAK,CAAC,CAAA;QAC1D,MAAM,IAAI,GAAG,cAAc,CAAC,MAAM,EAAE,MAAM,EAAE,KAAK,CAAC,CAAA;QAClD,MAAM,GAAG,GAAG,cAAc,CAAC,MAAM,EAAE,KAAK,EAAE,KAAK,CAAC,CAAA;QAChD,2FAA2F;QAC3F,2DAA2D;QAC3D,IAAI,CAAC,CAAC,IAAI,KAAK,CAAC,CAAC,GAAG;YAAE,MAAM,IAAI,KAAK,CAAC,GAAG,KAAK,aAAa,KAAK,8BAA8B,IAAI,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,KAAK,EAAE,CAAC,CAAA;QACtH,OAAO;YACH,GAAG,MAAM;YACT,IAAI;YACJ,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;YACzB,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;YACzB,GAAG,CAAC,QAAQ,CAAC,CAAC,CAAC,EAAE,QAAQ,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;YACjC,GAAG,CAAC,IAAI,IAAI,GAAG,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,GAAG,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;SACxC,CAAA;IACL,CAAC;IAED,IAAI,IAAI,KAAK,MAAM,EAAE,CAAC;QAClB,aAAa,CAAC,MAAM,EAAE,CAAC,GAAG,aAAa,EAAE,SAAS,EAAE,cAAc,EAAE,QAAQ,CAAC,EAAE,KAAK,CAAC,CAAA;QACrF,MAAM,MAAM,GAAG,cAAc,CAAC,MAAM,EAAE,QAAQ,EAAE,KAAK,CAAC,CAAA;QACtD,OAAO;YACH,GAAG,MAAM;YACT,IAAI;YACJ,OAAO,EAAE,cAAc,CAAC,MAAM,EAAE,SAAS,EAAE,KAAK,CAAC;YACjD,YAAY,EAAE,UAAU,CAAC,MAAM,EAAE,cAAc,EAAE,KAAK,CAAC;YACvD,GAAG,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;SAChC,CAAA;IACL,CAAC;IAED,aAAa,CAAC,MAAM,EAAE,CAAC,GAAG,aAAa,EAAE,UAAU,EAAE,QAAQ,EAAE,WAAW,CAAC,EAAE,KAAK,CAAC,CAAA;IACnF,MAAM,MAAM,GAAG,cAAc,CAAC,MAAM,EAAE,QAAQ,EAAE,KAAK,CAAC,CAAA;IACtD,MAAM,SAAS,GAAG,eAAe,CAAC,MAAM,EAAE,WAAW,EAAE,KAAK,CAAC,CAAA;IAC7D,OAAO;QACH,GAAG,MAAM;QACT,IAAI;QACJ,QAAQ,EAAE,cAAc,CAAC,MAAM,EAAE,UAAU,EAAE,KAAK,CAAC;QACnD,GAAG,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;QAC7B,GAAG,CAAC,SAAS,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,SAAS,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;KACpD,CAAA;AACL,CAAC,CAAA;AAED,kGAAkG;AAClG,MAAM,CAAC,MAAM,aAAa,GAAG,CAAC,KAAc,EAAqB,EAAE;IAC/D,MAAM,IAAI,GAAG,QAAQ,CAAC,KAAK,EAAE,WAAW,CAAC,CAAA;IACzC,aAAa,CAAC,IAAI,EAAE,CAAC,SAAS,EAAE,SAAS,EAAE,OAAO,CAAC,EAAE,WAAW,CAAC,CAAA;IACjE,IAAI,IAAI,CAAC,OAAO,KAAK,CAAC;QAAE,MAAM,IAAI,KAAK,CAAC,6BAA6B,CAAC,CAAA;IACtE,MAAM,OAAO,GAAG,IAAI,CAAC,OAAO,KAAK,SAAS,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,YAAY,CAAC,IAAI,CAAC,OAAO,EAAE,mBAAmB,CAAC,CAAA;IACxG,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,MAAM;QAAE,MAAM,IAAI,KAAK,CAAC,2CAA2C,CAAC,CAAA;IAClH,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,SAAS,CAAC,CAAA;IACvC,MAAM,SAAS,GAAG,KAAK,CAAC,IAAI,CAAC,CAAC,IAAI,EAAE,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,SAAS,CAAC,CAAC,SAAS,EAAE,EAAE,CAAC,SAAS,CAAC,EAAE,KAAK,IAAI,CAAC,EAAE,CAAC,KAAK,KAAK,CAAC,CAAA;IACjH,IAAI,SAAS;QAAE,MAAM,IAAI,KAAK,CAAC,oCAAoC,SAAS,CAAC,EAAE,GAAG,CAAC,CAAA;IACnF,OAAO,EAAE,OAAO,EAAE,CAAC,EAAE,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,OAAO,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,KAAK,EAAE,CAAA;AACjE,CAAC,CAAA;AAED,MAAM,QAAQ,GAAG,CAAC,IAAY,EAAE,IAAY,EAAW,EAAE;IACrD,IAAI,CAAC;QACD,OAAO,IAAI,CAAC,KAAK,CAAC,YAAY,CAAC,IAAI,EAAE,MAAM,CAAC,CAAY,CAAA;IAC5D,CAAC;IAAC,OAAO,CAAC,EAAE,CAAC;QACT,MAAM,IAAI,KAAK,CAAC,eAAe,IAAI,SAAS,IAAI,KAAK,CAAC,YAAY,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,EAAE,KAAK,EAAE,CAAC,EAAE,CAAC,CAAA;IACpH,CAAC;AACL,CAAC,CAAA;AAaD,+FAA+F;AAC/F,MAAM,UAAU,GAAG,CAAC,IAAmB,EAAE,SAAiB,EAAE,OAAkC,EAAmD,EAAE;IAC/I,IAAI,CAAC,IAAI,CAAC,IAAI;QAAE,OAAO,SAAS,CAAA;IAChC,IAAI,OAAO,IAAI,CAAC,IAAI,KAAK,QAAQ;QAAE,OAAO,EAAE,OAAO,EAAE,WAAW,CAAC,IAAI,CAAC,IAAI,EAAE,SAAS,IAAI,CAAC,EAAE,GAAG,CAAC,EAAE,KAAK,EAAE,gBAAgB,EAAE,CAAA;IAC3H,MAAM,IAAI,GAAG,OAAO,CAAC,SAAS,EAAE,IAAI,CAAC,IAAI,CAAC,CAAA;IAC1C,MAAM,MAAM,GAAG,eAAe,CAAC,IAAI,CAAC,CAAA;IACpC,IAAI,MAAM,CAAC,gBAAgB;QAAE,OAAO,CAAC,SAAS,IAAI,CAAC,EAAE,MAAM,IAAI,6BAA6B,CAAC,CAAA;IAC7F,OAAO,EAAE,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,IAAI,EAAE,CAAA;AAC3C,CAAC,CAAA;AAED;;;;;;GAMG;AACH,MAAM,OAAO,GAAG,CAAC,IAAmB,EAAE,SAAiB,EAAE,OAAkC,EAAY,EAAE;IACrG,IAAI,CAAC,IAAI,CAAC,IAAI;QAAE,OAAO,EAAE,CAAA;IACzB,IAAI,OAAO,IAAI,CAAC,IAAI,KAAK,QAAQ;QAAE,OAAO,IAAI,CAAC,IAAI,CAAA;IACnD,MAAM,IAAI,GAAG,OAAO,CAAC,SAAS,EAAE,IAAI,CAAC,IAAI,CAAC,CAAA;IAC1C,MAAM,MAAM,GAAG,YAAY,CAAC,IAAI,CAAC,CAAA;IACjC,IAAI,MAAM,CAAC,gBAAgB;QAAE,OAAO,CAAC,SAAS,IAAI,CAAC,EAAE,MAAM,IAAI,6BAA6B,CAAC,CAAA;IAC7F,OAAO,EAAE,GAAG,CAAC,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,MAAM,CAAC,IAAI,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,GAAG,CAAC,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,MAAM,EAAE,MAAM,CAAC,IAAI,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,CAAA;AACxI,CAAC,CAAA;AAED,MAAM,YAAY,GAAG,CAAC,IAAY,EAAE,MAAyB,EAAE,SAA4B,EAAkB,EAAE;IAC3G,MAAM,SAAS,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;IAC/B,MAAM,IAAI,GAAG,CAAC,OAAe,EAAE,EAAE,CAAC,SAAS,CAAC,OAAO,EAAE,CAAC,OAAO,CAAC,CAAA;IAC9D,MAAM,QAAQ,GAAG,MAAM,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE;QACvC,MAAM,MAAM,GAAG,EAAE,GAAG,MAAM,CAAC,OAAO,EAAE,GAAG,IAAI,CAAC,OAAO,EAAE,CAAA;QACrD,IAAI,CAAC,MAAM,CAAC,MAAM,IAAI,CAAC,MAAM,CAAC,GAAG;YAAE,MAAM,IAAI,KAAK,CAAC,SAAS,IAAI,CAAC,EAAE,uCAAuC,CAAC,CAAA;QAE3G,MAAM,QAAQ,GAAG,UAAU,CAAC,IAAI,EAAE,SAAS,EAAE,IAAI,CAAC,CAAA;QAClD,MAAM,OAAO,GAAG,QAAQ,EAAE,OAAO,CAAA;QACjC,IAAI,OAAO,EAAE,IAAI,CAAC,IAAI,IAAI,IAAI,CAAC,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC,IAAI,KAAK,IAAI,CAAC,IAAI;YAClE,MAAM,IAAI,KAAK,CAAC,SAAS,IAAI,CAAC,EAAE,YAAY,IAAI,CAAC,IAAI,qBAAqB,OAAO,CAAC,IAAI,CAAC,IAAI,QAAQ,QAAQ,EAAE,KAAK,EAAE,CAAC,CAAA;QACzH,MAAM,IAAI,GAAG,OAAO,CAAC,IAAI,EAAE,SAAS,EAAE,IAAI,CAAC,CAAA;QAE3C,MAAM,OAAO,GAAmB;YAC5B,GAAG,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;YACnD,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,GAAG,EAAE,MAAM,CAAC,GAAG,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;YAC1C,GAAG,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;YACnD,IAAI,EAAE,IAAI,CAAC,IAAI,IAAI,OAAO,EAAE,IAAI,CAAC,IAAI,IAAI,IAAI,CAAC,EAAE;YAChD,WAAW,EAAE,MAAM,CAAC,OAAO,IAAI,KAAK;YACpC,GAAG,CAAC,MAAM,CAAC,WAAW,CAAC,CAAC,CAAC,EAAE,WAAW,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;YACpD,GAAG,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,QAAQ,EAAE,MAAM,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;YACjD,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,OAAO,CAAC,IAAI,EAAE,GAAG,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,MAAM,EAAE,OAAO,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;YACjG,uFAAuF;YACvF,4FAA4F;YAC5F,GAAG,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,cAAc,EAAE,EAAE,KAAK,EAAE,IAAI,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;SACnE,CAAA;QACD,IAAI,IAAI,CAAC,KAAK,IAAI,MAAM,CAAC,MAAM,IAAI,CAAC,MAAM,CAAC,GAAG;YAC1C,IAAI,CAAC,SAAS,IAAI,CAAC,EAAE,+FAA+F,CAAC,CAAA;QACzH,0FAA0F;QAC1F,sEAAsE;QACtE,MAAM,aAAa,GAAG,IAAI,CAAC,IAAI,KAAK,MAAM,CAAC,CAAC,CAAC,iBAAiB,CAAC,IAAI,EAAE,OAAO,CAAC,IAAI,EAAE,CAAC,OAAO,EAAE,EAAE,CAAC,IAAI,CAAC,SAAS,IAAI,CAAC,EAAE,MAAM,OAAO,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,SAAS,CAAA;QAElJ,4FAA4F;QAC5F,+DAA+D;QAC/D,MAAM,GAAG,GACL,IAAI,CAAC,IAAI,KAAK,SAAS,IAAI,IAAI,CAAC,IAAI,IAAI,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,OAAO,CAAC,OAAO,CAAC,SAAS,EAAE,IAAI,CAAC,IAAI,CAAC,EAAE,OAAO,CAAC,SAAS,EAAE,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,SAAS,CAAA;QACvI,yFAAyF;QACzF,qFAAqF;QACrF,MAAM,QAAQ,GAAG,IAAI,CAAC,IAAI,KAAK,MAAM,IAAI,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,YAAY,CAAC,OAAO,CAAC,SAAS,EAAE,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC,SAAS,CAAA;QAEhH,IAAI,IAAI,CAAC,IAAI,KAAK,OAAO,EAAE,CAAC;YACxB,MAAM,YAAY,GAAG,OAAO,CAAC,SAAS,EAAE,IAAI,CAAC,QAAQ,CAAC,CAAA;YACtD,MAAM,MAAM,GAAG,QAAQ,CAAC,YAAY,EAAE,UAAU,CAAC,CAAA;YACjD,IAAI,CAAC,eAAe,CAAC,MAAM,CAAC;gBAAE,MAAM,IAAI,KAAK,CAAC,SAAS,IAAI,CAAC,EAAE,MAAM,YAAY,+BAA+B,CAAC,CAAA;YAChH,MAAM,UAAU,GAAG,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,OAAO,CAAC,SAAS,EAAE,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,SAAS,CAAA;YAC5E,MAAM,MAAM,GAAG,UAAU,CAAC,CAAC,CAAE,QAAQ,CAAC,UAAU,EAAE,QAAQ,CAAgB,CAAC,CAAC,CAAC,SAAS,CAAA;YACtF,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,MAAM,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,CAAA;QACnE,CAAC;QACD,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,GAAG,CAAC,aAAa,CAAC,CAAC,CAAC,EAAE,aAAa,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,GAAG,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,GAAG,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,GAAG,CAAC,QAAQ,CAAC,CAAC,CAAC,EAAE,QAAQ,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,CAAA;IACrI,CAAC,CAAC,CAAA;IAEF,MAAM,SAAS,GAAG,QAAQ,CAAC,IAAI,CAAC,CAAC,KAAK,EAAE,KAAK,EAAE,EAAE,CAAC,QAAQ,CAAC,SAAS,CAAC,CAAC,SAAS,EAAE,EAAE,CAAC,SAAS,CAAC,OAAO,CAAC,IAAI,KAAK,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,KAAK,KAAK,CAAC,CAAA;IAC7I,IAAI,SAAS;QAAE,MAAM,IAAI,KAAK,CAAC,8CAA8C,SAAS,CAAC,OAAO,CAAC,IAAI,GAAG,CAAC,CAAA;IACvG,OAAO,QAAQ,CAAA;AACnB,CAAC,CAAA;AAED;;;;GAIG;AACH,MAAM,CAAC,MAAM,cAAc,GAAG,CAAC,IAAiB,EAAE,MAAe,EAAE,EAAE,CAAC,IAAI,CAAC,IAAI,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,oBAAoB,CAAC,CAAC,CAAC,cAAc,CAAC,CAAA;AAEnI,MAAM,YAAY,GAAG,KAAK,EAAE,OAAyC,EAAE,EAAE;IACrE,MAAM,QAAQ,GAAc,EAAE,CAAA;IAC9B,KAAK,MAAM,IAAI,IAAI,CAAC,GAAG,OAAO,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC;QACxC,IAAI,CAAC;YACD,MAAM,IAAI,CAAC,KAAK,EAAE,CAAA;QACtB,CAAC;QAAC,OAAO,CAAC,EAAE,CAAC;YACT,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAC,CAAA;QACpB,CAAC;IACL,CAAC;IACD,IAAI,QAAQ,CAAC,MAAM;QAAE,MAAM,IAAI,cAAc,CAAC,QAAQ,EAAE,mBAAmB,QAAQ,CAAC,MAAM,QAAQ,QAAQ,CAAC,MAAM,KAAK,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,GAAG,EAAE,CAAC,CAAA;AACzI,CAAC,CAAA;AAED;;;;;;GAMG;AACH,MAAM,cAAc,GAAG,CAAC,MAAyB,EAAE,EAAE,CACjD,CAAC,CAAC,MAAM,CAAC,OAAO,EAAE,IAAI,EAAE,QAAQ;IAChC,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,OAAO,IAAI,CAAC,IAAI,KAAK,QAAQ,IAAI,OAAO,IAAI,CAAC,IAAI,KAAK,QAAQ,IAAI,CAAC,CAAC,IAAI,CAAC,OAAO,EAAE,IAAI,EAAE,QAAQ,CAAC,CAAA;AAEjI,mGAAmG;AACnG,MAAM,CAAC,MAAM,aAAa,GAAG,KAAK,EAAE,IAAY,EAAE,SAAS,GAAsB,EAAE,EAAwB,EAAE;IACzG,MAAM,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;IAC1B,MAAM,MAAM,GAAG,aAAa,CAAC,QAAQ,CAAC,IAAI,EAAE,WAAW,CAAC,CAAC,CAAA;IACzD,IAAI,cAAc,CAAC,MAAM,CAAC,IAAI,gBAAgB,CAAC,IAAI,CAAC;QAAE,SAAS,CAAC,OAAO,EAAE,CAAC,GAAG,IAAI,iDAAiD,CAAC,CAAA;IACnI,MAAM,QAAQ,GAAG,YAAY,CAAC,IAAI,EAAE,MAAM,EAAE,SAAS,CAAC,CAAA;IACtD,MAAM,OAAO,GAAmF,EAAE,CAAA;IAElG,KAAK,MAAM,KAAK,IAAI,QAAQ,EAAE,CAAC;QAC3B,MAAM,EAAE,IAAI,EAAE,OAAO,EAAE,GAAG,KAAK,CAAA;QAC/B,IAAI,CAAC;YACD,IAAI,IAAI,CAAC,IAAI,KAAK,SAAS,EAAE,CAAC;gBAC1B,MAAM,OAAO,GAAG,MAAM,YAAY,CAAC;oBAC/B,GAAG,OAAO;oBACV,IAAI,EAAE,IAAI,CAAC,IAAI,IAAI,WAAW;oBAC9B,IAAI,EAAE,cAAc,CAAC,IAAI,EAAE,CAAC,CAAC,KAAK,CAAC,GAAG,CAAC;oBACvC,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,GAAG,EAAE,KAAK,CAAC,GAAG,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;oBACxC,GAAG,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC,CAAC,EAAE,QAAQ,EAAE,IAAI,CAAC,QAAQ,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;iBACxD,CAAC,CAAA;gBACF,OAAO,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,EAAE,EAAE,EAAE,IAAI,CAAC,EAAE,EAAE,IAAI,EAAE,IAAI,CAAC,IAAI,EAAE,IAAI,EAAE,OAAO,CAAC,IAAI,EAAE,GAAG,EAAE,OAAO,CAAC,GAAG,EAAE,EAAE,KAAK,EAAE,OAAO,CAAC,KAAK,EAAE,CAAC,CAAA;gBACpH,IAAI,IAAI,CAAC,IAAI,IAAI,IAAI,CAAC,IAAI,KAAK,WAAW,IAAI,IAAI,CAAC,IAAI,KAAK,WAAW;oBACnE,SAAS,CAAC,OAAO,EAAE,CAAC,SAAS,IAAI,CAAC,EAAE,0BAA0B,IAAI,CAAC,IAAI,yDAAyD,CAAC,CAAA;YACzI,CAAC;iBAAM,IAAI,IAAI,CAAC,IAAI,KAAK,MAAM,EAAE,CAAC;gBAC9B,MAAM,IAAI,GAAG,MAAM,SAAS,CAAC;oBACzB,GAAG,OAAO;oBACV,OAAO,EAAE,OAAO,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,IAAI,CAAC,OAAO,CAAC;oBAC7C,YAAY,EAAE,IAAI,CAAC,YAAY;oBAC/B,GAAG,CAAC,KAAK,CAAC,aAAa,CAAC,CAAC,CAAC,EAAE,aAAa,EAAE,KAAK,CAAC,aAAa,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;oBACtE,GAAG,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC,CAAC,EAAE,QAAQ,EAAE,KAAK,CAAC,QAAQ,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;iBAC1D,CAAC,CAAA;gBACF,OAAO,CAAC,IAAI,CAAC;oBACT,IAAI,EAAE,EAAE,EAAE,EAAE,IAAI,CAAC,EAAE,EAAE,IAAI,EAAE,IAAI,CAAC,IAAI,EAAE,IAAI,EAAE,OAAO,CAAC,IAAI,EAAE;oBAC1D,KAAK,EAAE,IAAI,CAAC,KAAK;oBACjB,GAAG,CAAC,IAAI,CAAC,MAAM;wBACX,CAAC,CAAC;4BACI,YAAY,EAAE,GAAG,EAAE;gCACf,MAAM,IAAI,GAAG,OAAO,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,IAAI,CAAC,MAAO,CAAC,CAAA;gCACjD,IAAI,IAAI,CAAA;gCACR,IAAI,CAAC;oCACD,IAAI,GAAG,YAAY,CAAC,IAAI,CAAC,CAAA;gCAC7B,CAAC;gCAAC,OAAO,CAAC,EAAE,CAAC;oCACT,SAAS,CAAC,OAAO,EAAE,CAAC,SAAS,IAAI,CAAC,EAAE,wBAAwB,CAAC,YAAY,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAA;oCACzG,OAAM;gCACV,CAAC;gCACD,IAAI,CAAC,WAAW,CAAC,IAAI,CAAC,CAAA;gCACtB,KAAK,MAAM,IAAI,IAAI,UAAU,CAAC,IAAI,CAAC;oCAAE,SAAS,CAAC,OAAO,EAAE,CAAC,SAAS,IAAI,CAAC,EAAE,MAAM,IAAI,EAAE,CAAC,CAAA;4BAC1F,CAAC;yBACJ;wBACH,CAAC,CAAC,EAAE,CAAC;iBACZ,CAAC,CAAA;gBACF,oFAAoF;gBACpF,gEAAgE;gBAChE,KAAK,MAAM,IAAI,IAAI,UAAU,CAAC,KAAK,CAAC,QAAQ,CAAC;oBAAE,SAAS,CAAC,OAAO,EAAE,CAAC,SAAS,IAAI,CAAC,EAAE,MAAM,IAAI,EAAE,CAAC,CAAA;gBAChG,IAAI,OAAO,CAAC,MAAM,IAAI,CAAC,OAAO,CAAC,IAAI;oBAC/B,SAAS,CAAC,OAAO,EAAE,CAAC,SAAS,IAAI,CAAC,EAAE,8FAA8F,CAAC,CAAA;YAC3I,CAAC;iBAAM,CAAC;gBACJ,MAAM,IAAI,GAAG,MAAM,SAAS,CAAC;oBACzB,GAAG,OAAO;oBACV,MAAM,EAAE,KAAK,CAAC,MAAO;oBACrB,GAAG,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,MAAM,EAAE,KAAK,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;oBACjD,GAAG,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC,CAAC,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;iBACjD,CAAC,CAAA;gBACF,OAAO,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,EAAE,EAAE,EAAE,IAAI,CAAC,EAAE,EAAE,IAAI,EAAE,IAAI,CAAC,IAAI,EAAE,IAAI,EAAE,OAAO,CAAC,IAAI,EAAE,UAAU,EAAE,IAAI,CAAC,UAAU,EAAE,EAAE,KAAK,EAAE,IAAI,CAAC,KAAK,EAAE,CAAC,CAAA;gBAC5H,SAAS,CAAC,OAAO,EAAE,CAAC,SAAS,IAAI,CAAC,EAAE,kCAAkC,CAAC,CAAA;gBACvE,IAAI,IAAI,CAAC,SAAS;oBAAE,SAAS,CAAC,OAAO,EAAE,CAAC,SAAS,IAAI,CAAC,EAAE,+CAA+C,CAAC,CAAA;YAC5G,CAAC;YACD,SAAS,CAAC,OAAO,EAAE,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC,CAAC,CAAE,CAAC,IAAI,CAAC,CAAA;QAC7C,CAAC;QAAC,OAAO,CAAC,EAAE,CAAC;YACT,IAAI,CAAC;gBACD,MAAM,YAAY,CAAC,OAAO,CAAC,CAAA;YAC/B,CAAC;YAAC,OAAO,QAAQ,EAAE,CAAC;gBAChB,MAAM,IAAI,cAAc,CAAC,CAAC,CAAC,EAAE,QAAQ,CAAC,EAAE,SAAS,IAAI,CAAC,EAAE,4CAA4C,EAAE,EAAE,KAAK,EAAE,QAAQ,EAAE,CAAC,CAAA;YAC9H,CAAC;YACD,MAAM,IAAI,KAAK,CAAC,SAAS,IAAI,CAAC,EAAE,sBAAsB,CAAC,YAAY,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,EAAE,KAAK,EAAE,CAAC,EAAE,CAAC,CAAA;QACrH,CAAC;IACL,CAAC;IAED,IAAI,MAAM,GAAG,KAAK,CAAA;IAClB,OAAO;QACH,IAAI;QACJ,KAAK,EAAE,OAAO,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,EAAE,EAAE,EAAE,CAAC,IAAI,CAAC;QACtC,KAAK,EAAE,KAAK,IAAI,EAAE;YACd,IAAI,MAAM;gBAAE,OAAM;YAClB,MAAM,GAAG,IAAI,CAAA;YACb,MAAM,YAAY,CAAC,OAAO,CAAC,CAAA;QAC/B,CAAC;QACD,YAAY,EAAE,GAAG,EAAE;YACf,KAAK,MAAM,IAAI,IAAI,OAAO;gBAAE,IAAI,CAAC,YAAY,EAAE,EAAE,CAAA;QACrD,CAAC;KACJ,CAAA;AACL,CAAC,CAAA;AAED,6FAA6F;AAC7F,MAAM,WAAW,GAAG,GAAG,EAAE,CAAC,WAAW,CAAC,EAAE,CAAC,CAAC,QAAQ,CAAC,WAAW,CAAC,CAAA;AAE/D;;;;;;;;;;;;GAYG;AACH,MAAM,CAAC,MAAM,gBAAgB,GAAG,CAAC,OAAO,GAA2D,EAAE,EAAE,EAAE;IACrG,MAAM,UAAU,GAAG,OAAO,CAAC,UAAU,IAAI,YAAY,CAAA;IACrD,MAAM,KAAK,GAAG,CAAC,cAAc,EAAE,WAAW,EAAE,gBAAgB,CAAU,CAAA;IACtE,MAAM,OAAO,GAAG,MAAM,CAAC,WAAW,CAAC,CAAC,GAAG,KAAK,EAAE,UAAU,CAAC,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC,IAAI,EAAE,WAAW,EAAE,CAAC,CAAC,CAAC,CAAA;IAC/F,+FAA+F;IAC/F,kGAAkG;IAClG,4EAA4E;IAC5E,MAAM,KAAK,GAAG,CAAC,IAAY,EAAE,EAAE,CAAC,MAAM,CAAC,WAAW,CAAC,CAAC,GAAG,KAAK,CAAC,MAAM,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,KAAK,IAAI,CAAC,EAAE,UAAU,CAAC,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC,IAAI,EAAE,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAA;IAE/I,OAAO;QACH,OAAO,EAAE,CAAU;QACnB,OAAO,EAAE;YACL,GAAG,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,GAAG,EAAE,OAAO,CAAC,GAAG,EAAE,CAAC,CAAC,CAAC,EAAE,MAAM,EAAE,OAAO,CAAC,MAAM,IAAI,uBAAuB,EAAE,CAAC;YAC/F,OAAO,EAAE,KAAK;SACjB;QACD,KAAK,EAAE;YACH;gBACI,EAAE,EAAE,SAAS;gBACb,IAAI,EAAE,SAAkB;gBACxB,IAAI,EAAE,EAAE,IAAI,EAAE,cAAc,EAAE,MAAM,EAAE,OAAO,CAAC,cAAc,CAAC,EAAE,KAAK,EAAE,KAAK,CAAC,cAAc,CAAC,EAAE;gBAC7F,IAAI,EAAE,WAAW;gBACjB,IAAI,EAAE,cAAc;aACvB;YACD;gBACI,EAAE,EAAE,MAAM;gBACV,IAAI,EAAE,MAAe;gBACrB,IAAI,EAAE,EAAE,IAAI,EAAE,WAAW,EAAE,MAAM,EAAE,OAAO,CAAC,WAAW,CAAC,EAAE,KAAK,EAAE,KAAK,CAAC,WAAW,CAAC,EAAE;gBACpF,OAAO,EAAE,SAAS;gBAClB,YAAY,EAAE,CAAC,UAAU,CAAC;aAC7B;YACD;gBACI,EAAE,EAAE,WAAW;gBACf,IAAI,EAAE,OAAgB;gBACtB,IAAI,EAAE,EAAE,IAAI,EAAE,gBAAgB,EAAE,MAAM,EAAE,OAAO,CAAC,gBAAgB,CAAC,EAAE,KAAK,EAAE,KAAK,CAAC,gBAAgB,CAAC,EAAE;gBACnG,QAAQ,EAAE,2BAA2B;aACxC;SACJ;KACJ,CAAA;AACL,CAAC,CAAA;AAED,sGAAsG;AACtG,MAAM,CAAC,MAAM,qBAAqB,GAAG,CAAC,IAAY,EAAE,UAAU,GAAG,YAAY,EAAE,EAAE,CAAC;IAC9E,GAAG,IAAI,0EAA0E;IACjF,sEAAsE;IACtE,sHAAsH;IACtH,IAAI,UAAU,iGAAiG;IAC/G,+FAA+F;CAClG,CAAA"}
package/dist/web/app.css CHANGED
@@ -1 +1 @@
1
- :root{--bg: #0e1116;--panel: #151a21;--panel-2: #1b212a;--line: #262e3a;--text: #d6dde7;--muted: #7d8899;--accent: #4ea1ff;--accent-dim: #1d3a5c;--ok: #37d18a;--bad: #ff6b6b;--warn: #f0b429;--mono: ui-monospace, "SF Mono", "JetBrains Mono", Menlo, Consolas, monospace;color-scheme:dark}*{box-sizing:border-box}body{margin:0;background:var(--bg);color:var(--text);font:14px/1.5 system-ui,-apple-system,Segoe UI,Roboto,sans-serif;-webkit-font-smoothing:antialiased}button{font:inherit;color:inherit;background:none;border:none;cursor:pointer}.mono,code,pre,time{font-family:var(--mono)}.muted{color:var(--muted);font-size:12px}.app{display:grid;grid-template-columns:240px minmax(0,1fr) 340px;height:100vh}aside{background:var(--panel);overflow-y:auto;padding:14px}.stream{flex:1 1 auto;min-height:0;overflow-y:auto;padding:14px}aside{border-right:1px solid var(--line)}.side{display:flex;flex-direction:column;min-height:0;border-left:1px solid var(--line);background:var(--panel)}.chat{display:flex;flex-direction:column;min-height:0;flex:1 1 auto;padding:14px;border-bottom:1px solid var(--line)}.chat-log{flex:1;min-height:0;overflow-y:auto;margin-bottom:10px}.said{margin-bottom:6px;font-size:13px}.said .text{display:inline-block;padding:5px 9px;border-radius:8px;background:var(--panel-2);word-break:break-word}.said.mine{text-align:right}.said.mine .text{background:var(--accent-dim);color:#eaf3ff;text-align:left}.chat-send{display:flex;gap:6px}.chat-send .control{flex:1}.bad{color:var(--bad)}main{overflow-y:auto;padding:22px 26px 60px}aside header,.stream header{display:flex;align-items:baseline;justify-content:space-between;gap:8px;margin-bottom:12px}h1{margin:0;font-size:12px;font-weight:600;letter-spacing:.09em;text-transform:uppercase;color:var(--muted)}.peer-head{display:flex;align-items:baseline;gap:12px;padding-bottom:14px;margin-bottom:18px;border-bottom:1px solid var(--line)}.peer-head h1{font-size:20px;letter-spacing:0;text-transform:none;color:var(--text);font-family:var(--mono)}.identity{display:flex;flex-direction:column;gap:1px;padding:8px 10px 9px;margin-bottom:14px;border:1px solid var(--line);border-left:3px solid var(--accent);border-radius:6px;background:var(--panel-2)}.identity .name{font-size:13px;color:var(--text);word-break:break-word}.you{margin-left:auto;padding:0 6px;border-radius:999px;background:var(--line);font-size:10px;letter-spacing:.06em;text-transform:uppercase;color:var(--muted)}.status{font-size:11px;font-family:var(--mono)}.status.ok{color:var(--ok)}.status.warn{color:var(--warn)}.peer{display:flex;align-items:center;gap:8px;width:100%;padding:7px 10px;margin-bottom:3px;border-radius:6px;font-family:var(--mono);font-size:13px;text-align:left;transition:background .12s}.peer:hover{background:var(--panel-2)}.peer.selected{background:var(--accent-dim);color:#fff}.dot{width:7px;height:7px;border-radius:50%;background:var(--ok);flex:none;box-shadow:0 0 6px #37d18a99}.dot.off{background:var(--muted);box-shadow:none}.namespace{margin-bottom:28px}.namespace h2{display:flex;align-items:baseline;gap:10px;margin:0 0 10px;font-size:15px;font-weight:600;font-family:var(--mono)}.badge{padding:1px 6px;border:1px solid var(--line);border-radius:999px;font-size:11px;font-family:var(--mono);color:var(--muted)}.method{border:1px solid var(--line);border-radius:8px;margin-bottom:6px;background:var(--panel);overflow:hidden}.method.open{border-color:var(--accent-dim);background:var(--panel-2)}.method-head{display:flex;align-items:baseline;gap:8px;width:100%;padding:9px 12px;text-align:left}.method-head:hover{background:#4ea1ff0f}.method-head code{font-size:13px;word-break:break-word}.chevron{color:var(--muted);font-size:11px;flex:none}.method-body{padding:4px 14px 14px;border-top:1px solid var(--line)}.field{padding:10px 0;border-bottom:1px dashed rgba(38,46,58,.7)}.field:last-of-type{border-bottom:none}.field.off .control,.field.off .checkbox{opacity:.35}.field-label{display:flex;align-items:center;gap:8px;margin-bottom:5px}.field-label .name{font-family:var(--mono);font-size:13px}.field-label .type{font-family:var(--mono);font-size:11px;color:var(--muted)}.include{accent-color:var(--accent)}.control{width:100%;padding:7px 10px;background:#0b0f14;border:1px solid var(--line);border-radius:6px;color:var(--text);font:inherit;font-size:13px}textarea.control{resize:vertical}.control:focus{outline:none;border-color:var(--accent);box-shadow:0 0 0 2px #4ea1ff2e}.checkbox{display:inline-flex;align-items:center;gap:8px;font-family:var(--mono);font-size:13px;cursor:pointer}.checkbox input{accent-color:var(--accent);width:16px;height:16px}.actions{margin-top:12px}.primary{padding:7px 18px;border-radius:6px;background:var(--accent);color:#06121f;font-weight:600;font-size:13px}.primary:hover:not(:disabled){background:#6fb4ff}.primary:disabled{opacity:.5;cursor:default}.result{margin:12px 0 0;padding:10px 12px;border-radius:6px;background:#0b0f14;border-left:3px solid var(--ok);font-size:12.5px;white-space:pre-wrap;word-break:break-word;max-height:320px;overflow:auto}.result.bad{border-left-color:var(--bad);color:#ffb1b1}.events{margin-top:10px;padding:10px 12px;border:1px solid var(--line);border-radius:8px}.events h3{margin:0 0 6px;font-size:11px;font-weight:600;letter-spacing:.09em;text-transform:uppercase;color:var(--muted)}.event-row{display:flex;align-items:center;gap:10px;padding:4px 0;font-size:13px}.event-row code{flex:1;min-width:0;word-break:break-word}.toggle{padding:3px 10px;border:1px solid var(--line);border-radius:999px;font-size:11px;font-family:var(--mono);color:var(--muted)}.toggle:hover{border-color:var(--accent);color:var(--text)}.toggle.on{background:var(--accent-dim);border-color:var(--accent);color:#cfe6ff}.streamed{padding:7px 0;border-bottom:1px solid rgba(38,46,58,.6);font-size:12px}.streamed time{color:var(--muted);font-size:11px;margin-right:8px;white-space:nowrap}.streamed code{color:var(--accent);font-size:12px}.streamed pre{margin:3px 0 0;color:var(--text);font-size:12px;white-space:pre-wrap;word-break:break-word}.notice{padding:14px 16px;border:1px solid var(--line);border-left:3px solid var(--warn);border-radius:6px;background:var(--panel)}.notice p{margin:6px 0 0}@media(max-width:1100px){.app{grid-template-columns:200px minmax(0,1fr);grid-template-rows:minmax(0,1fr) 220px}.stream{grid-column:1 / -1;border-left:none;border-top:1px solid var(--line)}}.tabs{display:flex;flex:0 0 auto;border-bottom:1px solid var(--line);background:var(--panel-2)}.tab{flex:1 1 0;padding:9px 6px;font-size:11px;font-family:var(--mono);letter-spacing:.06em;text-transform:uppercase;color:var(--muted);border-bottom:2px solid transparent}.tab:hover{color:var(--text)}.tab.on{color:#cfe6ff;border-bottom-color:var(--accent)}.tab .count{margin-left:6px;padding:1px 6px;border-radius:999px;background:var(--accent-dim);color:#cfe6ff;font-size:10px}.traffic{flex:1 1 auto;min-height:0;overflow-y:auto;padding:14px}.traffic header{display:flex;align-items:baseline;justify-content:space-between;gap:8px;margin-bottom:12px}.traffic-actions{display:flex;gap:6px}.tap-setup{display:flex;flex-direction:column;gap:9px;padding-bottom:10px}.kinds{display:flex;flex-wrap:wrap;align-items:center;gap:6px}.tap-where{margin:0 0 10px;font-size:11px}.traffic .control{margin-bottom:10px}.frame{padding:6px 0 6px 8px;border-left:2px solid var(--line);border-bottom:1px solid rgba(38,46,58,.6);font-size:12px}.frame.post{border-left-color:var(--accent)}.frame.success{border-left-color:var(--ok)}.frame.error{border-left-color:var(--bad)}.frame.event{border-left-color:var(--warn)}.frame-head{display:flex;flex-wrap:wrap;align-items:baseline;gap:6px}.frame-head time{color:var(--muted);font-size:11px;white-space:nowrap}.frame .arrow{color:var(--muted)}.frame .peers{color:var(--muted);font-size:11px}.frame code{color:var(--accent);font-size:12px}.frame .ms{font-size:11px}.frame .code{color:var(--bad);font-family:var(--mono);font-size:11px}.frame-error{margin:3px 0 0;color:var(--bad);font-size:11px}.frame-payload{margin:3px 0 0;color:var(--text);font-size:11px;font-family:var(--mono);cursor:pointer;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.frame-payload.open{white-space:pre-wrap;word-break:break-word}.traffic.hidden{display:none}.problems{flex:1 1 auto;min-height:0;overflow-y:auto;padding:14px}.problems header{display:flex;align-items:baseline;justify-content:space-between;gap:8px;margin-bottom:12px}.problem{padding:7px 0 7px 8px;border-left:2px solid var(--line);border-bottom:1px solid rgba(38,46,58,.6);font-size:12px}.problem.rejected{border-left-color:var(--bad)}.problem.unroutable{border-left-color:var(--warn)}.problem.peerDisplaced{border-left-color:var(--accent)}.problem.transportError{border-left-color:var(--bad)}.problem-head{display:flex;flex-wrap:wrap;align-items:baseline;gap:6px}.problem-head time{color:var(--muted);font-size:11px;white-space:nowrap}.problem .kind{font-family:var(--mono);font-size:11px;color:var(--text)}.problem .link{font-size:11px}.problem-body code{color:var(--accent);font-size:11px}.problem-body p{margin:2px 0 0;color:var(--muted);font-size:11px}.tab .count.bad{background:#ff6b6b2e;color:#ffb3b3}.peer .count{margin-left:6px;padding:1px 6px;border-radius:999px;background:var(--accent-dim);color:#cfe6ff;font-size:10px}.peer .link{margin-left:auto;padding-left:8px;color:var(--muted);font-size:10px;font-family:var(--mono);overflow:hidden;text-overflow:ellipsis;white-space:nowrap;max-width:45%}.actions{flex-wrap:wrap}.actions .timing{font-size:11px;font-family:var(--mono);margin-left:auto}.events h3{display:flex;align-items:baseline;gap:8px}.stream .control{margin-bottom:10px}.presence{flex:1 1 auto;min-height:0;overflow-y:auto;padding:14px}.presence header{display:flex;align-items:baseline;justify-content:space-between;gap:8px;margin-bottom:12px}.flapping{margin-bottom:12px;padding:8px 10px;border:1px solid var(--line);border-left:3px solid var(--warn);border-radius:6px}.flapping p{margin:2px 0;font-size:12px;color:var(--text)}.coming{display:flex;align-items:baseline;gap:8px;padding:5px 0;border-bottom:1px solid rgba(38,46,58,.6);font-size:12px}.coming time{color:var(--muted);font-size:11px;white-space:nowrap}.coming .mark{font-family:var(--mono);width:10px}.coming.online .mark{color:var(--ok)}.coming.offline .mark{color:var(--bad)}.coming .link{margin-left:auto;font-size:10px;font-family:var(--mono)}.peer .role{margin-left:8px;padding:1px 6px;border-radius:999px;font-size:10px;font-family:var(--mono);background:var(--panel-2);color:var(--muted)}.peer .role.broker{color:#cfe6ff;background:var(--accent-dim)}.peer .role.undescribed{color:var(--warn)}.presets{display:flex;flex-wrap:wrap;align-items:center;gap:6px;margin:10px 0 4px}.preset{display:inline-flex;align-items:center}.preset .toggle{border-top-right-radius:0;border-bottom-right-radius:0;max-width:260px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.forget{padding:3px 7px;border:1px solid var(--line);border-left:none;border-radius:0 999px 999px 0;font-size:11px;color:var(--muted)}.forget:hover{color:var(--bad);border-color:var(--bad)}.component{border:1px solid var(--line);border-radius:8px;margin-bottom:6px;padding:9px 12px;background:var(--panel)}.component-head{display:flex;align-items:baseline;gap:10px;font-size:12px;flex-wrap:wrap}.component-head .toggle{margin-left:auto}.comp-label{font-family:var(--mono);font-size:11px;letter-spacing:.08em;text-transform:uppercase;color:var(--muted)}.status-badge{padding:1px 8px;border-radius:999px;font-size:11px;font-family:var(--mono);border:1px solid var(--line);color:var(--muted)}.status-badge.live{border-color:var(--ok);color:var(--ok)}.status-badge.stale{border-color:var(--warn);color:var(--warn)}.status-badge.closed{border-color:var(--bad);color:var(--bad)}.component-body{display:flex;gap:24px;flex-wrap:wrap;margin-top:8px}.component.stale .component-body{opacity:.55}.value-table{min-width:200px;flex:1}.value-table h4{margin:0 0 4px;font-size:11px;letter-spacing:.08em;text-transform:uppercase;color:var(--muted);font-weight:600}.value-row{display:flex;align-items:baseline;gap:10px;padding:3px 0;border-bottom:1px solid rgba(38,46,58,.6);font-size:13px}.value-name{color:var(--muted);min-width:110px}.value{word-break:break-word}.value .unit{color:var(--muted);font-size:12px}.quality{padding:0 7px;border-radius:999px;font-size:11px;font-family:var(--mono);border:1px solid var(--line);color:var(--muted)}.quality.q-good{border-color:var(--ok);color:var(--ok)}.quality.q-stale,.quality.q-uncertain{border-color:var(--warn);color:var(--warn)}.quality.q-bad{border-color:var(--bad);color:var(--bad)}.quality.forced{border-color:#c792ea;color:#c792ea}.component-error{color:var(--bad);font-size:12px;margin:6px 0 0}.sem{flex:none;padding:1px 7px;border-radius:999px;font-size:10px;font-family:var(--mono);border:1px solid var(--line);color:var(--muted)}.sem.idempotent{border-color:var(--accent-dim);color:var(--accent)}.sem.once{border-color:var(--warn);color:var(--warn)}.confirm{display:flex;align-items:center;gap:10px;padding:8px 10px;margin:8px 0;border:1px solid var(--warn);border-radius:6px;background:#f0b42914;font-size:12px}.confirm span{flex:1}.structure{border:1px solid var(--line);border-radius:8px;margin-bottom:6px;padding:9px 12px;background:var(--panel)}.structure-head{display:flex;align-items:baseline;gap:10px;flex-wrap:wrap;font-size:12px}.node-label{color:var(--muted);font-size:12px}.tree{list-style:none;margin:8px 0 0;padding:0;font-size:13px}.tree ul{list-style:none;margin:0;padding-left:18px;border-left:1px solid var(--line)}.tree li{padding:2px 0}.node{display:inline-flex;align-items:baseline;gap:8px}.owner-chip{padding:0 7px;border-radius:999px;font-size:11px;font-family:var(--mono);border:1px solid var(--line);color:var(--muted)}.owner-chip.link{cursor:pointer;border-color:var(--accent-dim);color:var(--accent)}.owner-chip.link:hover{border-color:var(--accent)}.capability{padding:1px 8px;border-radius:999px;font-size:11px;font-family:var(--mono);border:1px solid var(--accent-dim);color:var(--accent);font-weight:400}.peer-lines{display:inline-flex;flex-direction:column;align-items:flex-start;gap:1px;min-width:0}.peer-place{font-size:10px;color:var(--muted);font-family:var(--mono);white-space:nowrap;overflow:hidden;text-overflow:ellipsis;max-width:180px}
1
+ :root{--bg: #0e1116;--panel: #151a21;--panel-2: #1b212a;--line: #262e3a;--text: #d6dde7;--muted: #7d8899;--accent: #4ea1ff;--accent-dim: #1d3a5c;--ok: #37d18a;--bad: #ff6b6b;--warn: #f0b429;--mono: ui-monospace, "SF Mono", "JetBrains Mono", Menlo, Consolas, monospace;color-scheme:dark}*{box-sizing:border-box}body{margin:0;background:var(--bg);color:var(--text);font:14px/1.5 system-ui,-apple-system,Segoe UI,Roboto,sans-serif;-webkit-font-smoothing:antialiased}button{font:inherit;color:inherit;background:none;border:none;cursor:pointer}.mono,code,pre,time{font-family:var(--mono)}.muted{color:var(--muted);font-size:12px}.app{display:grid;grid-template-columns:240px minmax(0,1fr) 340px;height:100vh}aside{background:var(--panel);overflow-y:auto;padding:14px}.stream{flex:1 1 auto;min-height:0;overflow-y:auto;padding:14px}aside{border-right:1px solid var(--line)}.side{display:flex;flex-direction:column;min-height:0;border-left:1px solid var(--line);background:var(--panel)}.chat{display:flex;flex-direction:column;min-height:0;flex:1 1 auto;padding:14px;border-bottom:1px solid var(--line)}.chat-log{flex:1;min-height:0;overflow-y:auto;margin-bottom:10px}.said{margin-bottom:6px;font-size:13px}.said .text{display:inline-block;padding:5px 9px;border-radius:8px;background:var(--panel-2);word-break:break-word}.said.mine{text-align:right}.said.mine .text{background:var(--accent-dim);color:#eaf3ff;text-align:left}.chat-send{display:flex;gap:6px}.chat-send .control{flex:1}.bad{color:var(--bad)}main{overflow-y:auto;padding:22px 26px 60px}aside header,.stream header{display:flex;align-items:baseline;justify-content:space-between;gap:8px;margin-bottom:12px}h1{margin:0;font-size:12px;font-weight:600;letter-spacing:.09em;text-transform:uppercase;color:var(--muted)}.peer-head{display:flex;align-items:baseline;gap:12px;padding-bottom:14px;margin-bottom:18px;border-bottom:1px solid var(--line)}.peer-head h1{display:flex;flex-wrap:wrap;align-items:baseline;gap:8px;margin:0;font-size:20px;letter-spacing:0;text-transform:none;color:var(--text)}.entity-title{display:inline-flex;flex-wrap:wrap;align-items:baseline;gap:7px;min-width:0}.entity-title.compact{flex-direction:column;align-items:flex-start;gap:0}.entity-id{font-size:11px;font-weight:400;color:var(--muted)}.small-id{color:var(--muted);font-size:10px;font-weight:400}.identity{display:flex;flex-direction:column;gap:1px;padding:8px 10px 9px;margin-bottom:14px;border:1px solid var(--line);border-left:3px solid var(--accent);border-radius:6px;background:var(--panel-2)}.identity .name{font-size:13px;font-weight:600;color:var(--text);word-break:break-word}.you{margin-left:auto;padding:0 6px;border-radius:999px;background:var(--line);font-size:10px;letter-spacing:.06em;text-transform:uppercase;color:var(--muted)}.status{font-size:11px;font-family:var(--mono)}.status.ok{color:var(--ok)}.status.warn{color:var(--warn)}.peer{display:flex;align-items:center;gap:8px;width:100%;padding:7px 10px;margin-bottom:3px;border-radius:6px;font-size:13px;text-align:left;transition:background .12s}.peer:hover{background:var(--panel-2)}.peer.selected{background:var(--accent-dim);color:#fff}.dot{width:7px;height:7px;border-radius:50%;background:var(--ok);flex:none;box-shadow:0 0 6px #37d18a99}.dot.off{background:var(--muted);box-shadow:none}.namespace{margin-bottom:28px}.namespace h2{display:flex;align-items:baseline;gap:10px;margin:0 0 10px;font-size:15px;font-weight:600;font-family:var(--mono)}.badge{padding:1px 6px;border:1px solid var(--line);border-radius:999px;font-size:11px;font-family:var(--mono);color:var(--muted)}.method{border:1px solid var(--line);border-radius:8px;margin-bottom:6px;background:var(--panel);overflow:hidden}.method.open{border-color:var(--accent-dim);background:var(--panel-2)}.method-head{display:flex;align-items:baseline;gap:8px;width:100%;padding:9px 12px;text-align:left}.method-head:hover{background:#4ea1ff0f}.method-head code{font-size:13px;word-break:break-word}.chevron{color:var(--muted);font-size:11px;flex:none}.method-body{padding:4px 14px 14px;border-top:1px solid var(--line)}.field{padding:10px 0;border-bottom:1px dashed rgba(38,46,58,.7)}.field:last-of-type{border-bottom:none}.field.off .control,.field.off .checkbox{opacity:.35}.field-label{display:flex;align-items:center;gap:8px;margin-bottom:5px}.field-label .name{font-family:var(--mono);font-size:13px}.field-label .type{font-family:var(--mono);font-size:11px;color:var(--muted)}.include{accent-color:var(--accent)}.control{width:100%;padding:7px 10px;background:#0b0f14;border:1px solid var(--line);border-radius:6px;color:var(--text);font:inherit;font-size:13px}textarea.control{resize:vertical}.control:focus{outline:none;border-color:var(--accent);box-shadow:0 0 0 2px #4ea1ff2e}.checkbox{display:inline-flex;align-items:center;gap:8px;font-family:var(--mono);font-size:13px;cursor:pointer}.checkbox input{accent-color:var(--accent);width:16px;height:16px}.actions{margin-top:12px}.primary{padding:7px 18px;border-radius:6px;background:var(--accent);color:#06121f;font-weight:600;font-size:13px}.primary:hover:not(:disabled){background:#6fb4ff}.primary:disabled{opacity:.5;cursor:default}.result{margin:12px 0 0;padding:10px 12px;border-radius:6px;background:#0b0f14;border-left:3px solid var(--ok);font-size:12.5px;white-space:pre-wrap;word-break:break-word;max-height:320px;overflow:auto}.result.bad{border-left-color:var(--bad);color:#ffb1b1}.events{margin-top:10px;padding:10px 12px;border:1px solid var(--line);border-radius:8px}.events h3{margin:0 0 6px;font-size:11px;font-weight:600;letter-spacing:.09em;text-transform:uppercase;color:var(--muted)}.event-row{display:flex;align-items:center;gap:10px;padding:4px 0;font-size:13px}.event-row code{flex:1;min-width:0;word-break:break-word}.toggle{padding:3px 10px;border:1px solid var(--line);border-radius:999px;font-size:11px;font-family:var(--mono);color:var(--muted)}.toggle:hover{border-color:var(--accent);color:var(--text)}.toggle.on{background:var(--accent-dim);border-color:var(--accent);color:#cfe6ff}.streamed{padding:7px 0;border-bottom:1px solid rgba(38,46,58,.6);font-size:12px}.streamed time{color:var(--muted);font-size:11px;margin-right:8px;white-space:nowrap}.streamed code{color:var(--accent);font-size:12px}.streamed pre{margin:3px 0 0;color:var(--text);font-size:12px;white-space:pre-wrap;word-break:break-word}.notice{padding:14px 16px;border:1px solid var(--line);border-left:3px solid var(--warn);border-radius:6px;background:var(--panel)}.notice p{margin:6px 0 0}@media(max-width:1100px){.app{grid-template-columns:200px minmax(0,1fr);grid-template-rows:minmax(0,1fr) 220px}.stream{grid-column:1 / -1;border-left:none;border-top:1px solid var(--line)}}.tabs{display:flex;flex:0 0 auto;border-bottom:1px solid var(--line);background:var(--panel-2)}.tab{flex:1 1 0;padding:9px 6px;font-size:11px;font-family:var(--mono);letter-spacing:.06em;text-transform:uppercase;color:var(--muted);border-bottom:2px solid transparent}.tab:hover{color:var(--text)}.tab.on{color:#cfe6ff;border-bottom-color:var(--accent)}.tab .count{margin-left:6px;padding:1px 6px;border-radius:999px;background:var(--accent-dim);color:#cfe6ff;font-size:10px}.traffic{flex:1 1 auto;min-height:0;overflow-y:auto;padding:14px}.traffic header{display:flex;align-items:baseline;justify-content:space-between;gap:8px;margin-bottom:12px}.traffic-actions{display:flex;gap:6px}.tap-setup{display:flex;flex-direction:column;gap:9px;padding-bottom:10px}.kinds{display:flex;flex-wrap:wrap;align-items:center;gap:6px}.tap-where{margin:0 0 10px;font-size:11px}.traffic .control{margin-bottom:10px}.frame{padding:6px 0 6px 8px;border-left:2px solid var(--line);border-bottom:1px solid rgba(38,46,58,.6);font-size:12px}.frame.post{border-left-color:var(--accent)}.frame.success{border-left-color:var(--ok)}.frame.error{border-left-color:var(--bad)}.frame.event{border-left-color:var(--warn)}.frame-head{display:flex;flex-wrap:wrap;align-items:baseline;gap:6px}.frame-head time{color:var(--muted);font-size:11px;white-space:nowrap}.frame .arrow{color:var(--muted)}.frame .peers{color:var(--muted);font-size:11px}.frame code{color:var(--accent);font-size:12px}.frame .ms{font-size:11px}.frame .code{color:var(--bad);font-family:var(--mono);font-size:11px}.frame-error{margin:3px 0 0;color:var(--bad);font-size:11px}.frame-payload{margin:3px 0 0;color:var(--text);font-size:11px;font-family:var(--mono);cursor:pointer;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.frame-payload.open{white-space:pre-wrap;word-break:break-word}.traffic.hidden{display:none}.problems{flex:1 1 auto;min-height:0;overflow-y:auto;padding:14px}.problems header{display:flex;align-items:baseline;justify-content:space-between;gap:8px;margin-bottom:12px}.problem{padding:7px 0 7px 8px;border-left:2px solid var(--line);border-bottom:1px solid rgba(38,46,58,.6);font-size:12px}.problem.rejected{border-left-color:var(--bad)}.problem.unroutable{border-left-color:var(--warn)}.problem.peerDisplaced{border-left-color:var(--accent)}.problem.transportError{border-left-color:var(--bad)}.problem-head{display:flex;flex-wrap:wrap;align-items:baseline;gap:6px}.problem-head time{color:var(--muted);font-size:11px;white-space:nowrap}.problem .kind{font-family:var(--mono);font-size:11px;color:var(--text)}.problem .link{font-size:11px}.problem-body code{color:var(--accent);font-size:11px}.problem-body p{margin:2px 0 0;color:var(--muted);font-size:11px}.tab .count.bad{background:#ff6b6b2e;color:#ffb3b3}.peer .count{margin-left:6px;padding:1px 6px;border-radius:999px;background:var(--accent-dim);color:#cfe6ff;font-size:10px}.peer .link{margin-left:auto;padding-left:8px;color:var(--muted);font-size:10px;font-family:var(--mono);overflow:hidden;text-overflow:ellipsis;white-space:nowrap;max-width:45%}.actions{flex-wrap:wrap}.actions .timing{font-size:11px;font-family:var(--mono);margin-left:auto}.events h3{display:flex;align-items:baseline;gap:8px}.stream .control{margin-bottom:10px}.presence{flex:1 1 auto;min-height:0;overflow-y:auto;padding:14px}.presence header{display:flex;align-items:baseline;justify-content:space-between;gap:8px;margin-bottom:12px}.flapping{margin-bottom:12px;padding:8px 10px;border:1px solid var(--line);border-left:3px solid var(--warn);border-radius:6px}.flapping p{margin:2px 0;font-size:12px;color:var(--text)}.coming{display:flex;align-items:baseline;gap:8px;padding:5px 0;border-bottom:1px solid rgba(38,46,58,.6);font-size:12px}.coming time{color:var(--muted);font-size:11px;white-space:nowrap}.coming .mark{font-family:var(--mono);width:10px}.presence-peer{display:inline-flex;flex-direction:column;gap:0;min-width:0}.coming.online .mark{color:var(--ok)}.coming.offline .mark{color:var(--bad)}.coming .link{margin-left:auto;font-size:10px;font-family:var(--mono)}.peer .role{margin-left:8px;padding:1px 6px;border-radius:999px;font-size:10px;font-family:var(--mono);background:var(--panel-2);color:var(--muted)}.peer .role.broker{color:#cfe6ff;background:var(--accent-dim)}.peer .role.undescribed{color:var(--warn)}.presets{display:flex;flex-wrap:wrap;align-items:center;gap:6px;margin:10px 0 4px}.preset{display:inline-flex;align-items:center}.preset .toggle{border-top-right-radius:0;border-bottom-right-radius:0;max-width:260px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.forget{padding:3px 7px;border:1px solid var(--line);border-left:none;border-radius:0 999px 999px 0;font-size:11px;color:var(--muted)}.forget:hover{color:var(--bad);border-color:var(--bad)}.component{border:1px solid var(--line);border-radius:8px;margin-bottom:6px;padding:9px 12px;background:var(--panel)}.component-head{display:flex;align-items:baseline;gap:10px;font-size:12px;flex-wrap:wrap}.component-head .toggle{margin-left:auto}.comp-label{font-family:var(--mono);font-size:11px;letter-spacing:.08em;text-transform:uppercase;color:var(--muted)}.status-badge{padding:1px 8px;border-radius:999px;font-size:11px;font-family:var(--mono);border:1px solid var(--line);color:var(--muted)}.status-badge.live{border-color:var(--ok);color:var(--ok)}.status-badge.stale{border-color:var(--warn);color:var(--warn)}.status-badge.closed{border-color:var(--bad);color:var(--bad)}.component-body{display:flex;gap:24px;flex-wrap:wrap;margin-top:8px}.component.stale .component-body{opacity:.55}.value-table{min-width:200px;flex:1}.value-table h4{margin:0 0 4px;font-size:11px;letter-spacing:.08em;text-transform:uppercase;color:var(--muted);font-weight:600}.value-row{display:flex;align-items:baseline;gap:10px;padding:3px 0;border-bottom:1px solid rgba(38,46,58,.6);font-size:13px}.value-name{color:var(--muted);min-width:110px}.value{word-break:break-word}.value .unit{color:var(--muted);font-size:12px}.quality{padding:0 7px;border-radius:999px;font-size:11px;font-family:var(--mono);border:1px solid var(--line);color:var(--muted)}.quality.q-good{border-color:var(--ok);color:var(--ok)}.quality.q-stale,.quality.q-uncertain{border-color:var(--warn);color:var(--warn)}.quality.q-bad{border-color:var(--bad);color:var(--bad)}.quality.forced{border-color:#c792ea;color:#c792ea}.component-error{color:var(--bad);font-size:12px;margin:6px 0 0}.sem{flex:none;padding:1px 7px;border-radius:999px;font-size:10px;font-family:var(--mono);border:1px solid var(--line);color:var(--muted)}.sem.idempotent{border-color:var(--accent-dim);color:var(--accent)}.sem.once{border-color:var(--warn);color:var(--warn)}.confirm{display:flex;align-items:center;gap:10px;padding:8px 10px;margin:8px 0;border:1px solid var(--warn);border-radius:6px;background:#f0b42914;font-size:12px}.confirm span{flex:1}.structure{border:1px solid var(--line);border-radius:8px;margin-bottom:6px;padding:9px 12px;background:var(--panel)}.structure-head{display:flex;align-items:baseline;gap:10px;flex-wrap:wrap;font-size:12px}.node-label{color:var(--muted);font-size:12px}.tree{list-style:none;margin:8px 0 0;padding:0;font-size:13px}.tree ul{list-style:none;margin:0;padding-left:18px;border-left:1px solid var(--line)}.tree li{padding:2px 0}.node{display:inline-flex;align-items:baseline;gap:8px}.owner-chip{padding:0 7px;border-radius:999px;font-size:11px;font-family:var(--mono);border:1px solid var(--line);color:var(--muted)}.owner-chip.link{cursor:pointer;border-color:var(--accent-dim);color:var(--accent)}.owner-chip.link:hover{border-color:var(--accent)}.capability{padding:1px 8px;border-radius:999px;font-size:11px;font-family:var(--mono);border:1px solid var(--accent-dim);color:var(--accent);font-weight:400}.peer-lines{display:inline-flex;flex-direction:column;align-items:flex-start;gap:1px;flex:1 1 auto;min-width:0;overflow:hidden}.peer-primary{display:flex;align-items:baseline;gap:7px;width:100%;min-width:0}.peer-display{min-width:0;color:var(--text);font-weight:600;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.peer-id{flex:0 1 auto;min-width:34px;max-width:100px;color:var(--muted);font-size:10px;font-weight:400;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.peer-place{font-size:10px;color:var(--muted);font-family:var(--mono);white-space:nowrap;overflow:hidden;text-overflow:ellipsis;max-width:180px}.value-branch{margin-left:12px;padding-left:10px;border-left:1px solid rgba(38,46,58,.9)}.value-node{padding:2px 0}.value-name.branch{display:inline-block;padding:2px 0;color:var(--fg);font-size:12px}.scope-pane{min-width:150px;max-width:240px;flex:0 0 auto;border-right:1px solid rgba(38,46,58,.9);padding-right:12px}.scope-node{padding:0}.scope-name{display:block;width:100%;text-align:left;background:none;border:0;padding:2px 4px;color:var(--muted);font-size:12px;cursor:pointer;border-radius:3px}.scope-name:hover{color:var(--fg);background:#262e3a80}.scope-name.on{color:var(--fg);background:#262e3ae6}.value-grid{min-width:0}.grid-head{display:flex;align-items:center;gap:10px;padding-bottom:4px}.collection{margin-top:10px}.collection-head{display:flex;align-items:baseline;gap:10px;padding:2px 0;border-bottom:1px solid rgba(38,46,58,.9)}.filter-box{width:11em;font-size:12px;padding:1px 5px}.sorter{display:flex;align-items:center;gap:4px;margin-left:auto}.sorter+.pager{margin-left:0}.waiting,.slow{color:var(--warn)}.collection-row{display:flex;align-items:baseline;gap:8px}.collection-row>:first-child{flex:1;min-width:0}.row-actions{display:flex;gap:4px;opacity:0;transition:opacity .1s}.collection-row:hover .row-actions,.collection-row:focus-within .row-actions{opacity:1}.pager{display:flex;align-items:center;gap:6px;margin-left:auto}.period{background:none;border:1px solid rgba(38,46,58,.9);border-radius:3px;color:var(--muted);font-size:11px;padding:1px 4px}.value-row .toggle.edit{margin-left:auto;opacity:0;transition:opacity .1s}.value-row:hover .toggle.edit,.value-row:focus-within .toggle.edit{opacity:1}.value-edit-form{display:flex;gap:6px;align-items:center}.value-edit{background:var(--bg);border:1px solid var(--accent);border-radius:4px;color:var(--fg);font-family:var(--mono);font-size:13px;padding:2px 6px;min-width:90px}.context{margin-top:10px;padding:10px 12px;border:1px solid var(--line);border-radius:6px}.context-head{display:flex;align-items:center;gap:10px;flex-wrap:wrap}.context-add{display:flex;gap:6px;margin-left:auto}.context-add input,.context-add select{background:var(--bg);border:1px solid var(--line);border-radius:4px;color:var(--fg);font-family:var(--mono);font-size:12px;padding:2px 6px}.context-token{margin-top:8px;padding-top:6px;border-top:1px solid rgba(38,46,58,.6)}.context-token-head{display:flex;align-items:center;gap:8px;flex-wrap:wrap}.context-token-head .toggle{margin-left:auto}.context-entry{margin:4px 0 4px 12px;padding-left:10px;border-left:2px solid rgba(38,46,58,.9)}.context-entry .provider{display:block;font-size:11px;margin-bottom:2px}.axis-badge{padding:1px 8px;border-radius:999px;font-size:11px;font-family:var(--mono);border:1px solid var(--line);color:var(--muted)}.axis-badge.physical{border-color:var(--accent);color:var(--accent)}.axis-badge.logical{border-color:var(--warn);color:var(--warn)}.status-badge.missing,.status-badge.initializing{border-color:var(--muted);color:var(--muted)}.status-badge.invalid,.status-badge.error{border-color:var(--bad);color:var(--bad)}.value-row{flex-wrap:wrap}.value-row .component-error{flex-basis:100%;margin:2px 0 0;font-size:12px}.elevated{display:flex;flex-wrap:wrap;align-items:baseline;gap:10px;margin:8px 0;padding:8px 12px;border:1px solid var(--warn);border-left-width:4px;border-radius:4px;background:#f0b42914;font-size:13px}.elevated strong{color:var(--warn);text-transform:uppercase;letter-spacing:.08em;font-size:11px}.elevation.unbounded{color:var(--bad)}