@zeph-to/cli 1.27.1 → 2.1.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/README.md +59 -29
- package/dist/cli.js +6 -1
- package/dist/crypto.d.ts +80 -55
- package/dist/crypto.d.ts.map +1 -1
- package/dist/crypto.js +178 -184
- package/dist/gate.d.ts +47 -2
- package/dist/gate.d.ts.map +1 -1
- package/dist/gate.js +50 -4
- package/dist/input-sequencer.d.ts +66 -0
- package/dist/input-sequencer.d.ts.map +1 -0
- package/dist/input-sequencer.js +127 -0
- package/dist/installer.d.ts.map +1 -1
- package/dist/installer.js +7 -3
- package/dist/listener.d.ts +119 -2
- package/dist/listener.d.ts.map +1 -1
- package/dist/listener.js +549 -40
- package/dist/templates.d.ts.map +1 -1
- package/dist/templates.js +15 -5
- package/dist/types.d.ts +2 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/zeph-hook.d.ts +9 -0
- package/dist/zeph-hook.d.ts.map +1 -1
- package/dist/zeph-hook.js +81 -46
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -45,9 +45,11 @@ binary.
|
|
|
45
45
|
|
|
46
46
|
Once installed, the hooks fire in **every** session of each configured
|
|
47
47
|
agent — `zeph cc` is the phone-control bridge, not the notification
|
|
48
|
-
switch.
|
|
49
|
-
|
|
50
|
-
`/zeph-
|
|
48
|
+
switch. In Claude Code the routine per-turn push starts off (`quiet` is
|
|
49
|
+
the default); `/zeph-normal` turns it on for a project, `/zeph-loud`
|
|
50
|
+
pushes on every turn, `/zeph-mute` silences a project entirely, and
|
|
51
|
+
`/zeph-status` shows what's in effect. See
|
|
52
|
+
[Mute & push mode](#mute--push-mode).
|
|
51
53
|
|
|
52
54
|
`~/.zeph/config.json` is the single source of truth — the CLI, the MCP
|
|
53
55
|
server, the plugin hooks, and the listener all read it. You never need
|
|
@@ -424,6 +426,7 @@ zeph notify --title "Hello" --json
|
|
|
424
426
|
| `--device <id>` | Target device ID |
|
|
425
427
|
| `--session <id>` | AI session ID so the push threads into that session's chat (or `ZEPH_SESSION_ID` env) |
|
|
426
428
|
| `--auto` | Apply the push gate before sending — honors the `/zeph-quiet` / `/zeph-loud` push-mode dial, per project or machine-wide (`--global`); gated-out exits silently with code 0 |
|
|
429
|
+
| `--pushmode-default <m>` | Mode `--auto` assumes when the project has no dial: `quiet` (built-in), `normal`, `loud`. A dial the user set always wins |
|
|
427
430
|
| `--marker <m>` | Push Signal marker for `--auto`: `skip`, `push`, `high` |
|
|
428
431
|
| `--tools <n>`, `--nonreadonly <n>` | Turn tool counts feeding `--auto`'s heuristic (defaults assume real work) |
|
|
429
432
|
|
|
@@ -491,13 +494,25 @@ this order — first hit wins:
|
|
|
491
494
|
|-------|------|--------|
|
|
492
495
|
| 1 | `$STATE_DIR/pushmode-<hash>` | `/zeph-quiet` · `/zeph-loud` · `/zeph-normal` |
|
|
493
496
|
| 2 | `/tmp/zeph-pushmode-<hash>` | older versions (honored only when you own the file) |
|
|
494
|
-
| 3 | `$STATE_DIR/pushmode-default` |
|
|
495
|
-
| 4 |
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
497
|
+
| 3 | `$STATE_DIR/pushmode-default` | the `--global` form of any dial — the machine-wide default |
|
|
498
|
+
| 4 | `--pushmode-default <mode>` | the calling hook (the installed ones pass `normal`) |
|
|
499
|
+
| 5 | *(nothing above)* | `quiet` |
|
|
500
|
+
|
|
501
|
+
**Row 5 changed**: an install with no dial anywhere used to be `normal`.
|
|
502
|
+
It is now `quiet`, so upgrading turns the routine per-turn push off until
|
|
503
|
+
you run `/zeph-normal`. Row 4 is why the hooks this CLI installs are
|
|
504
|
+
unaffected: they name `normal` themselves, since a hook that supplies no
|
|
505
|
+
turn counts also supplies no `high` marker, and `quiet` would make it
|
|
506
|
+
permanently silent rather than merely quieter. Row 4 sits *below* the
|
|
507
|
+
state files on purpose — the flag names a default, it does not override a
|
|
508
|
+
dial the user set.
|
|
509
|
+
|
|
510
|
+
A dial file that exists but reads empty resolves to `normal`, not to row
|
|
511
|
+
5: an empty file is a failed write, and resolving breakage to silence
|
|
512
|
+
leaves no symptom to debug.
|
|
513
|
+
|
|
514
|
+
Mute has no `-default` form on purpose: it is keyed on presence, not
|
|
515
|
+
content, so a global mute could never be lifted for a single project.
|
|
501
516
|
|
|
502
517
|
Legacy `/tmp/zeph-muted-<hash>` files are still honored when owned by the
|
|
503
518
|
current user (the state dir moved out of world-writable `/tmp`).
|
|
@@ -604,25 +619,40 @@ session.
|
|
|
604
619
|
|
|
605
620
|
## Encryption
|
|
606
621
|
|
|
607
|
-
Push bodies are encrypted with AES-256-GCM.
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
622
|
+
Push bodies and long-body attachments are encrypted with AES-256-GCM.
|
|
623
|
+
This host holds its own ECDH P-256 keypair in `~/.zeph/device-keys.json`
|
|
624
|
+
— generated on first use, and the private half never leaves the machine.
|
|
625
|
+
Each push is encrypted once, and its AES key is wrapped separately for
|
|
626
|
+
every device on your account using ECDH against that device's public
|
|
627
|
+
key.
|
|
628
|
+
|
|
629
|
+
Toggle encryption in the Zeph app (Settings → Encryption); when it is
|
|
630
|
+
off, the CLI sends plaintext. No configuration needed.
|
|
631
|
+
|
|
632
|
+
**Threat model:** against a passive backend — a leaked snapshot, an
|
|
633
|
+
operator reading the table — the stored ciphertext and wrapped keys are
|
|
634
|
+
useless, so push contents stay private. Three limits worth knowing:
|
|
635
|
+
|
|
636
|
+
- **No protection from an active malicious operator.** Recipient public
|
|
637
|
+
keys come from `GET /devices` on that same server, unsigned and
|
|
638
|
+
unpinned. A backend that injects a device record carrying its own key
|
|
639
|
+
gets the message key wrapped for it, and reads everything. Closing
|
|
640
|
+
this needs out-of-band device verification (ADR-0007 Phase 4, not
|
|
641
|
+
built).
|
|
642
|
+
- **No forward secrecy.** The ECDH secret for a given sender/device pair
|
|
643
|
+
is static, so compromising either private key opens every past push
|
|
644
|
+
wrapped for that pair.
|
|
645
|
+
- **`senderPublicKey` is unsigned**, so a swapped one makes a push
|
|
646
|
+
undecryptable — that direction fails closed rather than leaking.
|
|
647
|
+
|
|
648
|
+
A device that has not registered a per-device public key cannot be sent
|
|
649
|
+
to; it is skipped, and if no device qualifies the push goes out in the
|
|
650
|
+
clear rather than arriving as something nothing can open.
|
|
651
|
+
|
|
652
|
+
The `zeph listener` ignores `isEncrypted` pushes for now — it does not
|
|
653
|
+
try to decrypt them. Stop-hook auto-pushes and `zeph_ask` responses are
|
|
654
|
+
not part of the `@<session>` injection path, so this doesn't affect
|
|
655
|
+
normal use.
|
|
626
656
|
|
|
627
657
|
## Requirements
|
|
628
658
|
|
package/dist/cli.js
CHANGED
|
@@ -96,6 +96,10 @@ Notify options:
|
|
|
96
96
|
--session <id> AI session ID (or set ZEPH_SESSION_ID env)
|
|
97
97
|
--auto Apply the push gate before sending (honors the
|
|
98
98
|
/zeph-quiet | /zeph-loud dial; silent exit when gated)
|
|
99
|
+
--pushmode-default <m>
|
|
100
|
+
Mode --auto assumes when the project has no dial
|
|
101
|
+
(quiet|normal|loud) [default: quiet]. A dial set with
|
|
102
|
+
/zeph-quiet | /zeph-loud | /zeph-normal always wins
|
|
99
103
|
--marker <m> Push Signal marker for --auto (skip|push|high)
|
|
100
104
|
--tools <n> Turn tool count for --auto [default: assume real work]
|
|
101
105
|
--nonreadonly <n> Non-read-only tool count for --auto
|
|
@@ -254,13 +258,14 @@ const handleNotify = async (args) => {
|
|
|
254
258
|
// GATE_DEFAULTS ("assume real work") so dumb hooks keep their historical
|
|
255
259
|
// always-push behavior in normal mode, while the /zeph-quiet | /zeph-loud
|
|
256
260
|
// dial now works for every hook-driven agent. Gated-out → silent success.
|
|
261
|
+
// With no dial the mode falls back to --pushmode-default, then to quiet.
|
|
257
262
|
if (args.auto === true) {
|
|
258
263
|
const verdict = (0, gate_js_1.decidePush)({
|
|
259
264
|
toolCount: gateCount(args.tools, gate_js_1.GATE_DEFAULTS.toolCount),
|
|
260
265
|
nonReadonlyCount: gateCount(args.nonreadonly, gate_js_1.GATE_DEFAULTS.nonReadonlyCount),
|
|
261
266
|
alreadyAsked: gate_js_1.GATE_DEFAULTS.alreadyAsked,
|
|
262
267
|
marker: (0, gate_js_1.normalizeMarker)(typeof args.marker === 'string' ? args.marker : undefined),
|
|
263
|
-
pushMode: (0, gate_js_1.
|
|
268
|
+
pushMode: (0, gate_js_1.autoPushMode)(projectDir, args[gate_js_1.PUSHMODE_DEFAULT_FLAG]),
|
|
264
269
|
});
|
|
265
270
|
if (!verdict.push)
|
|
266
271
|
return 0;
|
package/dist/crypto.d.ts
CHANGED
|
@@ -1,31 +1,31 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* Per-device encryption for the Hook SDK — self-contained ECDH P-256 +
|
|
3
3
|
* AES-256-GCM. Mirrors @zeph/crypto API but bundled inline (no external
|
|
4
4
|
* dependency). Uses Web Crypto API via node:crypto webcrypto — Node.js 18+
|
|
5
5
|
* (the `crypto` global only exists unflagged from Node 19, so we import it).
|
|
6
6
|
*
|
|
7
|
-
*
|
|
7
|
+
* How it works (ADR-0007):
|
|
8
8
|
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
* • Cross-device readability (all your devices share one keypair)
|
|
16
|
-
* What it does NOT give you:
|
|
17
|
-
* • Protection against the Zeph backend itself
|
|
18
|
-
* • Forward secrecy — encryptPushBodyForSelf / encryptFileForSelf do
|
|
19
|
-
* ECDH(self, self), which collapses to a static derived key. A single
|
|
20
|
-
* device compromise (since all your devices share the same keypair)
|
|
21
|
-
* lets the attacker decrypt every past push for which they have the
|
|
22
|
-
* ciphertext. The per-message AES key is random, but its wrap key is
|
|
23
|
-
* static, so wrapped keys are decryptable forever.
|
|
9
|
+
* This host holds its own ECDH keypair in ~/.zeph/device-keys.json. The
|
|
10
|
+
* private half is generated here and never leaves — the server only ever
|
|
11
|
+
* sees public keys. A push is encrypted once with a random AES key, and
|
|
12
|
+
* that key is wrapped separately for each of the user's registered devices
|
|
13
|
+
* using ECDH(this host, that device). Same keypair the stream frames below
|
|
14
|
+
* already used; push and file bodies now share it.
|
|
24
15
|
*
|
|
25
|
-
*
|
|
26
|
-
*
|
|
27
|
-
*
|
|
28
|
-
*
|
|
16
|
+
* What it does not give you:
|
|
17
|
+
* • Forward secrecy — the ECDH secret for a given (sender, device) pair is
|
|
18
|
+
* static, so a compromise of either private key retroactively opens every
|
|
19
|
+
* push wrapped for that pair.
|
|
20
|
+
* • Authenticity beyond the key pairing — nothing signs `senderPublicKey`.
|
|
21
|
+
*
|
|
22
|
+
* Superseded scheme: a single account-wide keypair whose private half the
|
|
23
|
+
* backend escrowed so it could sync to new devices. Key escrow was removed
|
|
24
|
+
* server-side (zeph@8a6d21b) and `GET /users/me/keys` has returned a public
|
|
25
|
+
* key only ever since. This client used to react by generating a fresh
|
|
26
|
+
* account keypair and PUTting it back — which overwrote the account public
|
|
27
|
+
* key and encrypted to a key no device held. Both that upload path and the
|
|
28
|
+
* account keypair are gone.
|
|
29
29
|
*/
|
|
30
30
|
interface EncryptedPayload {
|
|
31
31
|
ciphertext: string;
|
|
@@ -34,12 +34,40 @@ interface EncryptedPayload {
|
|
|
34
34
|
keyIv: string;
|
|
35
35
|
}
|
|
36
36
|
/**
|
|
37
|
-
* Initialize
|
|
38
|
-
*
|
|
39
|
-
*
|
|
40
|
-
*
|
|
37
|
+
* Initialize push/file encryption.
|
|
38
|
+
*
|
|
39
|
+
* Encryption turns on only when the account has explicitly opted in —
|
|
40
|
+
* `encryptionEnabled` from `GET /users/me/keys` is the single authoritative
|
|
41
|
+
* signal (ADR-0008). Server unreachable or flag off leaves it off and every
|
|
42
|
+
* send goes out in the clear.
|
|
43
|
+
*
|
|
44
|
+
* When it is on, this delegates to the per-device keypair: nothing is asked
|
|
45
|
+
* of the server but the flag, and nothing is ever uploaded.
|
|
46
|
+
*
|
|
47
|
+
* Safe to call concurrently — deduplicates to a single init.
|
|
48
|
+
* Returns this host's public key when encryption is active, '' otherwise.
|
|
41
49
|
*/
|
|
42
50
|
export declare const initCrypto: (apiKey?: string, baseUrl?: string) => Promise<string>;
|
|
51
|
+
/** One target device, as returned by `GET /devices`. */
|
|
52
|
+
export interface DeviceRecipient {
|
|
53
|
+
deviceId: string;
|
|
54
|
+
/** Base64 SPKI of that device's per-device public key. */
|
|
55
|
+
publicKey: string;
|
|
56
|
+
}
|
|
57
|
+
/** deviceId → JSON `{ encryptedKey, keyIv }`, the wire shape the clients parse. */
|
|
58
|
+
export type DeviceKeyMap = Record<string, string>;
|
|
59
|
+
/**
|
|
60
|
+
* Keep only devices this host can actually encrypt for.
|
|
61
|
+
*
|
|
62
|
+
* A device without a public key has never run a build that registers one, and
|
|
63
|
+
* a device still advertising the account-wide key has not migrated to
|
|
64
|
+
* per-device E2E — wrapping for either produces a push it cannot open, which
|
|
65
|
+
* is worse than sending plaintext it can read.
|
|
66
|
+
*/
|
|
67
|
+
export declare const selectRecipients: (devices: {
|
|
68
|
+
deviceId: string;
|
|
69
|
+
publicKey?: string;
|
|
70
|
+
}[]) => DeviceRecipient[];
|
|
43
71
|
export declare const getKeyPair: () => CryptoKeyPair | null;
|
|
44
72
|
export declare const getPublicKey: () => string | null;
|
|
45
73
|
/**
|
|
@@ -55,48 +83,29 @@ export declare const getPublicKey: () => string | null;
|
|
|
55
83
|
*/
|
|
56
84
|
export declare const disableCrypto: () => void;
|
|
57
85
|
/**
|
|
58
|
-
* Encrypt push body for
|
|
59
|
-
*
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
body?: string;
|
|
64
|
-
url?: string;
|
|
65
|
-
}, recipientPublicKeyRaw: string) => Promise<{
|
|
66
|
-
body: string;
|
|
67
|
-
encryptedKey: string;
|
|
68
|
-
senderPublicKey: string;
|
|
69
|
-
isEncrypted: true;
|
|
70
|
-
}>;
|
|
71
|
-
/**
|
|
72
|
-
* Encrypt push body for self (all own devices).
|
|
86
|
+
* Encrypt a push body for the given recipient devices.
|
|
87
|
+
*
|
|
88
|
+
* Returns the wire fields the API expects: `body` carries the ciphertext and
|
|
89
|
+
* IV, `deviceKeyMap` the per-device wrapped keys, `senderPublicKey` the half
|
|
90
|
+
* recipients need to derive the same secret back.
|
|
73
91
|
*/
|
|
74
|
-
export declare const
|
|
92
|
+
export declare const encryptPushBodyForDevices: (input: {
|
|
75
93
|
title?: string;
|
|
76
94
|
body?: string;
|
|
77
95
|
url?: string;
|
|
78
|
-
}) => Promise<{
|
|
96
|
+
}, recipients: DeviceRecipient[]) => Promise<{
|
|
79
97
|
body: string;
|
|
80
|
-
|
|
98
|
+
deviceKeyMap: DeviceKeyMap;
|
|
81
99
|
senderPublicKey: string;
|
|
82
100
|
isEncrypted: true;
|
|
83
101
|
}>;
|
|
84
102
|
/**
|
|
85
|
-
* Encrypt file content for
|
|
86
|
-
* Returns encrypted buffer + key material for file attachment metadata.
|
|
103
|
+
* Encrypt file content for the given recipient devices.
|
|
87
104
|
*/
|
|
88
|
-
export declare const
|
|
105
|
+
export declare const encryptFileForDevices: (content: string | Buffer, recipients: DeviceRecipient[]) => Promise<{
|
|
89
106
|
ciphertext: Buffer;
|
|
90
107
|
iv: string;
|
|
91
|
-
|
|
92
|
-
}>;
|
|
93
|
-
/**
|
|
94
|
-
* Encrypt file content for self (all own devices).
|
|
95
|
-
*/
|
|
96
|
-
export declare const encryptFileForSelf: (content: string) => Promise<{
|
|
97
|
-
ciphertext: Buffer;
|
|
98
|
-
iv: string;
|
|
99
|
-
encryptedKey: string;
|
|
108
|
+
deviceKeyMap: DeviceKeyMap;
|
|
100
109
|
}>;
|
|
101
110
|
/**
|
|
102
111
|
* Flat ephemeral envelope — field-for-field what the web's decrypt()
|
|
@@ -118,5 +127,21 @@ export declare const getDevicePublicKey: () => string | null;
|
|
|
118
127
|
* device. Requires initDeviceCrypto() to have completed.
|
|
119
128
|
*/
|
|
120
129
|
export declare const encryptEphemeral: (plaintext: string, recipientPublicKeyRaw: string) => Promise<EncryptedEphemeralPayload>;
|
|
130
|
+
/**
|
|
131
|
+
* Open an ephemeral envelope addressed to this device — the exact inverse of
|
|
132
|
+
* encryptEphemeral, and of the web's `encrypt` from @zeph/crypto, which
|
|
133
|
+
* produces the same five fields.
|
|
134
|
+
*
|
|
135
|
+
* Rejects rather than returning null: AES-GCM is authenticated, so a throw
|
|
136
|
+
* here means the envelope was sealed for another key, tampered with, or is not
|
|
137
|
+
* an envelope at all. Callers must treat every rejection as a refusal — there
|
|
138
|
+
* is no partial result to fall back on. Requires initDeviceCrypto().
|
|
139
|
+
*
|
|
140
|
+
* Note that opening an envelope proves only that its sender holds the private
|
|
141
|
+
* half of `senderPublicKey`; nothing signs that field, so it authenticates the
|
|
142
|
+
* key pairing and not the sender. A caller that needs to know *which* peer
|
|
143
|
+
* sent this must compare `senderPublicKey` against a key it already trusts.
|
|
144
|
+
*/
|
|
145
|
+
export declare const decryptEphemeral: (payload: EncryptedEphemeralPayload) => Promise<string>;
|
|
121
146
|
export {};
|
|
122
147
|
//# sourceMappingURL=crypto.d.ts.map
|
package/dist/crypto.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"crypto.d.ts","sourceRoot":"","sources":["../src/crypto.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AA+EH,UAAU,gBAAgB;IACxB,UAAU,EAAE,MAAM,CAAC;IACnB,EAAE,EAAE,MAAM,CAAC;IACX,YAAY,EAAE,MAAM,CAAC;IACrB,KAAK,EAAE,MAAM,CAAC;CACf;
|
|
1
|
+
{"version":3,"file":"crypto.d.ts","sourceRoot":"","sources":["../src/crypto.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AA+EH,UAAU,gBAAgB;IACxB,UAAU,EAAE,MAAM,CAAC;IACnB,EAAE,EAAE,MAAM,CAAC;IACX,YAAY,EAAE,MAAM,CAAC;IACrB,KAAK,EAAE,MAAM,CAAC;CACf;AAsED;;;;;;;;;;;;;GAaG;AACH,eAAO,MAAM,UAAU,GAAI,SAAS,MAAM,EAAE,UAAU,MAAM,KAAG,OAAO,CAAC,MAAM,CAyB5E,CAAC;AAsCF,wDAAwD;AACxD,MAAM,WAAW,eAAe;IAC9B,QAAQ,EAAE,MAAM,CAAC;IACjB,0DAA0D;IAC1D,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,mFAAmF;AACnF,MAAM,MAAM,YAAY,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;AAElD;;;;;;;GAOG;AACH,eAAO,MAAM,gBAAgB,GAC3B,SAAS;IAAE,QAAQ,EAAE,MAAM,CAAC;IAAC,SAAS,CAAC,EAAE,MAAM,CAAA;CAAE,EAAE,KAClD,eAAe,EAG8C,CAAC;AA2CjE,eAAO,MAAM,UAAU,QAAO,aAAa,GAAG,IAAkD,CAAC;AACjG,eAAO,MAAM,YAAY,QAAO,MAAM,GAAG,IAA4D,CAAC;AAEtG;;;;;;;;;;GAUG;AACH,eAAO,MAAM,aAAa,QAAO,IAEhC,CAAC;AAEF;;;;;;GAMG;AACH,eAAO,MAAM,yBAAyB,GACpC,OAAO;IAAE,KAAK,CAAC,EAAE,MAAM,CAAC;IAAC,IAAI,CAAC,EAAE,MAAM,CAAC;IAAC,GAAG,CAAC,EAAE,MAAM,CAAA;CAAE,EACtD,YAAY,eAAe,EAAE,KAC5B,OAAO,CAAC;IACT,IAAI,EAAE,MAAM,CAAC;IACb,YAAY,EAAE,YAAY,CAAC;IAC3B,eAAe,EAAE,MAAM,CAAC;IACxB,WAAW,EAAE,IAAI,CAAC;CACnB,CAkBA,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,qBAAqB,GAChC,SAAS,MAAM,GAAG,MAAM,EACxB,YAAY,eAAe,EAAE,KAC5B,OAAO,CAAC;IAAE,UAAU,EAAE,MAAM,CAAC;IAAC,EAAE,EAAE,MAAM,CAAC;IAAC,YAAY,EAAE,YAAY,CAAA;CAAE,CAsBxE,CAAC;AAcF;;;;GAIG;AACH,MAAM,WAAW,yBAA0B,SAAQ,gBAAgB;IACjE,eAAe,EAAE,MAAM,CAAC;CACzB;AAsBD;;;;GAIG;AACH,eAAO,MAAM,gBAAgB,QAAO,OAAO,CAAC,MAAM,CAoBjD,CAAC;AAEF,eAAO,MAAM,kBAAkB,QAAO,MAAM,GAAG,IAA+B,CAAC;AAE/E;;;GAGG;AACH,eAAO,MAAM,gBAAgB,GAC3B,WAAW,MAAM,EACjB,uBAAuB,MAAM,KAC5B,OAAO,CAAC,yBAAyB,CAKnC,CAAC;AAEF;;;;;;;;;;;;;;GAcG;AACH,eAAO,MAAM,gBAAgB,GAC3B,SAAS,yBAAyB,KACjC,OAAO,CAAC,MAAM,CAIhB,CAAC"}
|