@tidegap/cli 0.0.0-stage → 0.19.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +201 -0
- package/README.md +136 -2
- package/dist/commands/clients.d.ts +20 -0
- package/dist/commands/clients.d.ts.map +1 -0
- package/dist/commands/clients.js +36 -0
- package/dist/commands/clients.js.map +1 -0
- package/dist/commands/env-file.d.ts +54 -0
- package/dist/commands/env-file.d.ts.map +1 -0
- package/dist/commands/env-file.js +153 -0
- package/dist/commands/env-file.js.map +1 -0
- package/dist/commands/install.d.ts +83 -0
- package/dist/commands/install.d.ts.map +1 -0
- package/dist/commands/install.js +297 -0
- package/dist/commands/install.js.map +1 -0
- package/dist/commands/queue.d.ts +139 -0
- package/dist/commands/queue.d.ts.map +1 -0
- package/dist/commands/queue.js +491 -0
- package/dist/commands/queue.js.map +1 -0
- package/dist/commands/receipts.d.ts +15 -0
- package/dist/commands/receipts.d.ts.map +1 -0
- package/dist/commands/receipts.js +70 -0
- package/dist/commands/receipts.js.map +1 -0
- package/dist/commands/register-wake.d.ts +21 -0
- package/dist/commands/register-wake.d.ts.map +1 -0
- package/dist/commands/register-wake.js +76 -0
- package/dist/commands/register-wake.js.map +1 -0
- package/dist/commands/stats.d.ts +31 -0
- package/dist/commands/stats.d.ts.map +1 -0
- package/dist/commands/stats.js +165 -0
- package/dist/commands/stats.js.map +1 -0
- package/dist/commands/tick.d.ts +73 -0
- package/dist/commands/tick.d.ts.map +1 -0
- package/dist/commands/tick.js +294 -0
- package/dist/commands/tick.js.map +1 -0
- package/dist/commands/ui.d.ts +33 -0
- package/dist/commands/ui.d.ts.map +1 -0
- package/dist/commands/ui.js +119 -0
- package/dist/commands/ui.js.map +1 -0
- package/dist/commands/verify.d.ts +32 -0
- package/dist/commands/verify.d.ts.map +1 -0
- package/dist/commands/verify.js +135 -0
- package/dist/commands/verify.js.map +1 -0
- package/dist/env.d.ts +57 -0
- package/dist/env.d.ts.map +1 -0
- package/dist/env.js +105 -0
- package/dist/env.js.map +1 -0
- package/dist/index.d.ts +25 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +366 -0
- package/dist/index.js.map +1 -0
- package/dist/platform/index.d.ts +30 -0
- package/dist/platform/index.d.ts.map +1 -0
- package/dist/platform/index.js +66 -0
- package/dist/platform/index.js.map +1 -0
- package/dist/platform/linux.d.ts +81 -0
- package/dist/platform/linux.d.ts.map +1 -0
- package/dist/platform/linux.js +158 -0
- package/dist/platform/linux.js.map +1 -0
- package/dist/platform/macos.d.ts +69 -0
- package/dist/platform/macos.d.ts.map +1 -0
- package/dist/platform/macos.js +151 -0
- package/dist/platform/macos.js.map +1 -0
- package/dist/platform/windows.d.ts +31 -0
- package/dist/platform/windows.d.ts.map +1 -0
- package/dist/platform/windows.js +58 -0
- package/dist/platform/windows.js.map +1 -0
- package/dist/ui/api.d.ts +66 -0
- package/dist/ui/api.d.ts.map +1 -0
- package/dist/ui/api.js +613 -0
- package/dist/ui/api.js.map +1 -0
- package/dist/ui/clients.d.ts +65 -0
- package/dist/ui/clients.d.ts.map +1 -0
- package/dist/ui/clients.js +179 -0
- package/dist/ui/clients.js.map +1 -0
- package/dist/ui/discovery.d.ts +25 -0
- package/dist/ui/discovery.d.ts.map +1 -0
- package/dist/ui/discovery.js +46 -0
- package/dist/ui/discovery.js.map +1 -0
- package/dist/ui/dispatcher.d.ts +84 -0
- package/dist/ui/dispatcher.d.ts.map +1 -0
- package/dist/ui/dispatcher.js +150 -0
- package/dist/ui/dispatcher.js.map +1 -0
- package/dist/ui/events.d.ts +27 -0
- package/dist/ui/events.d.ts.map +1 -0
- package/dist/ui/events.js +85 -0
- package/dist/ui/events.js.map +1 -0
- package/dist/ui/server.d.ts +84 -0
- package/dist/ui/server.d.ts.map +1 -0
- package/dist/ui/server.js +322 -0
- package/dist/ui/server.js.map +1 -0
- package/dist/ui/summary.d.ts +47 -0
- package/dist/ui/summary.d.ts.map +1 -0
- package/dist/ui/summary.js +120 -0
- package/dist/ui/summary.js.map +1 -0
- package/package.json +53 -3
- package/ui/agenda.js +256 -0
- package/ui/api.js +114 -0
- package/ui/app.css +713 -0
- package/ui/app.js +599 -0
- package/ui/clients.js +104 -0
- package/ui/detail.js +431 -0
- package/ui/dialog.js +284 -0
- package/ui/dnd.js +131 -0
- package/ui/dom.js +255 -0
- package/ui/index.html +38 -0
- package/ui/journal.js +207 -0
- package/ui/keys.js +92 -0
- package/ui/row.js +153 -0
- package/ui/state.js +412 -0
- package/ui/timeline.js +202 -0
- package/ui/toast.js +69 -0
- package/ui/tokens.css +108 -0
- package/ui/views.js +31 -0
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Change feed for `tidegap ui` (KTD10). Polls `PRAGMA data_version`, which moves
|
|
3
|
+
* when another connection commits (the tick, the MCP server). On a move it
|
|
4
|
+
* re-lists the ledger and reports the ids whose status, read marker or
|
|
5
|
+
* scheduled time changed. The server's own writes do not move
|
|
6
|
+
* `data_version`, so the server calls `emit` itself after a mark-read.
|
|
7
|
+
*/
|
|
8
|
+
export class TaskEvents {
|
|
9
|
+
store;
|
|
10
|
+
intervalMs;
|
|
11
|
+
listeners = new Set();
|
|
12
|
+
fingerprints;
|
|
13
|
+
version;
|
|
14
|
+
timer;
|
|
15
|
+
constructor(store, intervalMs = 2_000) {
|
|
16
|
+
this.store = store;
|
|
17
|
+
this.intervalMs = intervalMs;
|
|
18
|
+
this.version = store.dataVersion();
|
|
19
|
+
this.fingerprints = this.snapshot();
|
|
20
|
+
}
|
|
21
|
+
start() {
|
|
22
|
+
if (this.timer)
|
|
23
|
+
return;
|
|
24
|
+
this.timer = setInterval(() => this.poll(), this.intervalMs);
|
|
25
|
+
this.timer.unref();
|
|
26
|
+
}
|
|
27
|
+
stop() {
|
|
28
|
+
if (this.timer)
|
|
29
|
+
clearInterval(this.timer);
|
|
30
|
+
this.timer = undefined;
|
|
31
|
+
this.listeners.clear();
|
|
32
|
+
}
|
|
33
|
+
subscribe(fn) {
|
|
34
|
+
this.listeners.add(fn);
|
|
35
|
+
return () => this.listeners.delete(fn);
|
|
36
|
+
}
|
|
37
|
+
/** Push a change the poller cannot see (the server's own write). */
|
|
38
|
+
emit(ids) {
|
|
39
|
+
// Fingerprints stay as they are: re-reading them here could absorb a
|
|
40
|
+
// tick commit that landed between the write and the next poll. The next
|
|
41
|
+
// external commit may report these ids once more, which is harmless.
|
|
42
|
+
for (const fn of this.listeners)
|
|
43
|
+
fn(ids);
|
|
44
|
+
}
|
|
45
|
+
/** One poll step; public so tests need not wait on the timer. */
|
|
46
|
+
poll() {
|
|
47
|
+
let v;
|
|
48
|
+
try {
|
|
49
|
+
v = this.store.dataVersion();
|
|
50
|
+
}
|
|
51
|
+
catch {
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
if (v === this.version)
|
|
55
|
+
return;
|
|
56
|
+
let next;
|
|
57
|
+
try {
|
|
58
|
+
next = this.snapshot();
|
|
59
|
+
}
|
|
60
|
+
catch {
|
|
61
|
+
// A busy or locked ledger: keep the old version so the next poll retries.
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
this.version = v;
|
|
65
|
+
const changed = [];
|
|
66
|
+
for (const [id, fp] of next)
|
|
67
|
+
if (this.fingerprints.get(id) !== fp)
|
|
68
|
+
changed.push(id);
|
|
69
|
+
for (const id of this.fingerprints.keys())
|
|
70
|
+
if (!next.has(id))
|
|
71
|
+
changed.push(id);
|
|
72
|
+
this.fingerprints = next;
|
|
73
|
+
if (changed.length > 0)
|
|
74
|
+
for (const fn of this.listeners)
|
|
75
|
+
fn(changed);
|
|
76
|
+
}
|
|
77
|
+
snapshot() {
|
|
78
|
+
const out = new Map();
|
|
79
|
+
for (const r of this.store.list()) {
|
|
80
|
+
out.set(r.taskId, `${r.status}|${r.readAt ?? ""}|${r.scheduledFor ?? ""}|${r.deadline ?? ""}|${r.repeat ?? ""}`);
|
|
81
|
+
}
|
|
82
|
+
return out;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
//# sourceMappingURL=events.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"events.js","sourceRoot":"","sources":["../../src/ui/events.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAMH,MAAM,OAAO,UAAU;IAOF;IACA;IAPF,SAAS,GAAG,IAAI,GAAG,EAAsB,CAAC;IACnD,YAAY,CAAsB;IAClC,OAAO,CAAS;IAChB,KAAK,CAA6B;IAE1C,YACmB,KAAgB,EAChB,aAAa,KAAK;QADlB,UAAK,GAAL,KAAK,CAAW;QAChB,eAAU,GAAV,UAAU,CAAQ;QAEnC,IAAI,CAAC,OAAO,GAAG,KAAK,CAAC,WAAW,EAAE,CAAC;QACnC,IAAI,CAAC,YAAY,GAAG,IAAI,CAAC,QAAQ,EAAE,CAAC;IACtC,CAAC;IAED,KAAK;QACH,IAAI,IAAI,CAAC,KAAK;YAAE,OAAO;QACvB,IAAI,CAAC,KAAK,GAAG,WAAW,CAAC,GAAG,EAAE,CAAC,IAAI,CAAC,IAAI,EAAE,EAAE,IAAI,CAAC,UAAU,CAAC,CAAC;QAC7D,IAAI,CAAC,KAAK,CAAC,KAAK,EAAE,CAAC;IACrB,CAAC;IAED,IAAI;QACF,IAAI,IAAI,CAAC,KAAK;YAAE,aAAa,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QAC1C,IAAI,CAAC,KAAK,GAAG,SAAS,CAAC;QACvB,IAAI,CAAC,SAAS,CAAC,KAAK,EAAE,CAAC;IACzB,CAAC;IAED,SAAS,CAAC,EAAsB;QAC9B,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;QACvB,OAAO,GAAG,EAAE,CAAC,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC;IACzC,CAAC;IAED,oEAAoE;IACpE,IAAI,CAAC,GAAa;QAChB,qEAAqE;QACrE,wEAAwE;QACxE,qEAAqE;QACrE,KAAK,MAAM,EAAE,IAAI,IAAI,CAAC,SAAS;YAAE,EAAE,CAAC,GAAG,CAAC,CAAC;IAC3C,CAAC;IAED,iEAAiE;IACjE,IAAI;QACF,IAAI,CAAS,CAAC;QACd,IAAI,CAAC;YACH,CAAC,GAAG,IAAI,CAAC,KAAK,CAAC,WAAW,EAAE,CAAC;QAC/B,CAAC;QAAC,MAAM,CAAC;YACP,OAAO;QACT,CAAC;QACD,IAAI,CAAC,KAAK,IAAI,CAAC,OAAO;YAAE,OAAO;QAC/B,IAAI,IAAyB,CAAC;QAC9B,IAAI,CAAC;YACH,IAAI,GAAG,IAAI,CAAC,QAAQ,EAAE,CAAC;QACzB,CAAC;QAAC,MAAM,CAAC;YACP,0EAA0E;YAC1E,OAAO;QACT,CAAC;QACD,IAAI,CAAC,OAAO,GAAG,CAAC,CAAC;QACjB,MAAM,OAAO,GAAa,EAAE,CAAC;QAC7B,KAAK,MAAM,CAAC,EAAE,EAAE,EAAE,CAAC,IAAI,IAAI;YAAE,IAAI,IAAI,CAAC,YAAY,CAAC,GAAG,CAAC,EAAE,CAAC,KAAK,EAAE;gBAAE,OAAO,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;QACpF,KAAK,MAAM,EAAE,IAAI,IAAI,CAAC,YAAY,CAAC,IAAI,EAAE;YAAE,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC;gBAAE,OAAO,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;QAC/E,IAAI,CAAC,YAAY,GAAG,IAAI,CAAC;QACzB,IAAI,OAAO,CAAC,MAAM,GAAG,CAAC;YAAE,KAAK,MAAM,EAAE,IAAI,IAAI,CAAC,SAAS;gBAAE,EAAE,CAAC,OAAO,CAAC,CAAC;IACvE,CAAC;IAEO,QAAQ;QACd,MAAM,GAAG,GAAG,IAAI,GAAG,EAAkB,CAAC;QACtC,KAAK,MAAM,CAAC,IAAI,IAAI,CAAC,KAAK,CAAC,IAAI,EAAE,EAAE,CAAC;YAClC,GAAG,CAAC,GAAG,CACL,CAAC,CAAC,MAAM,EACR,GAAG,CAAC,CAAC,MAAM,IAAI,CAAC,CAAC,MAAM,IAAI,EAAE,IAAI,CAAC,CAAC,YAAY,IAAI,EAAE,IAAI,CAAC,CAAC,QAAQ,IAAI,EAAE,IAAI,CAAC,CAAC,MAAM,IAAI,EAAE,EAAE,CAC9F,CAAC;QACJ,CAAC;QACD,OAAO,GAAG,CAAC;IACb,CAAC;CACF"}
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `tidegap ui` HTTP server (KTD8, KTD9). Loopback only. Writes by POST, never
|
|
3
|
+
* dispatches: actions go through a Scheduler with no adapters, and the
|
|
4
|
+
* dispatcher (`tidegap tick` or the OpenClaw loop) runs the task.
|
|
5
|
+
*
|
|
6
|
+
* Security model:
|
|
7
|
+
* - binds 127.0.0.1 only; there is no host option;
|
|
8
|
+
* - every `/api/*` request (JSON and SSE) needs `Authorization: Bearer
|
|
9
|
+
* <token>`, compared in constant time. Two scopes (native clients KTD3):
|
|
10
|
+
* `page`, the random per-run token in the page link, and `client`, a
|
|
11
|
+
* long-lived `tgc_` token from the registry (`ui-clients.json`, SHA-256
|
|
12
|
+
* only; `tidegap clients`). A client token never reaches `/api/clients*`;
|
|
13
|
+
* - a `Host` header whose name is not 127.0.0.1, localhost or [::1] (with
|
|
14
|
+
* no port or this server's port) gets 403, which blocks DNS-rebinding
|
|
15
|
+
* pages;
|
|
16
|
+
* - an `Origin` header, when sent, must name the same loopback host, or be
|
|
17
|
+
* a browser extension origin (`chrome-extension://`, `moz-extension://`,
|
|
18
|
+
* `safari-web-extension://` plus an id). Any other origin, `null`
|
|
19
|
+
* included, gets 403. An extension origin reaches `/api/*` only, and only
|
|
20
|
+
* with a client token: the page token from an extension gets 403, and so
|
|
21
|
+
* does a static file. The origin only keeps web pages out; the token is
|
|
22
|
+
* the authentication;
|
|
23
|
+
* - no CORS headers, so other origins cannot read responses;
|
|
24
|
+
* - static files come from a fixed map; no request path touches the disk;
|
|
25
|
+
* - every write is a POST with the bearer header; routes that read a body
|
|
26
|
+
* (create, cancel, reschedule, reply, clients) need `Content-Type:
|
|
27
|
+
* application/json` (415 otherwise: a cross-site form cannot send it
|
|
28
|
+
* without a preflight, which gets no CORS answer) and at most 64 KiB
|
|
29
|
+
* (413); body-less routes (read, unread, run-now, dispatcher/start,
|
|
30
|
+
* revoke) refuse a non-empty body (400); bodies are strict JSON objects.
|
|
31
|
+
* No cookie is ever set.
|
|
32
|
+
* The static page holds no data; it reads the token from the URL fragment,
|
|
33
|
+
* which browsers never send to the server.
|
|
34
|
+
*/
|
|
35
|
+
import { type Server } from "node:http";
|
|
36
|
+
import { type ApiContext } from "./api.js";
|
|
37
|
+
import { type Spawner } from "./dispatcher.js";
|
|
38
|
+
import { TaskEvents } from "./events.js";
|
|
39
|
+
/** Largest POST body read, in bytes. */
|
|
40
|
+
export declare const MAX_BODY_BYTES: number;
|
|
41
|
+
export declare const LOOPBACK = "127.0.0.1";
|
|
42
|
+
/** ES modules of the page (ui v2). Each is listed: no path touches the disk. */
|
|
43
|
+
export declare const UI_MODULES: readonly ["app.js", "api.js", "state.js", "toast.js", "dom.js", "views.js", "row.js", "agenda.js", "detail.js", "journal.js", "timeline.js", "dnd.js", "keys.js", "dialog.js", "clients.js"];
|
|
44
|
+
export declare const STATIC_FILES: Readonly<Record<string, {
|
|
45
|
+
file: string;
|
|
46
|
+
type: string;
|
|
47
|
+
}>>;
|
|
48
|
+
/** `packages/cli/ui`, from both `src/ui` (tests) and `dist/ui` (published). */
|
|
49
|
+
export declare function defaultAssetDir(): string;
|
|
50
|
+
export interface UiServerOptions extends Omit<ApiContext, "store" | "dispatcher"> {
|
|
51
|
+
token: string;
|
|
52
|
+
/** `tidegap` launcher for Start dispatcher; default: the install step's. */
|
|
53
|
+
launcher?: string[];
|
|
54
|
+
/** Inject the dispatcher spawner, liveness probe and log dir (tests). */
|
|
55
|
+
spawn?: Spawner;
|
|
56
|
+
isAlive?: (pid: number) => boolean;
|
|
57
|
+
logDir?: string;
|
|
58
|
+
/** 0 (default) picks a free port. */
|
|
59
|
+
port?: number;
|
|
60
|
+
/** Directory of the client registry (`ui-clients.json`); default: the Tidegap home. */
|
|
61
|
+
stateDir?: string;
|
|
62
|
+
assetDir?: string;
|
|
63
|
+
/** data_version poll interval for the change stream. */
|
|
64
|
+
pollMs?: number;
|
|
65
|
+
}
|
|
66
|
+
export interface UiServer {
|
|
67
|
+
port: number;
|
|
68
|
+
address: string;
|
|
69
|
+
url: string;
|
|
70
|
+
server: Server;
|
|
71
|
+
events: TaskEvents;
|
|
72
|
+
close(): Promise<void>;
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* True when `host` (a Host header, or the host part of an Origin) names a
|
|
76
|
+
* loopback address with no port or with `port`. Anything else, including a
|
|
77
|
+
* DNS name that resolves to 127.0.0.1, is refused.
|
|
78
|
+
*/
|
|
79
|
+
export declare function isLoopbackHost(host: string, port: number): boolean;
|
|
80
|
+
export type OriginClass = "none" | "loopback" | "extension" | "foreign";
|
|
81
|
+
/** Classify an `Origin` header for a server on `port`. */
|
|
82
|
+
export declare function classifyOrigin(origin: string | undefined, port: number): OriginClass;
|
|
83
|
+
export declare function startUiServer(opts: UiServerOptions): Promise<UiServer>;
|
|
84
|
+
//# sourceMappingURL=server.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../../src/ui/server.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAiCG;AAIH,OAAO,EAAsC,KAAK,MAAM,EAAuB,MAAM,WAAW,CAAC;AASjG,OAAO,EAA2B,KAAK,UAAU,EAAE,MAAM,UAAU,CAAC;AAEpE,OAAO,EAAqB,KAAK,OAAO,EAAE,MAAM,iBAAiB,CAAC;AAClE,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAEzC,wCAAwC;AACxC,eAAO,MAAM,cAAc,QAAY,CAAC;AAExC,eAAO,MAAM,QAAQ,cAAc,CAAC;AAapC,gFAAgF;AAChF,eAAO,MAAM,UAAU,8LAgBb,CAAC;AAEX,eAAO,MAAM,YAAY,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,CAAC,CAKjF,CAAC;AAEF,+EAA+E;AAC/E,wBAAgB,eAAe,IAAI,MAAM,CAExC;AAED,MAAM,WAAW,eACf,SAAQ,IAAI,CAAC,UAAU,EAAE,OAAO,GAAG,YAAY,CAAC;IAChD,KAAK,EAAE,MAAM,CAAC;IACd,4EAA4E;IAC5E,QAAQ,CAAC,EAAE,MAAM,EAAE,CAAC;IACpB,yEAAyE;IACzE,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,OAAO,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,OAAO,CAAC;IACnC,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,qCAAqC;IACrC,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,uFAAuF;IACvF,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,wDAAwD;IACxD,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,QAAQ;IACvB,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,GAAG,EAAE,MAAM,CAAC;IACZ,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,UAAU,CAAC;IACnB,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;CACxB;AAQD;;;;GAIG;AACH,wBAAgB,cAAc,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAIlE;AAKD,MAAM,MAAM,WAAW,GAAG,MAAM,GAAG,UAAU,GAAG,WAAW,GAAG,SAAS,CAAC;AAExE,0DAA0D;AAC1D,wBAAgB,cAAc,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,EAAE,IAAI,EAAE,MAAM,GAAG,WAAW,CAKpF;AA6DD,wBAAsB,aAAa,CAAC,IAAI,EAAE,eAAe,GAAG,OAAO,CAAC,QAAQ,CAAC,CAwI5E"}
|
|
@@ -0,0 +1,322 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `tidegap ui` HTTP server (KTD8, KTD9). Loopback only. Writes by POST, never
|
|
3
|
+
* dispatches: actions go through a Scheduler with no adapters, and the
|
|
4
|
+
* dispatcher (`tidegap tick` or the OpenClaw loop) runs the task.
|
|
5
|
+
*
|
|
6
|
+
* Security model:
|
|
7
|
+
* - binds 127.0.0.1 only; there is no host option;
|
|
8
|
+
* - every `/api/*` request (JSON and SSE) needs `Authorization: Bearer
|
|
9
|
+
* <token>`, compared in constant time. Two scopes (native clients KTD3):
|
|
10
|
+
* `page`, the random per-run token in the page link, and `client`, a
|
|
11
|
+
* long-lived `tgc_` token from the registry (`ui-clients.json`, SHA-256
|
|
12
|
+
* only; `tidegap clients`). A client token never reaches `/api/clients*`;
|
|
13
|
+
* - a `Host` header whose name is not 127.0.0.1, localhost or [::1] (with
|
|
14
|
+
* no port or this server's port) gets 403, which blocks DNS-rebinding
|
|
15
|
+
* pages;
|
|
16
|
+
* - an `Origin` header, when sent, must name the same loopback host, or be
|
|
17
|
+
* a browser extension origin (`chrome-extension://`, `moz-extension://`,
|
|
18
|
+
* `safari-web-extension://` plus an id). Any other origin, `null`
|
|
19
|
+
* included, gets 403. An extension origin reaches `/api/*` only, and only
|
|
20
|
+
* with a client token: the page token from an extension gets 403, and so
|
|
21
|
+
* does a static file. The origin only keeps web pages out; the token is
|
|
22
|
+
* the authentication;
|
|
23
|
+
* - no CORS headers, so other origins cannot read responses;
|
|
24
|
+
* - static files come from a fixed map; no request path touches the disk;
|
|
25
|
+
* - every write is a POST with the bearer header; routes that read a body
|
|
26
|
+
* (create, cancel, reschedule, reply, clients) need `Content-Type:
|
|
27
|
+
* application/json` (415 otherwise: a cross-site form cannot send it
|
|
28
|
+
* without a preflight, which gets no CORS answer) and at most 64 KiB
|
|
29
|
+
* (413); body-less routes (read, unread, run-now, dispatcher/start,
|
|
30
|
+
* revoke) refuse a non-empty body (400); bodies are strict JSON objects.
|
|
31
|
+
* No cookie is ever set.
|
|
32
|
+
* The static page holds no data; it reads the token from the URL fragment,
|
|
33
|
+
* which browsers never send to the server.
|
|
34
|
+
*/
|
|
35
|
+
import { createHash, timingSafeEqual } from "node:crypto";
|
|
36
|
+
import { readFileSync } from "node:fs";
|
|
37
|
+
import { createServer } from "node:http";
|
|
38
|
+
import { dirname, join } from "node:path";
|
|
39
|
+
import { fileURLToPath } from "node:url";
|
|
40
|
+
import { TaskStore } from "@tidegap/core";
|
|
41
|
+
import { resolveLauncher } from "../commands/install.js";
|
|
42
|
+
import { actionScheduler } from "../commands/queue.js";
|
|
43
|
+
import { handleApi, postBodyKind } from "./api.js";
|
|
44
|
+
import { ClientRegistry } from "./clients.js";
|
|
45
|
+
import { DispatcherControl } from "./dispatcher.js";
|
|
46
|
+
import { TaskEvents } from "./events.js";
|
|
47
|
+
/** Largest POST body read, in bytes. */
|
|
48
|
+
export const MAX_BODY_BYTES = 64 * 1024;
|
|
49
|
+
export const LOOPBACK = "127.0.0.1";
|
|
50
|
+
const SECURITY_HEADERS = {
|
|
51
|
+
"content-security-policy": "default-src 'self'; connect-src 'self'; img-src 'self' data:; frame-ancestors 'none'; base-uri 'none'; form-action 'none'",
|
|
52
|
+
"x-content-type-options": "nosniff",
|
|
53
|
+
"referrer-policy": "no-referrer",
|
|
54
|
+
"cache-control": "no-store",
|
|
55
|
+
};
|
|
56
|
+
const JS = "text/javascript; charset=utf-8";
|
|
57
|
+
const CSS = "text/css; charset=utf-8";
|
|
58
|
+
/** ES modules of the page (ui v2). Each is listed: no path touches the disk. */
|
|
59
|
+
export const UI_MODULES = [
|
|
60
|
+
"app.js",
|
|
61
|
+
"api.js",
|
|
62
|
+
"state.js",
|
|
63
|
+
"toast.js",
|
|
64
|
+
"dom.js",
|
|
65
|
+
"views.js",
|
|
66
|
+
"row.js",
|
|
67
|
+
"agenda.js",
|
|
68
|
+
"detail.js",
|
|
69
|
+
"journal.js",
|
|
70
|
+
"timeline.js",
|
|
71
|
+
"dnd.js",
|
|
72
|
+
"keys.js",
|
|
73
|
+
"dialog.js",
|
|
74
|
+
"clients.js",
|
|
75
|
+
];
|
|
76
|
+
export const STATIC_FILES = {
|
|
77
|
+
"/": { file: "index.html", type: "text/html; charset=utf-8" },
|
|
78
|
+
"/tokens.css": { file: "tokens.css", type: CSS },
|
|
79
|
+
"/app.css": { file: "app.css", type: CSS },
|
|
80
|
+
...Object.fromEntries(UI_MODULES.map((f) => [`/${f}`, { file: f, type: JS }])),
|
|
81
|
+
};
|
|
82
|
+
/** `packages/cli/ui`, from both `src/ui` (tests) and `dist/ui` (published). */
|
|
83
|
+
export function defaultAssetDir() {
|
|
84
|
+
return join(dirname(fileURLToPath(import.meta.url)), "..", "..", "ui");
|
|
85
|
+
}
|
|
86
|
+
function digest(s) {
|
|
87
|
+
return createHash("sha256").update(s).digest();
|
|
88
|
+
}
|
|
89
|
+
const LOOPBACK_NAMES = new Set([LOOPBACK, "localhost", "[::1]"]);
|
|
90
|
+
/**
|
|
91
|
+
* True when `host` (a Host header, or the host part of an Origin) names a
|
|
92
|
+
* loopback address with no port or with `port`. Anything else, including a
|
|
93
|
+
* DNS name that resolves to 127.0.0.1, is refused.
|
|
94
|
+
*/
|
|
95
|
+
export function isLoopbackHost(host, port) {
|
|
96
|
+
const m = /^(\[::1\]|[^:]+)(?::(\d+))?$/.exec(host.trim().toLowerCase());
|
|
97
|
+
if (!m || !LOOPBACK_NAMES.has(m[1]))
|
|
98
|
+
return false;
|
|
99
|
+
return m[2] === undefined || Number(m[2]) === port;
|
|
100
|
+
}
|
|
101
|
+
/** A browser extension origin: scheme plus an id, nothing else (KTD3). */
|
|
102
|
+
const EXTENSION_ORIGIN = /^(?:chrome-extension|moz-extension|safari-web-extension):\/\/[a-z0-9-]+$/i;
|
|
103
|
+
/** Classify an `Origin` header for a server on `port`. */
|
|
104
|
+
export function classifyOrigin(origin, port) {
|
|
105
|
+
if (origin === undefined)
|
|
106
|
+
return "none";
|
|
107
|
+
const m = /^http:\/\/([^/]+)$/i.exec(origin);
|
|
108
|
+
if (m && isLoopbackHost(m[1], port))
|
|
109
|
+
return "loopback";
|
|
110
|
+
return EXTENSION_ORIGIN.test(origin) ? "extension" : "foreign";
|
|
111
|
+
}
|
|
112
|
+
/**
|
|
113
|
+
* `/api/clients` and everything under it: page token only. Splits the path
|
|
114
|
+
* the way the router does (empty segments dropped), so `/api//clients`
|
|
115
|
+
* cannot route to the clients handlers past this check.
|
|
116
|
+
*/
|
|
117
|
+
function isClientsRoute(pathname) {
|
|
118
|
+
const segs = pathname.split("/").filter(Boolean);
|
|
119
|
+
return segs[0] === "api" && segs[1] === "clients";
|
|
120
|
+
}
|
|
121
|
+
function send(res, status, body, type = "application/json; charset=utf-8") {
|
|
122
|
+
res.writeHead(status, { ...SECURITY_HEADERS, ...(body === undefined ? {} : { "content-type": type }) });
|
|
123
|
+
if (body === undefined)
|
|
124
|
+
res.end();
|
|
125
|
+
else
|
|
126
|
+
res.end(typeof body === "string" || Buffer.isBuffer(body) ? body : JSON.stringify(body));
|
|
127
|
+
}
|
|
128
|
+
/** Read a request body, keeping at most `max + 1` bytes; `null` when over `max`. */
|
|
129
|
+
function readBody(req, max) {
|
|
130
|
+
return new Promise((resolve, reject) => {
|
|
131
|
+
const chunks = [];
|
|
132
|
+
let size = 0;
|
|
133
|
+
req.on("data", (chunk) => {
|
|
134
|
+
size += chunk.length;
|
|
135
|
+
// Keep draining past the cap so the client can read the 413.
|
|
136
|
+
if (size <= max)
|
|
137
|
+
chunks.push(chunk);
|
|
138
|
+
});
|
|
139
|
+
req.on("end", () => resolve(size > max ? null : Buffer.concat(chunks)));
|
|
140
|
+
req.on("error", reject);
|
|
141
|
+
});
|
|
142
|
+
}
|
|
143
|
+
/** KTD9 body rules for one POST route. */
|
|
144
|
+
async function checkBody(req, kind) {
|
|
145
|
+
if (kind === "json") {
|
|
146
|
+
const type = (req.headers["content-type"] ?? "").split(";")[0].trim().toLowerCase();
|
|
147
|
+
if (type !== "application/json") {
|
|
148
|
+
return { ok: false, status: 415, error: "content-type must be application/json" };
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
const raw = await readBody(req, MAX_BODY_BYTES);
|
|
152
|
+
if (raw === null)
|
|
153
|
+
return { ok: false, status: 413, error: "body too large" };
|
|
154
|
+
if (kind === "none") {
|
|
155
|
+
return raw.length === 0 ? { ok: true, body: {} } : { ok: false, status: 400, error: "this route takes no body" };
|
|
156
|
+
}
|
|
157
|
+
if (raw.length === 0)
|
|
158
|
+
return { ok: true, body: {} };
|
|
159
|
+
let parsed;
|
|
160
|
+
try {
|
|
161
|
+
parsed = JSON.parse(raw.toString("utf8"));
|
|
162
|
+
}
|
|
163
|
+
catch {
|
|
164
|
+
return { ok: false, status: 400, error: "body is not JSON" };
|
|
165
|
+
}
|
|
166
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
167
|
+
return { ok: false, status: 400, error: "body must be a JSON object" };
|
|
168
|
+
}
|
|
169
|
+
return { ok: true, body: parsed };
|
|
170
|
+
}
|
|
171
|
+
export async function startUiServer(opts) {
|
|
172
|
+
const store = new TaskStore({ dbPath: opts.dbPath });
|
|
173
|
+
const events = new TaskEvents(store, opts.pollMs);
|
|
174
|
+
// Writes go through a Scheduler built WITHOUT adapters (KTD8): it can
|
|
175
|
+
// cancel, move, mark run-now and enqueue a reply, never dispatch.
|
|
176
|
+
const ownScheduler = opts.scheduler === undefined;
|
|
177
|
+
const scheduler = opts.scheduler ??
|
|
178
|
+
actionScheduler({ dbPath: opts.dbPath, feed: opts.feed, region: opts.defaultRegion });
|
|
179
|
+
const dispatcher = new DispatcherControl(store, {
|
|
180
|
+
launcher: opts.launcher ?? resolveLauncher(undefined, []),
|
|
181
|
+
dbPath: opts.dbPath,
|
|
182
|
+
daemonStatus: opts.daemonStatus,
|
|
183
|
+
...(opts.spawn ? { spawn: opts.spawn } : {}),
|
|
184
|
+
...(opts.isAlive ? { isAlive: opts.isAlive } : {}),
|
|
185
|
+
...(opts.logDir ? { logDir: opts.logDir } : {}),
|
|
186
|
+
...(opts.now ? { now: opts.now } : {}),
|
|
187
|
+
});
|
|
188
|
+
const clients = opts.clients ?? new ClientRegistry(opts.stateDir);
|
|
189
|
+
const ctx = { ...opts, store, scheduler, dispatcher, clients };
|
|
190
|
+
const assetDir = opts.assetDir ?? defaultAssetDir();
|
|
191
|
+
const tokenDigest = digest(opts.token);
|
|
192
|
+
let port = 0;
|
|
193
|
+
/** The bearer's scope: the per-run page token, a registry client token, or none. */
|
|
194
|
+
const scope = (bearer) => {
|
|
195
|
+
if (bearer === undefined)
|
|
196
|
+
return undefined;
|
|
197
|
+
if (timingSafeEqual(digest(bearer), tokenDigest))
|
|
198
|
+
return "page";
|
|
199
|
+
return clients.verify(bearer) ? "client" : undefined;
|
|
200
|
+
};
|
|
201
|
+
/**
|
|
202
|
+
* The change stream. For a client token (`clientBearer`), the bearer is
|
|
203
|
+
* checked again before each event and on each keep-alive: once it is
|
|
204
|
+
* revoked the stream ends, and a reconnect gets 401.
|
|
205
|
+
*/
|
|
206
|
+
const openStream = (req, res, clientBearer) => {
|
|
207
|
+
res.writeHead(200, {
|
|
208
|
+
...SECURITY_HEADERS,
|
|
209
|
+
"content-type": "text/event-stream; charset=utf-8",
|
|
210
|
+
connection: "keep-alive",
|
|
211
|
+
});
|
|
212
|
+
res.write(`event: hello\ndata: {}\n\n`);
|
|
213
|
+
const valid = () => clientBearer === undefined || clients.verify(clientBearer) !== undefined;
|
|
214
|
+
let open = true;
|
|
215
|
+
const end = () => {
|
|
216
|
+
if (!open)
|
|
217
|
+
return;
|
|
218
|
+
open = false;
|
|
219
|
+
clearInterval(keepAlive);
|
|
220
|
+
unsubscribe();
|
|
221
|
+
res.end();
|
|
222
|
+
};
|
|
223
|
+
const unsubscribe = events.subscribe((ids) => {
|
|
224
|
+
if (!valid())
|
|
225
|
+
return end();
|
|
226
|
+
res.write(`event: tasks\ndata: ${JSON.stringify({ ids })}\n\n`);
|
|
227
|
+
});
|
|
228
|
+
const keepAlive = setInterval(() => (valid() ? res.write(`: keep-alive\n\n`) : end()), 15_000);
|
|
229
|
+
keepAlive.unref();
|
|
230
|
+
req.on("close", end);
|
|
231
|
+
};
|
|
232
|
+
const server = createServer((req, res) => {
|
|
233
|
+
void (async () => {
|
|
234
|
+
if (!isLoopbackHost(req.headers.host ?? "", port))
|
|
235
|
+
return send(res, 403, { error: "forbidden host" });
|
|
236
|
+
const origin = classifyOrigin(req.headers.origin, port);
|
|
237
|
+
if (origin === "foreign")
|
|
238
|
+
return send(res, 403, { error: "forbidden origin" });
|
|
239
|
+
const url = new URL(req.url ?? "/", `http://${LOOPBACK}`);
|
|
240
|
+
const method = req.method ?? "GET";
|
|
241
|
+
if (url.pathname === "/api" || url.pathname.startsWith("/api/")) {
|
|
242
|
+
const bearer = /^Bearer (.+)$/.exec(req.headers.authorization ?? "")?.[1];
|
|
243
|
+
const who = scope(bearer);
|
|
244
|
+
if (!who)
|
|
245
|
+
return send(res, 401, { error: "unauthorized" });
|
|
246
|
+
// An extension uses a client token; the page token stays in the page.
|
|
247
|
+
if (origin === "extension" && who === "page")
|
|
248
|
+
return send(res, 403, { error: "forbidden origin" });
|
|
249
|
+
if (who === "client" && isClientsRoute(url.pathname))
|
|
250
|
+
return send(res, 403, { error: "forbidden" });
|
|
251
|
+
if (url.pathname === "/api/events") {
|
|
252
|
+
return method === "GET"
|
|
253
|
+
? openStream(req, res, who === "client" ? bearer : undefined)
|
|
254
|
+
: send(res, 405, { error: "method not allowed" });
|
|
255
|
+
}
|
|
256
|
+
let body = {};
|
|
257
|
+
const kind = method === "POST" ? postBodyKind(url.pathname) : undefined;
|
|
258
|
+
if (kind) {
|
|
259
|
+
const checked = await checkBody(req, kind);
|
|
260
|
+
if (!checked.ok)
|
|
261
|
+
return send(res, checked.status, { error: checked.error });
|
|
262
|
+
body = checked.body;
|
|
263
|
+
}
|
|
264
|
+
const out = await handleApi(ctx, method, url.pathname, url.searchParams, body);
|
|
265
|
+
if (out.changedIds)
|
|
266
|
+
events.emit(out.changedIds);
|
|
267
|
+
return send(res, out.status, out.body);
|
|
268
|
+
}
|
|
269
|
+
if (origin === "extension")
|
|
270
|
+
return send(res, 403, { error: "forbidden origin" });
|
|
271
|
+
const asset = Object.hasOwn(STATIC_FILES, url.pathname) ? STATIC_FILES[url.pathname] : undefined;
|
|
272
|
+
if (!asset)
|
|
273
|
+
return send(res, 404, "not found", "text/plain; charset=utf-8");
|
|
274
|
+
if (method !== "GET" && method !== "HEAD")
|
|
275
|
+
return send(res, 405, "method not allowed", "text/plain; charset=utf-8");
|
|
276
|
+
try {
|
|
277
|
+
return send(res, 200, readFileSync(join(assetDir, asset.file)), asset.type);
|
|
278
|
+
}
|
|
279
|
+
catch {
|
|
280
|
+
return send(res, 404, "not found", "text/plain; charset=utf-8");
|
|
281
|
+
}
|
|
282
|
+
})().catch(() => {
|
|
283
|
+
// No error text in the response: it can carry paths or provider detail.
|
|
284
|
+
if (!res.headersSent)
|
|
285
|
+
send(res, 500, { error: "internal error" });
|
|
286
|
+
else
|
|
287
|
+
res.end();
|
|
288
|
+
});
|
|
289
|
+
});
|
|
290
|
+
try {
|
|
291
|
+
await new Promise((resolve, reject) => {
|
|
292
|
+
server.once("error", reject);
|
|
293
|
+
server.listen(opts.port ?? 0, LOOPBACK, () => resolve());
|
|
294
|
+
});
|
|
295
|
+
}
|
|
296
|
+
catch (err) {
|
|
297
|
+
// Port taken (the CLI retries on a free port): release what was opened.
|
|
298
|
+
if (ownScheduler)
|
|
299
|
+
scheduler.shutdown();
|
|
300
|
+
store.close();
|
|
301
|
+
throw err;
|
|
302
|
+
}
|
|
303
|
+
const addr = server.address();
|
|
304
|
+
port = addr.port;
|
|
305
|
+
events.start();
|
|
306
|
+
return {
|
|
307
|
+
port,
|
|
308
|
+
address: addr.address,
|
|
309
|
+
url: `http://${LOOPBACK}:${port}/`,
|
|
310
|
+
server,
|
|
311
|
+
events,
|
|
312
|
+
close: async () => {
|
|
313
|
+
events.stop();
|
|
314
|
+
server.closeAllConnections();
|
|
315
|
+
await new Promise((resolve) => server.close(() => resolve()));
|
|
316
|
+
if (ownScheduler)
|
|
317
|
+
scheduler.shutdown();
|
|
318
|
+
store.close();
|
|
319
|
+
},
|
|
320
|
+
};
|
|
321
|
+
}
|
|
322
|
+
//# sourceMappingURL=server.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"server.js","sourceRoot":"","sources":["../../src/ui/server.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `GET /api/summary` (native clients KTD5): the one answer a compact client
|
|
3
|
+
* renders. Queue counts, unread count, the next run, the next clean grid
|
|
4
|
+
* window and, with `since`, the tasks finished after that cursor. The clean
|
|
5
|
+
* window rule lives here once, so the extension badge and the Mac menu bar
|
|
6
|
+
* show the same numbers.
|
|
7
|
+
*/
|
|
8
|
+
import type { TaskStore } from "@tidegap/core";
|
|
9
|
+
/** One forecast hour, as `/api/forecast` returns it. */
|
|
10
|
+
export interface ForecastPoint {
|
|
11
|
+
/** ISO start of the hour. */
|
|
12
|
+
t: string;
|
|
13
|
+
g: number;
|
|
14
|
+
band: string;
|
|
15
|
+
}
|
|
16
|
+
export interface CleanWindow {
|
|
17
|
+
start: string;
|
|
18
|
+
end: string;
|
|
19
|
+
/** `very_clean` when every hour is very clean, else `clean` (or the fallback hour's band). */
|
|
20
|
+
band: string;
|
|
21
|
+
/** Lowest intensity in the window, gCO2e/kWh. */
|
|
22
|
+
g: number;
|
|
23
|
+
/** True when the window contains now. */
|
|
24
|
+
now: boolean;
|
|
25
|
+
/** True when no hour is clean: the window is the single cleanest hour. */
|
|
26
|
+
fallback: boolean;
|
|
27
|
+
}
|
|
28
|
+
/** Most finished tasks one summary lists. */
|
|
29
|
+
export declare const SUMMARY_FINISHED_MAX = 20;
|
|
30
|
+
export declare const ACTIVE_STATUSES: readonly ["queued", "scheduled", "running", "submitted"];
|
|
31
|
+
/**
|
|
32
|
+
* The first run of consecutive clean or very clean hours that ends after
|
|
33
|
+
* `now`. Without one, the cleanest hour that ends after `now`, flagged
|
|
34
|
+
* `fallback`. `null` when the forecast has no hour after `now`.
|
|
35
|
+
*/
|
|
36
|
+
export declare function nextCleanWindow(points: readonly ForecastPoint[], now: Date): CleanWindow | null;
|
|
37
|
+
export interface SummaryInput {
|
|
38
|
+
store: TaskStore;
|
|
39
|
+
region: string;
|
|
40
|
+
now: Date;
|
|
41
|
+
/** Forecast points, or `null` when the feed failed. */
|
|
42
|
+
points: readonly ForecastPoint[] | null;
|
|
43
|
+
since?: Date;
|
|
44
|
+
}
|
|
45
|
+
/** Assemble the summary body (KTD5). */
|
|
46
|
+
export declare function buildSummary(input: SummaryInput): Record<string, unknown>;
|
|
47
|
+
//# sourceMappingURL=summary.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"summary.d.ts","sourceRoot":"","sources":["../../src/ui/summary.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,KAAK,EAA0B,SAAS,EAAE,MAAM,eAAe,CAAC;AAIvE,wDAAwD;AACxD,MAAM,WAAW,aAAa;IAC5B,6BAA6B;IAC7B,CAAC,EAAE,MAAM,CAAC;IACV,CAAC,EAAE,MAAM,CAAC;IACV,IAAI,EAAE,MAAM,CAAC;CACd;AAED,MAAM,WAAW,WAAW;IAC1B,KAAK,EAAE,MAAM,CAAC;IACd,GAAG,EAAE,MAAM,CAAC;IACZ,8FAA8F;IAC9F,IAAI,EAAE,MAAM,CAAC;IACb,iDAAiD;IACjD,CAAC,EAAE,MAAM,CAAC;IACV,yCAAyC;IACzC,GAAG,EAAE,OAAO,CAAC;IACb,0EAA0E;IAC1E,QAAQ,EAAE,OAAO,CAAC;CACnB;AAID,6CAA6C;AAC7C,eAAO,MAAM,oBAAoB,KAAK,CAAC;AACvC,eAAO,MAAM,eAAe,0DAA2F,CAAC;AAExH;;;;GAIG;AACH,wBAAgB,eAAe,CAAC,MAAM,EAAE,SAAS,aAAa,EAAE,EAAE,GAAG,EAAE,IAAI,GAAG,WAAW,GAAG,IAAI,CAmC/F;AAED,MAAM,WAAW,YAAY;IAC3B,KAAK,EAAE,SAAS,CAAC;IACjB,MAAM,EAAE,MAAM,CAAC;IACf,GAAG,EAAE,IAAI,CAAC;IACV,uDAAuD;IACvD,MAAM,EAAE,SAAS,aAAa,EAAE,GAAG,IAAI,CAAC;IACxC,KAAK,CAAC,EAAE,IAAI,CAAC;CACd;AAuBD,wCAAwC;AACxC,wBAAgB,YAAY,CAAC,KAAK,EAAE,YAAY,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAsCzE"}
|