@floh-solutions/pharos-cli 0.24.0 → 0.26.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 (53) hide show
  1. package/README.md +97 -10
  2. package/dist/adopt-type.d.ts +58 -0
  3. package/dist/adopt-type.d.ts.map +1 -0
  4. package/dist/adopt-type.js +61 -0
  5. package/dist/adopt-type.js.map +1 -0
  6. package/dist/bridge.d.ts +156 -0
  7. package/dist/bridge.d.ts.map +1 -0
  8. package/dist/bridge.js +157 -0
  9. package/dist/bridge.js.map +1 -0
  10. package/dist/cli.d.ts +1 -1
  11. package/dist/cli.d.ts.map +1 -1
  12. package/dist/cli.js +176 -31
  13. package/dist/cli.js.map +1 -1
  14. package/dist/commands/bridge.d.ts +30 -0
  15. package/dist/commands/bridge.d.ts.map +1 -0
  16. package/dist/commands/bridge.js +255 -0
  17. package/dist/commands/bridge.js.map +1 -0
  18. package/dist/commands/comment-github.d.ts +138 -0
  19. package/dist/commands/comment-github.d.ts.map +1 -0
  20. package/dist/commands/comment-github.js +427 -0
  21. package/dist/commands/comment-github.js.map +1 -0
  22. package/dist/commands/comment.d.ts.map +1 -1
  23. package/dist/commands/comment.js +114 -19
  24. package/dist/commands/comment.js.map +1 -1
  25. package/dist/commands/doctor.d.ts +169 -0
  26. package/dist/commands/doctor.d.ts.map +1 -1
  27. package/dist/commands/doctor.js +241 -44
  28. package/dist/commands/doctor.js.map +1 -1
  29. package/dist/commands/issue.d.ts +133 -0
  30. package/dist/commands/issue.d.ts.map +1 -1
  31. package/dist/commands/issue.js +292 -110
  32. package/dist/commands/issue.js.map +1 -1
  33. package/dist/commands/setup.d.ts +45 -0
  34. package/dist/commands/setup.d.ts.map +1 -1
  35. package/dist/commands/setup.js +74 -0
  36. package/dist/commands/setup.js.map +1 -1
  37. package/dist/half-link.d.ts +14 -4
  38. package/dist/half-link.d.ts.map +1 -1
  39. package/dist/half-link.js +14 -4
  40. package/dist/half-link.js.map +1 -1
  41. package/dist/output.d.ts.map +1 -1
  42. package/dist/output.js +123 -1
  43. package/dist/output.js.map +1 -1
  44. package/dist/session.d.ts +51 -2
  45. package/dist/session.d.ts.map +1 -1
  46. package/dist/session.js +62 -11
  47. package/dist/session.js.map +1 -1
  48. package/dist/target.d.ts +40 -4
  49. package/dist/target.d.ts.map +1 -1
  50. package/dist/target.js +14 -3
  51. package/dist/target.js.map +1 -1
  52. package/package.json +4 -4
  53. package/skill/SKILL.md +167 -7
@@ -0,0 +1,255 @@
1
+ import { WorkItemTypesApi, selectableTypes, typeForCategory } from "@floh-solutions/ado-core";
2
+ import { CATEGORY, brokenMappings, categoryFor, defaultBridgePath, digestPolicy, emptyBridgeConfig, formatBridgeConfig, parseBridgeConfig, repoConfig, scopeKey, } from "../bridge.js";
3
+ import { emit } from "../output.js";
4
+ /**
5
+ * `pharos bridge` — **the mapping, and the file the app reads through these
6
+ * verbs rather than around them** (#861).
7
+ *
8
+ * ## Why the app does not read the file itself
9
+ *
10
+ * The rule `pharos-convert` and `doctor` already follow, and the one AGENTS.md
11
+ * restates every time it is broken: if the app parses the mapping and the CLI
12
+ * parses the mapping, that is two answers to one question, and the one that
13
+ * drifts is the one nobody is watching. `adopt` in a headless session would
14
+ * quietly file to a different type than the same gesture in the GUI.
15
+ *
16
+ * So this is the engine and the settings page is a door. Every verb takes
17
+ * `--json` with stable keys, because a GUI that has to parse English to know
18
+ * what happened is a GUI that will eventually be wrong.
19
+ */
20
+ export async function runBridge(io, session, positionals, options = {}) {
21
+ const verb = positionals[0] ?? "show";
22
+ const env = options.env ?? process.env;
23
+ const path = defaultBridgePath(env);
24
+ const organization = session.client.organization;
25
+ const project = session.client.project ?? "";
26
+ switch (verb) {
27
+ case "show":
28
+ return show(io, session, path, organization, project, env);
29
+ case "labels":
30
+ return labels(io, session, path, organization, project, options, env);
31
+ case "map":
32
+ return map(io, session, path, organization, project, options, env);
33
+ case "unmap":
34
+ return unmap(io, path, organization, project, options, env);
35
+ case "digest":
36
+ return digest(io, path, organization, project, options, env);
37
+ default:
38
+ emit(io, {
39
+ ok: false,
40
+ error: {
41
+ kind: "usage",
42
+ message: `Unknown "bridge ${verb}". Try show, labels, map, unmap or digest.`,
43
+ commands: ["show", "labels", "map", "unmap", "digest"],
44
+ },
45
+ });
46
+ return 2;
47
+ }
48
+ }
49
+ async function readConfig(path) {
50
+ try {
51
+ const { readFile } = await import("node:fs/promises");
52
+ return parseBridgeConfig(await readFile(path, "utf8"), path);
53
+ }
54
+ catch (error) {
55
+ // A file that is not there yet is the ordinary state. One that cannot be
56
+ // parsed is NOT swallowed here, unlike on the adopt path — somebody running
57
+ // `bridge` is asking about the file itself, and hiding a syntax error from
58
+ // them is how they end up editing a file the tool is ignoring.
59
+ if (error.code === "ENOENT")
60
+ return emptyBridgeConfig();
61
+ throw error;
62
+ }
63
+ }
64
+ async function writeConfig(path, config) {
65
+ const { mkdir, writeFile } = await import("node:fs/promises");
66
+ const { dirname } = await import("node:path");
67
+ await mkdir(dirname(path), { recursive: true });
68
+ await writeFile(path, formatBridgeConfig(config), "utf8");
69
+ }
70
+ async function show(io, session, path, organization, project, _env) {
71
+ const config = await readConfig(path);
72
+ const scope = config.scopes[scopeKey(organization, project)];
73
+ // What the PROJECT says its types are, which is the half no file can hold.
74
+ let types = [];
75
+ let categories = {};
76
+ let unreachable;
77
+ try {
78
+ const api = new WorkItemTypesApi(session.client.http);
79
+ const catalog = await api.categories();
80
+ types = selectableTypes(await api.list(), catalog);
81
+ categories = Object.fromEntries(catalog.defaults);
82
+ }
83
+ catch (error) {
84
+ unreachable = error.message;
85
+ }
86
+ emit(io, {
87
+ ok: true,
88
+ file: path,
89
+ scope: `${organization}/${project}`,
90
+ repos: scope?.repos ?? {},
91
+ digest: digestPolicy(config, organization, project),
92
+ // The project's own answer, beside the mapping, because a category is
93
+ // meaningless without it — `Microsoft.BugCategory` is `Issue` here and
94
+ // `Bug` on the next board.
95
+ project: { types, categories, ...(unreachable === undefined ? {} : { unreachable }) },
96
+ });
97
+ return 0;
98
+ }
99
+ async function labels(io, session, path, organization, project, options, _env) {
100
+ const repo = options.repo;
101
+ if (repo === undefined) {
102
+ emit(io, {
103
+ ok: false,
104
+ error: { kind: "usage", message: "`bridge labels` needs --repo owner/name." },
105
+ });
106
+ return 2;
107
+ }
108
+ const config = await readConfig(path);
109
+ const entry = repoConfig(config, organization, project, repo);
110
+ const github = await session.github();
111
+ // Reached AS the account bound to it, never as whichever one `gh` has active
112
+ // — the rule AGENTS.md states about `--user`, one layer up.
113
+ const live = await github.repos.labels(github.binding(repo));
114
+ const rows = live.map((label) => {
115
+ const mapped = entry.labels[String(label.id)];
116
+ const choice = categoryFor([{ id: label.id ?? -1, name: label.name }], entry);
117
+ return {
118
+ id: label.id ?? null,
119
+ name: label.name,
120
+ // Three different facts, and a GUI needs to tell them apart: this is
121
+ // mapped, this is what an unmapped one would default to, and this is the
122
+ // repository's catch-all.
123
+ mapped: mapped?.category ?? null,
124
+ wouldUse: choice.categories,
125
+ source: choice.source,
126
+ };
127
+ });
128
+ emit(io, {
129
+ ok: true,
130
+ repo,
131
+ labels: rows,
132
+ defaultCategory: entry.defaultCategory,
133
+ // A mapping whose label is gone: reported, never pruned. Delete-and-recreate
134
+ // yields a NEW id, so what replaced it is somebody's decision to confirm.
135
+ broken: brokenMappings(entry, live.map((l) => ({ id: l.id ?? -1, name: l.name }))),
136
+ });
137
+ return 0;
138
+ }
139
+ async function map(io, session, path, organization, project, options, _env) {
140
+ const { repo, label, category } = options;
141
+ if (repo === undefined || label === undefined || category === undefined) {
142
+ emit(io, {
143
+ ok: false,
144
+ error: {
145
+ kind: "usage",
146
+ message: "`bridge map` needs --repo owner/name --label <id or name> --category <reference>",
147
+ categories: Object.values(CATEGORY),
148
+ },
149
+ });
150
+ return 2;
151
+ }
152
+ const config = await readConfig(path);
153
+ const key = scopeKey(organization, project);
154
+ const scope = config.scopes[key] ?? { repos: {}, digest: { onAdopt: true, onClose: true } };
155
+ const repoKey = repo.toLowerCase();
156
+ const entry = scope.repos[repoKey] ?? {
157
+ labels: {},
158
+ defaultCategory: CATEGORY.requirement,
159
+ };
160
+ // **Keyed on the id.** A name is accepted for convenience and resolved to one
161
+ // here rather than stored, because a mapping stored under a name stops
162
+ // resolving the moment somebody renames the label — which is the thing this
163
+ // whole design is about.
164
+ const id = /^\d+$/.test(label) ? label : undefined;
165
+ if (id === undefined) {
166
+ emit(io, {
167
+ ok: false,
168
+ error: {
169
+ kind: "usage",
170
+ message: `--label must be a label ID, not a name. Run \`pharos bridge labels --repo ${repo}\` `
171
+ + "to see them: a mapping keyed on a name stops resolving when the label is renamed, "
172
+ + "which is exactly what keying on the id avoids.",
173
+ },
174
+ });
175
+ return 2;
176
+ }
177
+ // **The NAME is looked up, never taken from the flag.** `--label` is an id,
178
+ // so storing it as the name would put a number in the display field and the
179
+ // settings page would show `11710323621` where a person expects `bug`. It is
180
+ // display-only either way — nothing matches on it — but a field whose whole
181
+ // job is to be readable has to be readable.
182
+ let name = label;
183
+ try {
184
+ const github = await session.github();
185
+ const live = await github.repos.labels(github.binding(repo));
186
+ name = live.find((candidate) => String(candidate.id) === id)?.name ?? label;
187
+ }
188
+ catch {
189
+ // Offline, or a repository this machine cannot reach. The mapping still
190
+ // saves — it is keyed on the id and works without the name — and the name
191
+ // fills in the next time `labels` is run.
192
+ }
193
+ entry.labels[id] = { name, category };
194
+ scope.repos[repoKey] = entry;
195
+ config.scopes[key] = scope;
196
+ await writeConfig(path, config);
197
+ emit(io, { ok: true, applied: true, repo, label: id, category, file: path });
198
+ return 0;
199
+ }
200
+ async function unmap(io, path, organization, project, options, _env) {
201
+ const { repo, label } = options;
202
+ if (repo === undefined || label === undefined) {
203
+ emit(io, {
204
+ ok: false,
205
+ error: { kind: "usage", message: "`bridge unmap` needs --repo owner/name --label <id>" },
206
+ });
207
+ return 2;
208
+ }
209
+ const config = await readConfig(path);
210
+ const key = scopeKey(organization, project);
211
+ const entry = config.scopes[key]?.repos[repo.toLowerCase()];
212
+ const had = entry !== undefined && entry.labels[label] !== undefined;
213
+ if (entry !== undefined)
214
+ delete entry.labels[label];
215
+ await writeConfig(path, config);
216
+ // `removed: false` rather than an error: unmapping something already unmapped
217
+ // is the state somebody wanted, and failing it would make the verb unsafe to
218
+ // run twice.
219
+ emit(io, { ok: true, applied: true, removed: had, repo, label, file: path });
220
+ return 0;
221
+ }
222
+ async function digest(io, path, organization, project, options, _env) {
223
+ const config = await readConfig(path);
224
+ const key = scopeKey(organization, project);
225
+ const scope = config.scopes[key] ?? { repos: {}, digest: { onAdopt: true, onClose: true } };
226
+ // **`--manual` is how it gets turned OFF**, and it exists because a boolean
227
+ // flag can only say true: `--on-close` cannot express "stop doing that", so
228
+ // without this the policy was write-only. `--manual` clears both, and either
229
+ // flag after it turns that one back on — so `--manual --on-adopt` is "on
230
+ // adopt only", which is the arrangement somebody actually asks for.
231
+ let changed = false;
232
+ if (options.manual === true) {
233
+ scope.digest = { onAdopt: false, onClose: false };
234
+ changed = true;
235
+ }
236
+ if (options.onAdopt !== undefined) {
237
+ scope.digest.onAdopt = options.onAdopt;
238
+ changed = true;
239
+ }
240
+ if (options.onClose !== undefined) {
241
+ scope.digest.onClose = options.onClose;
242
+ changed = true;
243
+ }
244
+ config.scopes[key] = scope;
245
+ if (changed)
246
+ await writeConfig(path, config);
247
+ emit(io, { ok: true, digest: scope.digest, file: path });
248
+ return 0;
249
+ }
250
+ /** Resolve a category against a live project — shared with `adopt`'s reporting. */
251
+ export async function categoryToType(session, ...wanted) {
252
+ const catalog = await new WorkItemTypesApi(session.client.http).categories();
253
+ return typeForCategory(catalog, ...wanted);
254
+ }
255
+ //# sourceMappingURL=bridge.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"bridge.js","sourceRoot":"","sources":["../../src/commands/bridge.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,138 @@
1
+ import { type GitHubHideReason, type IssueCoordinate } from "@floh-solutions/gh-core";
2
+ import { type CliError, type ExitCode, type Io } from "../output.js";
3
+ import { type Session } from "../session.js";
4
+ import { type AdoTarget } from "../target.js";
5
+ /**
6
+ * `pharos comment …` on a GitHub issue — the half `gh-core` grew for #827.
7
+ *
8
+ * A sibling of the two Azure DevOps arms in `comment.ts`, not a second command:
9
+ * the verbs, the target grammar and the output shape are that file's, and this
10
+ * is the branch that knows what they mean on GitHub. Read its header first.
11
+ *
12
+ * ## What each verb costs, because an agent should be able to predict it
13
+ *
14
+ * | verb | requests | door |
15
+ * |---|---|---|
16
+ * | `list` | 1 per 100 comments | GraphQL |
17
+ * | `edit` | 2 — the read the marker guard needs, then the PATCH | REST |
18
+ * | `delete` | 2 — read to name the consequence, then the DELETE | REST |
19
+ * | `react` | 1 | REST |
20
+ * | `unreact` | 2 — `DELETE` names the reaction ROW id, so it lists first | REST |
21
+ * | `reactors` | 1 | REST |
22
+ * | `hide` / `unhide` / `pin` / `unpin` | 1, or 2 given a numeric id | GraphQL |
23
+ *
24
+ * The second request on `hide` and friends is the node-id lookup, and it
25
+ * disappears when the node id from `list` is passed back — see `handle()` in
26
+ * `comment.ts`.
27
+ *
28
+ * ## The account is never guessed
29
+ *
30
+ * Every call resolves its binding from `repos.json` through
31
+ * `GitHubClient.binding`, which throws naming the file when a repo is unbound.
32
+ * Plan §2: with more than one `gh` account signed in, letting anything else
33
+ * choose returns a perfectly good 200 from the wrong repository.
34
+ */
35
+ /** A comment named by either of its two ids. See `handle()` in `comment.ts`. */
36
+ export type GitHubCommentHandle = {
37
+ kind: "rest";
38
+ id: number;
39
+ } | {
40
+ kind: "node";
41
+ nodeId: string;
42
+ };
43
+ /**
44
+ * Every comment on an issue, with the state and the permissions REST cannot
45
+ * report.
46
+ *
47
+ * **This verb is why the rest of them are usable at all.** Before it, nothing
48
+ * in this CLI could tell an agent a GitHub comment id: `pharos issue say`
49
+ * returns the id of the comment it just posted and that was the entire surface.
50
+ * The app never needed a list because it has a window. An agent has this.
51
+ *
52
+ * The `viewerCan…` flags are the part worth having. For a window they grey out
53
+ * a menu item; for an agent they turn a write that would 403 into a fact known
54
+ * before the write, which is the difference between a failed run and a run that
55
+ * reports what it may do.
56
+ */
57
+ export declare function githubList(io: Io, session: Session, issue: IssueCoordinate): Promise<ExitCode>;
58
+ /**
59
+ * There is no `pharos comment add` on a GitHub issue, and there should not be.
60
+ *
61
+ * Plan §8 — do not rebuild `gh`. Two verbs already cover posting, and they
62
+ * cover different jobs: `gh issue comment` writes to GitHub, and
63
+ * `pharos issue say` writes the same message to GitHub *and* a summary to the
64
+ * work item, which is the whole reason the edge exists. A third door here would
65
+ * post to an adopted issue without the board ever hearing, which is exactly the
66
+ * asymmetry `say` was built to prevent — and it would do it under a verb whose
67
+ * Azure DevOps spelling *does* reach the board.
68
+ *
69
+ * Refused rather than silently absent: an agent that tries this has a real
70
+ * intention, and the useful answer names which of the two it wanted.
71
+ */
72
+ export declare function githubRefuseAdd(issue: IssueCoordinate): never;
73
+ /**
74
+ * Edit a comment, behind `gh-core`'s marker guard.
75
+ *
76
+ * The guard is in the package rather than here on purpose — it is a rule about
77
+ * the link record, not about this command, and the macOS app writes through the
78
+ * same rule when it edits. What this arm adds is the reporting: a restored
79
+ * trailer is stated in the output rather than happening quietly.
80
+ */
81
+ export declare function githubEdit(io: Io, session: Session, issue: IssueCoordinate, handle: GitHubCommentHandle, text: string): Promise<ExitCode>;
82
+ /**
83
+ * Delete a comment — and name what that costs before doing it.
84
+ *
85
+ * `approve()` already gates every destructive verb behind `--yes`. What is
86
+ * added here is that the preview knows whether this particular comment is the
87
+ * one carrying the `pharos:v1` trailer, because deleting **that** comment
88
+ * destroys the GitHub half of the link: `linksFromComments` reads it, so after
89
+ * this the pair is one-sided and `pharos issue drift` reports it — correctly,
90
+ * and after the fact.
91
+ *
92
+ * So the comment is read *before* the gate, including under `--dry-run`. A
93
+ * preview that could not say which of the two deletes this is would be a
94
+ * preview of the wrong thing.
95
+ *
96
+ * It is not refused outright. `--yes` is the caller's decision and there are
97
+ * good reasons to remove a link comment — a mis-adoption, a wrong repository.
98
+ * The repair is named instead.
99
+ */
100
+ export declare function githubRemove(io: Io, session: Session, issue: IssueCoordinate, handle: GitHubCommentHandle): Promise<ExitCode>;
101
+ export declare function githubReact(io: Io, session: Session, issue: IssueCoordinate, handle: GitHubCommentHandle, rawReaction: string | undefined, engaged: boolean): Promise<ExitCode>;
102
+ /** A count is not a name — "three people reacted" needs this second call. */
103
+ export declare function githubReactors(io: Io, session: Session, issue: IssueCoordinate, handle: GitHubCommentHandle, rawReaction: string | undefined): Promise<ExitCode>;
104
+ /**
105
+ * Hide or unhide, over GraphQL. There is no REST route — `POST …/minimize`
106
+ * answers 404.
107
+ *
108
+ * The reason is read back and reported as GitHub spells it, which is **not**
109
+ * how it was sent: `OFF_TOPIC` goes out and `off-topic` comes back. The CLI
110
+ * takes the display spelling for exactly that reason — ask for `off-topic`,
111
+ * read `off-topic`, and nothing has to know about the asymmetry.
112
+ */
113
+ export declare function githubHide(io: Io, session: Session, issue: IssueCoordinate, handle: GitHubCommentHandle, reason: GitHubHideReason | undefined): Promise<ExitCode>;
114
+ /**
115
+ * Pin or unpin, over GraphQL. No REST route exists at all.
116
+ *
117
+ * The input field is `issueCommentId`, not `subjectId` as minimize takes —
118
+ * handled in `gh-core`, noted here because the two mutations sit beside each
119
+ * other in GitHub's schema and the wrong one is an HTTP 200 that does nothing.
120
+ */
121
+ export declare function githubPin(io: Io, session: Session, issue: IssueCoordinate, handle: GitHubCommentHandle, pinned: boolean): Promise<ExitCode>;
122
+ /**
123
+ * Azure DevOps has no hide and no pin — not "not implemented here", **no such
124
+ * concept in the API**.
125
+ *
126
+ * Said plainly rather than reported as an unknown verb, because those are
127
+ * different facts: an unknown verb reads as a gap this CLI could close, and
128
+ * somebody would then go looking for the endpoint.
129
+ *
130
+ * **Returns the error rather than throwing it**, so the call site reads
131
+ * `if (target.kind !== "githubIssue") throw noSuchThingOnAzureDevOps(…)` and the
132
+ * compiler narrows the target on the next line. A function that threw would
133
+ * leave every GitHub-only arm needing a second refusal for a case that cannot
134
+ * happen — which is how an impossible branch acquires code nobody tests.
135
+ */
136
+ export declare function noSuchThingOnAzureDevOps(target: AdoTarget, verb: string): CliError;
137
+ export declare function hideReasonArgument(raw: string | undefined): GitHubHideReason;
138
+ //# sourceMappingURL=comment-github.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"comment-github.d.ts","sourceRoot":"","sources":["../../src/commands/comment-github.ts"],"names":[],"mappings":"AAAA,OAAO,EAUL,KAAK,gBAAgB,EACrB,KAAK,eAAe,EAErB,MAAM,yBAAyB,CAAC;AAEjC,OAAO,EAA6B,KAAK,QAAQ,EAAE,KAAK,QAAQ,EAAE,KAAK,EAAE,EAAE,MAAM,cAAc,CAAC;AAChG,OAAO,EAAW,KAAK,OAAO,EAAE,MAAM,eAAe,CAAC;AACtD,OAAO,EAAkB,KAAK,SAAS,EAAE,MAAM,cAAc,CAAC;AAE9D;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AAEH,gFAAgF;AAChF,MAAM,MAAM,mBAAmB,GAC3B;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,EAAE,EAAE,MAAM,CAAA;CAAE,GAC5B;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAAC;AAIrC;;;;;;;;;;;;;GAaG;AACH,wBAAsB,UAAU,CAC9B,EAAE,EAAE,EAAE,EACN,OAAO,EAAE,OAAO,EAChB,KAAK,EAAE,eAAe,GACrB,OAAO,CAAC,QAAQ,CAAC,CAsDnB;AAID;;;;;;;;;;;;;GAaG;AACH,wBAAgB,eAAe,CAAC,KAAK,EAAE,eAAe,GAAG,KAAK,CAW7D;AAID;;;;;;;GAOG;AACH,wBAAsB,UAAU,CAC9B,EAAE,EAAE,EAAE,EACN,OAAO,EAAE,OAAO,EAChB,KAAK,EAAE,eAAe,EACtB,MAAM,EAAE,mBAAmB,EAC3B,IAAI,EAAE,MAAM,GACX,OAAO,CAAC,QAAQ,CAAC,CAkCnB;AAID;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAsB,YAAY,CAChC,EAAE,EAAE,EAAE,EACN,OAAO,EAAE,OAAO,EAChB,KAAK,EAAE,eAAe,EACtB,MAAM,EAAE,mBAAmB,GAC1B,OAAO,CAAC,QAAQ,CAAC,CA+FnB;AAID,wBAAsB,WAAW,CAC/B,EAAE,EAAE,EAAE,EACN,OAAO,EAAE,OAAO,EAChB,KAAK,EAAE,eAAe,EACtB,MAAM,EAAE,mBAAmB,EAC3B,WAAW,EAAE,MAAM,GAAG,SAAS,EAC/B,OAAO,EAAE,OAAO,GACf,OAAO,CAAC,QAAQ,CAAC,CAqBnB;AAED,6EAA6E;AAC7E,wBAAsB,cAAc,CAClC,EAAE,EAAE,EAAE,EACN,OAAO,EAAE,OAAO,EAChB,KAAK,EAAE,eAAe,EACtB,MAAM,EAAE,mBAAmB,EAC3B,WAAW,EAAE,MAAM,GAAG,SAAS,GAC9B,OAAO,CAAC,QAAQ,CAAC,CA0BnB;AAID;;;;;;;;GAQG;AACH,wBAAsB,UAAU,CAC9B,EAAE,EAAE,EAAE,EACN,OAAO,EAAE,OAAO,EAChB,KAAK,EAAE,eAAe,EACtB,MAAM,EAAE,mBAAmB,EAC3B,MAAM,EAAE,gBAAgB,GAAG,SAAS,GACnC,OAAO,CAAC,QAAQ,CAAC,CAgBnB;AAED;;;;;;GAMG;AACH,wBAAsB,SAAS,CAC7B,EAAE,EAAE,EAAE,EACN,OAAO,EAAE,OAAO,EAChB,KAAK,EAAE,eAAe,EACtB,MAAM,EAAE,mBAAmB,EAC3B,MAAM,EAAE,OAAO,GACd,OAAO,CAAC,QAAQ,CAAC,CAYnB;AAID;;;;;;;;;;;;;GAaG;AACH,wBAAgB,wBAAwB,CAAC,MAAM,EAAE,SAAS,EAAE,IAAI,EAAE,MAAM,GAAG,QAAQ,CAOlF;AAED,wBAAgB,kBAAkB,CAAC,GAAG,EAAE,MAAM,GAAG,SAAS,GAAG,gBAAgB,CAe5E"}