@floh-solutions/pharos-cli 0.3.0 → 0.12.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 (67) hide show
  1. package/dist/cli.d.ts.map +1 -1
  2. package/dist/cli.js +392 -15
  3. package/dist/cli.js.map +1 -1
  4. package/dist/commands/attach.d.ts +30 -0
  5. package/dist/commands/attach.d.ts.map +1 -0
  6. package/dist/commands/attach.js +55 -0
  7. package/dist/commands/attach.js.map +1 -0
  8. package/dist/commands/create.d.ts +35 -0
  9. package/dist/commands/create.d.ts.map +1 -0
  10. package/dist/commands/create.js +82 -0
  11. package/dist/commands/create.js.map +1 -0
  12. package/dist/commands/detach.d.ts +34 -0
  13. package/dist/commands/detach.d.ts.map +1 -0
  14. package/dist/commands/detach.js +117 -0
  15. package/dist/commands/detach.js.map +1 -0
  16. package/dist/commands/discover.d.ts +66 -0
  17. package/dist/commands/discover.d.ts.map +1 -0
  18. package/dist/commands/discover.js +128 -0
  19. package/dist/commands/discover.js.map +1 -0
  20. package/dist/commands/hooks.d.ts +24 -0
  21. package/dist/commands/hooks.d.ts.map +1 -0
  22. package/dist/commands/hooks.js +184 -0
  23. package/dist/commands/hooks.js.map +1 -0
  24. package/dist/commands/image.d.ts +44 -0
  25. package/dist/commands/image.d.ts.map +1 -0
  26. package/dist/commands/image.js +114 -0
  27. package/dist/commands/image.js.map +1 -0
  28. package/dist/commands/import.d.ts +51 -0
  29. package/dist/commands/import.d.ts.map +1 -0
  30. package/dist/commands/import.js +190 -0
  31. package/dist/commands/import.js.map +1 -0
  32. package/dist/commands/link.d.ts +40 -0
  33. package/dist/commands/link.d.ts.map +1 -0
  34. package/dist/commands/link.js +150 -0
  35. package/dist/commands/link.js.map +1 -0
  36. package/dist/commands/people.d.ts +39 -0
  37. package/dist/commands/people.d.ts.map +1 -0
  38. package/dist/commands/people.js +64 -0
  39. package/dist/commands/people.js.map +1 -0
  40. package/dist/commands/query.d.ts +43 -0
  41. package/dist/commands/query.d.ts.map +1 -0
  42. package/dist/commands/query.js +202 -0
  43. package/dist/commands/query.js.map +1 -0
  44. package/dist/commands/recycle.d.ts +10 -0
  45. package/dist/commands/recycle.d.ts.map +1 -0
  46. package/dist/commands/recycle.js +118 -0
  47. package/dist/commands/recycle.js.map +1 -0
  48. package/dist/commands/setup.d.ts +4 -1
  49. package/dist/commands/setup.d.ts.map +1 -1
  50. package/dist/commands/setup.js +22 -21
  51. package/dist/commands/setup.js.map +1 -1
  52. package/dist/commands/update.d.ts +52 -0
  53. package/dist/commands/update.d.ts.map +1 -0
  54. package/dist/commands/update.js +181 -0
  55. package/dist/commands/update.js.map +1 -0
  56. package/dist/commands/wiki.d.ts.map +1 -1
  57. package/dist/commands/wiki.js +133 -2
  58. package/dist/commands/wiki.js.map +1 -1
  59. package/dist/output.d.ts.map +1 -1
  60. package/dist/output.js +32 -2
  61. package/dist/output.js.map +1 -1
  62. package/dist/wiql-build.d.ts +92 -0
  63. package/dist/wiql-build.d.ts.map +1 -0
  64. package/dist/wiql-build.js +147 -0
  65. package/dist/wiql-build.js.map +1 -0
  66. package/package.json +3 -3
  67. package/skill/SKILL.md +315 -65
@@ -0,0 +1,40 @@
1
+ import { type ExitCode, type Io } from "../output.js";
2
+ import type { Session } from "../session.js";
3
+ /**
4
+ * `pharos link` and `pharos unlink` — the relations between work items.
5
+ *
6
+ * **`az boards work-item relation add|remove` already exists, so this is only
7
+ * worth building for the guard** — which is the scope test (#590) applied
8
+ * honestly rather than a claim of coverage.
9
+ *
10
+ * The guard is not decoration. Azure DevOps removes a relation by its POSITION
11
+ * in the array (`/relations/3`), so an index from a stale read removes a
12
+ * DIFFERENT link, the request succeeds, and nothing anywhere says so. `unlink`
13
+ * therefore reads the item, finds the relation by identity, and applies the
14
+ * removal under a `test` op on the revision that read came from — so if the
15
+ * array moved in between, the whole patch aborts instead of cutting the wrong
16
+ * link. `ado-core`'s `removeRelationPatch` is where that lives, and it throws
17
+ * rather than returning an empty patch when the relation is absent: "already
18
+ * gone" and "your read was wrong" are different answers and a caller needs to
19
+ * know which it got.
20
+ */
21
+ /**
22
+ * The link kinds, and the direction each one means **from the item named on the
23
+ * command line**.
24
+ *
25
+ * The forward/reverse naming is Azure DevOps' and it is a reliable source of
26
+ * mistakes: on item X, a `Hierarchy-Reverse` relation points at X's PARENT.
27
+ * Naming the flags after the relationship rather than the direction is what
28
+ * stops somebody linking a child where they meant a parent.
29
+ */
30
+ export declare const LINK_KINDS: Record<string, {
31
+ rel: string;
32
+ describes: string;
33
+ }>;
34
+ export interface LinkCommandOptions {
35
+ kinds: Record<string, number | undefined>;
36
+ comment: string | undefined;
37
+ }
38
+ export declare function runLink(io: Io, session: Session, positionals: readonly string[], options: LinkCommandOptions): Promise<ExitCode>;
39
+ export declare function runUnlink(io: Io, session: Session, positionals: readonly string[], options: LinkCommandOptions): Promise<ExitCode>;
40
+ //# sourceMappingURL=link.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"link.d.ts","sourceRoot":"","sources":["../../src/commands/link.ts"],"names":[],"mappings":"AAQA,OAAO,EAAuC,KAAK,QAAQ,EAAE,KAAK,EAAE,EAAE,MAAM,cAAc,CAAC;AAC3F,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,eAAe,CAAC;AAE7C;;;;;;;;;;;;;;;;;GAiBG;AAEH;;;;;;;;GAQG;AACH,eAAO,MAAM,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE;IAAE,GAAG,EAAE,MAAM,CAAC;IAAC,SAAS,EAAE,MAAM,CAAA;CAAE,CAOzE,CAAC;AAEF,MAAM,WAAW,kBAAkB;IACjC,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC,CAAC;IAC1C,OAAO,EAAE,MAAM,GAAG,SAAS,CAAC;CAC7B;AA6BD,wBAAsB,OAAO,CAC3B,EAAE,EAAE,EAAE,EACN,OAAO,EAAE,OAAO,EAChB,WAAW,EAAE,SAAS,MAAM,EAAE,EAC9B,OAAO,EAAE,kBAAkB,GAC1B,OAAO,CAAC,QAAQ,CAAC,CAyDnB;AAED,wBAAsB,SAAS,CAC7B,EAAE,EAAE,EAAE,EACN,OAAO,EAAE,OAAO,EAChB,WAAW,EAAE,SAAS,MAAM,EAAE,EAC9B,OAAO,EAAE,kBAAkB,GAC1B,OAAO,CAAC,QAAQ,CAAC,CA+BnB"}
@@ -0,0 +1,150 @@
1
+ import { RelationNotFoundError, addRelation, removeRelationPatch, } from "@floh-solutions/ado-core";
2
+ import { emit, emitText, refusal, usageError } from "../output.js";
3
+ /**
4
+ * `pharos link` and `pharos unlink` — the relations between work items.
5
+ *
6
+ * **`az boards work-item relation add|remove` already exists, so this is only
7
+ * worth building for the guard** — which is the scope test (#590) applied
8
+ * honestly rather than a claim of coverage.
9
+ *
10
+ * The guard is not decoration. Azure DevOps removes a relation by its POSITION
11
+ * in the array (`/relations/3`), so an index from a stale read removes a
12
+ * DIFFERENT link, the request succeeds, and nothing anywhere says so. `unlink`
13
+ * therefore reads the item, finds the relation by identity, and applies the
14
+ * removal under a `test` op on the revision that read came from — so if the
15
+ * array moved in between, the whole patch aborts instead of cutting the wrong
16
+ * link. `ado-core`'s `removeRelationPatch` is where that lives, and it throws
17
+ * rather than returning an empty patch when the relation is absent: "already
18
+ * gone" and "your read was wrong" are different answers and a caller needs to
19
+ * know which it got.
20
+ */
21
+ /**
22
+ * The link kinds, and the direction each one means **from the item named on the
23
+ * command line**.
24
+ *
25
+ * The forward/reverse naming is Azure DevOps' and it is a reliable source of
26
+ * mistakes: on item X, a `Hierarchy-Reverse` relation points at X's PARENT.
27
+ * Naming the flags after the relationship rather than the direction is what
28
+ * stops somebody linking a child where they meant a parent.
29
+ */
30
+ export const LINK_KINDS = {
31
+ parent: { rel: "System.LinkTypes.Hierarchy-Reverse", describes: "the parent of" },
32
+ child: { rel: "System.LinkTypes.Hierarchy-Forward", describes: "a child of" },
33
+ related: { rel: "System.LinkTypes.Related", describes: "related to" },
34
+ predecessor: { rel: "System.LinkTypes.Dependency-Reverse", describes: "a predecessor of" },
35
+ successor: { rel: "System.LinkTypes.Dependency-Forward", describes: "a successor of" },
36
+ duplicate: { rel: "System.LinkTypes.Duplicate-Forward", describes: "a duplicate of" },
37
+ };
38
+ /** Exactly one link kind, or a refusal that lists them. */
39
+ function soleKind(kinds) {
40
+ const given = Object.entries(kinds).filter(([, id]) => id !== undefined);
41
+ if (given.length === 0) {
42
+ throw usageError(`Which relationship? One of ${Object.keys(LINK_KINDS)
43
+ .map((kind) => `--${kind} <id>`)
44
+ .join(", ")}.`);
45
+ }
46
+ if (given.length > 1) {
47
+ // Not silently applied one at a time: each is its own revision, so a
48
+ // partial failure would leave half the links made and no way to tell which.
49
+ throw usageError(`One relationship at a time; got ${given.map(([flag]) => `--${flag}`).join(" and ")}.`);
50
+ }
51
+ const [flag, target] = given[0];
52
+ return { flag, target: target };
53
+ }
54
+ function id(raw, usage) {
55
+ if (raw === undefined)
56
+ throw usageError(usage);
57
+ if (!/^\d+$/.test(raw))
58
+ throw usageError(`"${raw}" is not a work item id.`, { given: raw });
59
+ return Number(raw);
60
+ }
61
+ export async function runLink(io, session, positionals, options) {
62
+ const from = id(positionals[0], "Which work item? `pharos link <id> --parent <id>`.");
63
+ const { flag, target } = soleKind(options.kinds);
64
+ const kind = LINK_KINDS[flag];
65
+ if (target === from) {
66
+ throw usageError(`A work item cannot be ${kind.describes} itself (#${from}).`);
67
+ }
68
+ // Resolved rather than composed: `addRelation` wants the target's canonical
69
+ // url, and a target that does not exist fails here by name instead of as an
70
+ // opaque 400 about a relation.
71
+ const to = await session.client.workItems.get(target);
72
+ const url = to.url;
73
+ if (typeof url !== "string" || url === "") {
74
+ throw usageError(`Work item #${target} did not report a url, so it cannot be linked.`);
75
+ }
76
+ const item = await session.client.workItems.get(from, { expand: "relations" });
77
+ const existing = relationsOf(item).find((relation) => relation.rel === kind.rel && relation.url === url);
78
+ if (existing !== undefined) {
79
+ return report(io, session, {
80
+ applied: false,
81
+ unchanged: true,
82
+ from,
83
+ to: target,
84
+ relationship: flag,
85
+ message: `#${target} is already ${kind.describes} #${from}.`,
86
+ });
87
+ }
88
+ if (session.previewOnly) {
89
+ return report(io, session, { applied: false, dryRun: true, from, to: target, relationship: flag });
90
+ }
91
+ await session.client.workItems.updateWithRev(from, item.rev, [
92
+ addRelation(kind.rel, url, options.comment === undefined ? undefined : { comment: options.comment }),
93
+ ], { suppressNotifications: true });
94
+ return report(io, session, {
95
+ applied: true,
96
+ from,
97
+ to: target,
98
+ relationship: flag,
99
+ title: titleOf(to),
100
+ });
101
+ }
102
+ export async function runUnlink(io, session, positionals, options) {
103
+ const from = id(positionals[0], "Which work item? `pharos unlink <id> --parent <id>`.");
104
+ const { flag, target } = soleKind(options.kinds);
105
+ const kind = LINK_KINDS[flag];
106
+ const item = await session.client.workItems.get(from, { expand: "relations" });
107
+ let ops;
108
+ try {
109
+ ops = removeRelationPatch(relationsOf(item), item.rev, {
110
+ kind: "workItem",
111
+ id: target,
112
+ rel: kind.rel,
113
+ });
114
+ }
115
+ catch (error) {
116
+ if (error instanceof RelationNotFoundError) {
117
+ // Exit 3 rather than 1: nothing is wrong with the connection or the call,
118
+ // a guard here declined to act on an assumption. The message already says
119
+ // why removing by position would be worse.
120
+ throw refusal(error.message, { from, to: target, relationship: flag });
121
+ }
122
+ throw error;
123
+ }
124
+ if (session.previewOnly) {
125
+ return report(io, session, { applied: false, dryRun: true, from, to: target, relationship: flag });
126
+ }
127
+ await session.client.workItems.update(from, ops, { suppressNotifications: true });
128
+ return report(io, session, { applied: true, removed: true, from, to: target, relationship: flag });
129
+ }
130
+ function relationsOf(item) {
131
+ const relations = item.relations;
132
+ return relations ?? [];
133
+ }
134
+ function titleOf(item) {
135
+ const title = item.fields?.["System.Title"];
136
+ return typeof title === "string" ? title : null;
137
+ }
138
+ function report(io, session, result) {
139
+ if (!session.pretty)
140
+ return emit(io, result);
141
+ // The unchanged case carries its own sentence and must use it. Falling through
142
+ // to the verb table produced "#224 is would be a predecessor of #331", which
143
+ // is the kind of line that makes somebody doubt what actually happened.
144
+ if (typeof result["message"] === "string")
145
+ return emitText(io, result["message"]);
146
+ const describes = LINK_KINDS[result.relationship]?.describes ?? result.relationship;
147
+ const verb = result["removed"] === true ? "no longer" : result.applied ? "now" : "would be";
148
+ return emitText(io, `#${result.to} is ${verb} ${describes} #${result.from}`);
149
+ }
150
+ //# sourceMappingURL=link.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"link.js","sourceRoot":"","sources":["../../src/commands/link.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,qBAAqB,EACrB,WAAW,EACX,mBAAmB,GAGpB,MAAM,0BAA0B,CAAC;AAElC,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAE,OAAO,EAAE,UAAU,EAA0B,MAAM,cAAc,CAAC;AAG3F;;;;;;;;;;;;;;;;;GAiBG;AAEH;;;;;;;;GAQG;AACH,MAAM,CAAC,MAAM,UAAU,GAAuD;IAC5E,MAAM,EAAE,EAAE,GAAG,EAAE,oCAAoC,EAAE,SAAS,EAAE,eAAe,EAAE;IACjF,KAAK,EAAE,EAAE,GAAG,EAAE,oCAAoC,EAAE,SAAS,EAAE,YAAY,EAAE;IAC7E,OAAO,EAAE,EAAE,GAAG,EAAE,0BAA0B,EAAE,SAAS,EAAE,YAAY,EAAE;IACrE,WAAW,EAAE,EAAE,GAAG,EAAE,qCAAqC,EAAE,SAAS,EAAE,kBAAkB,EAAE;IAC1F,SAAS,EAAE,EAAE,GAAG,EAAE,qCAAqC,EAAE,SAAS,EAAE,gBAAgB,EAAE;IACtF,SAAS,EAAE,EAAE,GAAG,EAAE,oCAAoC,EAAE,SAAS,EAAE,gBAAgB,EAAE;CACtF,CAAC;AAOF,2DAA2D;AAC3D,SAAS,QAAQ,CAAC,KAAyC;IACzD,MAAM,KAAK,GAAG,MAAM,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,EAAE,EAAE,CAAC,EAAE,KAAK,SAAS,CAAC,CAAC;IACzE,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACvB,MAAM,UAAU,CACd,8BAA8B,MAAM,CAAC,IAAI,CAAC,UAAU,CAAC;aAClD,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,KAAK,IAAI,OAAO,CAAC;aAC/B,IAAI,CAAC,IAAI,CAAC,GAAG,CACjB,CAAC;IACJ,CAAC;IACD,IAAI,KAAK,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QACrB,qEAAqE;QACrE,4EAA4E;QAC5E,MAAM,UAAU,CACd,mCAAmC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC,EAAE,EAAE,CAAC,KAAK,IAAI,EAAE,CAAC,CAAC,IAAI,CAAC,OAAO,CAAC,GAAG,CACvF,CAAC;IACJ,CAAC;IACD,MAAM,CAAC,IAAI,EAAE,MAAM,CAAC,GAAG,KAAK,CAAC,CAAC,CAAE,CAAC;IACjC,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,MAAO,EAAE,CAAC;AACnC,CAAC;AAED,SAAS,EAAE,CAAC,GAAuB,EAAE,KAAa;IAChD,IAAI,GAAG,KAAK,SAAS;QAAE,MAAM,UAAU,CAAC,KAAK,CAAC,CAAC;IAC/C,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,GAAG,CAAC;QAAE,MAAM,UAAU,CAAC,IAAI,GAAG,0BAA0B,EAAE,EAAE,KAAK,EAAE,GAAG,EAAE,CAAC,CAAC;IAC5F,OAAO,MAAM,CAAC,GAAG,CAAC,CAAC;AACrB,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,OAAO,CAC3B,EAAM,EACN,OAAgB,EAChB,WAA8B,EAC9B,OAA2B;IAE3B,MAAM,IAAI,GAAG,EAAE,CAAC,WAAW,CAAC,CAAC,CAAC,EAAE,oDAAoD,CAAC,CAAC;IACtF,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,GAAG,QAAQ,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC;IACjD,MAAM,IAAI,GAAG,UAAU,CAAC,IAAI,CAAE,CAAC;IAE/B,IAAI,MAAM,KAAK,IAAI,EAAE,CAAC;QACpB,MAAM,UAAU,CAAC,yBAAyB,IAAI,CAAC,SAAS,aAAa,IAAI,IAAI,CAAC,CAAC;IACjF,CAAC;IAED,4EAA4E;IAC5E,4EAA4E;IAC5E,+BAA+B;IAC/B,MAAM,EAAE,GAAG,MAAM,OAAO,CAAC,MAAM,CAAC,SAAS,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;IACtD,MAAM,GAAG,GAAG,EAAE,CAAC,GAAG,CAAC;IACnB,IAAI,OAAO,GAAG,KAAK,QAAQ,IAAI,GAAG,KAAK,EAAE,EAAE,CAAC;QAC1C,MAAM,UAAU,CAAC,cAAc,MAAM,gDAAgD,CAAC,CAAC;IACzF,CAAC;IAED,MAAM,IAAI,GAAG,MAAM,OAAO,CAAC,MAAM,CAAC,SAAS,CAAC,GAAG,CAAC,IAAI,EAAE,EAAE,MAAM,EAAE,WAAW,EAAE,CAAC,CAAC;IAC/E,MAAM,QAAQ,GAAG,WAAW,CAAC,IAAI,CAAC,CAAC,IAAI,CACrC,CAAC,QAAQ,EAAE,EAAE,CAAC,QAAQ,CAAC,GAAG,KAAK,IAAI,CAAC,GAAG,IAAI,QAAQ,CAAC,GAAG,KAAK,GAAG,CAChE,CAAC;IACF,IAAI,QAAQ,KAAK,SAAS,EAAE,CAAC;QAC3B,OAAO,MAAM,CAAC,EAAE,EAAE,OAAO,EAAE;YACzB,OAAO,EAAE,KAAK;YACd,SAAS,EAAE,IAAI;YACf,IAAI;YACJ,EAAE,EAAE,MAAM;YACV,YAAY,EAAE,IAAI;YAClB,OAAO,EAAE,IAAI,MAAM,eAAe,IAAI,CAAC,SAAS,KAAK,IAAI,GAAG;SAC7D,CAAC,CAAC;IACL,CAAC;IAED,IAAI,OAAO,CAAC,WAAW,EAAE,CAAC;QACxB,OAAO,MAAM,CAAC,EAAE,EAAE,OAAO,EAAE,EAAE,OAAO,EAAE,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,IAAI,EAAE,EAAE,EAAE,MAAM,EAAE,YAAY,EAAE,IAAI,EAAE,CAAC,CAAC;IACrG,CAAC;IAED,MAAM,OAAO,CAAC,MAAM,CAAC,SAAS,CAAC,aAAa,CAC1C,IAAI,EACJ,IAAI,CAAC,GAAG,EACR;QACE,WAAW,CACT,IAAI,CAAC,GAAG,EACR,GAAG,EACH,OAAO,CAAC,OAAO,KAAK,SAAS,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,EAAE,OAAO,EAAE,OAAO,CAAC,OAAO,EAAE,CACzE;KACF,EACD,EAAE,qBAAqB,EAAE,IAAI,EAAE,CAChC,CAAC;IAEF,OAAO,MAAM,CAAC,EAAE,EAAE,OAAO,EAAE;QACzB,OAAO,EAAE,IAAI;QACb,IAAI;QACJ,EAAE,EAAE,MAAM;QACV,YAAY,EAAE,IAAI;QAClB,KAAK,EAAE,OAAO,CAAC,EAAE,CAAC;KACnB,CAAC,CAAC;AACL,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,SAAS,CAC7B,EAAM,EACN,OAAgB,EAChB,WAA8B,EAC9B,OAA2B;IAE3B,MAAM,IAAI,GAAG,EAAE,CAAC,WAAW,CAAC,CAAC,CAAC,EAAE,sDAAsD,CAAC,CAAC;IACxF,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,GAAG,QAAQ,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC;IACjD,MAAM,IAAI,GAAG,UAAU,CAAC,IAAI,CAAE,CAAC;IAE/B,MAAM,IAAI,GAAG,MAAM,OAAO,CAAC,MAAM,CAAC,SAAS,CAAC,GAAG,CAAC,IAAI,EAAE,EAAE,MAAM,EAAE,WAAW,EAAE,CAAC,CAAC;IAE/E,IAAI,GAAG,CAAC;IACR,IAAI,CAAC;QACH,GAAG,GAAG,mBAAmB,CAAC,WAAW,CAAC,IAAI,CAAC,EAAE,IAAI,CAAC,GAAG,EAAE;YACrD,IAAI,EAAE,UAAU;YAChB,EAAE,EAAE,MAAM;YACV,GAAG,EAAE,IAAI,CAAC,GAAG;SACd,CAAC,CAAC;IACL,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,IAAI,KAAK,YAAY,qBAAqB,EAAE,CAAC;YAC3C,0EAA0E;YAC1E,0EAA0E;YAC1E,2CAA2C;YAC3C,MAAM,OAAO,CAAC,KAAK,CAAC,OAAO,EAAE,EAAE,IAAI,EAAE,EAAE,EAAE,MAAM,EAAE,YAAY,EAAE,IAAI,EAAE,CAAC,CAAC;QACzE,CAAC;QACD,MAAM,KAAK,CAAC;IACd,CAAC;IAED,IAAI,OAAO,CAAC,WAAW,EAAE,CAAC;QACxB,OAAO,MAAM,CAAC,EAAE,EAAE,OAAO,EAAE,EAAE,OAAO,EAAE,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,IAAI,EAAE,EAAE,EAAE,MAAM,EAAE,YAAY,EAAE,IAAI,EAAE,CAAC,CAAC;IACrG,CAAC;IAED,MAAM,OAAO,CAAC,MAAM,CAAC,SAAS,CAAC,MAAM,CAAC,IAAI,EAAE,GAAG,EAAE,EAAE,qBAAqB,EAAE,IAAI,EAAE,CAAC,CAAC;IAElF,OAAO,MAAM,CAAC,EAAE,EAAE,OAAO,EAAE,EAAE,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,IAAI,EAAE,IAAI,EAAE,EAAE,EAAE,MAAM,EAAE,YAAY,EAAE,IAAI,EAAE,CAAC,CAAC;AACrG,CAAC;AAED,SAAS,WAAW,CAAC,IAAc;IACjC,MAAM,SAAS,GAAI,IAAsC,CAAC,SAAS,CAAC;IACpE,OAAO,SAAS,IAAI,EAAE,CAAC;AACzB,CAAC;AAED,SAAS,OAAO,CAAC,IAAc;IAC7B,MAAM,KAAK,GAAG,IAAI,CAAC,MAAM,EAAE,CAAC,cAAc,CAAC,CAAC;IAC5C,OAAO,OAAO,KAAK,KAAK,QAAQ,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,CAAC;AAClD,CAAC;AAED,SAAS,MAAM,CACb,EAAM,EACN,OAAgB,EAChB,MAAoG;IAEpG,IAAI,CAAC,OAAO,CAAC,MAAM;QAAE,OAAO,IAAI,CAAC,EAAE,EAAE,MAAM,CAAC,CAAC;IAE7C,+EAA+E;IAC/E,6EAA6E;IAC7E,wEAAwE;IACxE,IAAI,OAAO,MAAM,CAAC,SAAS,CAAC,KAAK,QAAQ;QAAE,OAAO,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC,SAAS,CAAC,CAAC,CAAC;IAElF,MAAM,SAAS,GAAG,UAAU,CAAC,MAAM,CAAC,YAAY,CAAC,EAAE,SAAS,IAAI,MAAM,CAAC,YAAY,CAAC;IACpF,MAAM,IAAI,GACR,MAAM,CAAC,SAAS,CAAC,KAAK,IAAI,CAAC,CAAC,CAAC,WAAW,CAAC,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,UAAU,CAAC;IACjF,OAAO,QAAQ,CAAC,EAAE,EAAE,IAAI,MAAM,CAAC,EAAE,OAAO,IAAI,IAAI,SAAS,KAAK,MAAM,CAAC,IAAI,EAAE,CAAC,CAAC;AAC/E,CAAC"}
@@ -0,0 +1,39 @@
1
+ import { type ExitCode, type Io } from "../output.js";
2
+ import type { Session } from "../session.js";
3
+ /**
4
+ * `pharos people [query]` — who can be mentioned, and the form that mentions
5
+ * them.
6
+ *
7
+ * ## Why this exists, and why its absence was invisible
8
+ *
9
+ * **A mention's wire form is `@<{guid}>` and NOTHING ELSE NOTIFIES.** Writing
10
+ * `@Ada Lovelace` into a comment produces text that reads exactly like a mention
11
+ * to every human who sees it, links to nobody, and sends no notification. There
12
+ * is no error and no warning — the comment posts, looks right, and quietly
13
+ * reaches no one.
14
+ *
15
+ * So a session that wanted to mention somebody had two ways to fail and no way
16
+ * to succeed: guess the guid, or write the readable form and never learn it did
17
+ * nothing. This verb is the missing half.
18
+ *
19
+ * ## Where the names come from
20
+ *
21
+ * **The board's own work items**, not an identity endpoint. That is deliberate:
22
+ * the identity APIs live on a different host (`vssps.dev.azure.com`) and want
23
+ * scopes a work-scoped PAT does not have, so a `pharos` configured exactly as
24
+ * the setup instructions say would get a 401 from a verb that looks like it
25
+ * should work. Every person who has been assigned work, created it or changed it
26
+ * is already in the items themselves, at no extra scope and no extra call
27
+ * beyond the query.
28
+ *
29
+ * The consequence is worth stating rather than discovering: **somebody who has
30
+ * never touched an item on this board will not be listed.** For mentioning
31
+ * people about work, that is nearly always the right set — and when it is not,
32
+ * the guid from any Azure DevOps url works just as well in `@<…>`.
33
+ */
34
+ export interface PeopleCommandOptions {
35
+ /** Cap the items scanned. More items, more people, slower. */
36
+ top: number | undefined;
37
+ }
38
+ export declare function runPeople(io: Io, session: Session, positionals: readonly string[], options: PeopleCommandOptions): Promise<ExitCode>;
39
+ //# sourceMappingURL=people.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"people.d.ts","sourceRoot":"","sources":["../../src/commands/people.ts"],"names":[],"mappings":"AAAA,OAAO,EAAkB,KAAK,QAAQ,EAAE,KAAK,EAAE,EAAE,MAAM,cAAc,CAAC;AACtE,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,eAAe,CAAC;AAE7C;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BG;AAEH,MAAM,WAAW,oBAAoB;IACnC,8DAA8D;IAC9D,GAAG,EAAE,MAAM,GAAG,SAAS,CAAC;CACzB;AAYD,wBAAsB,SAAS,CAC7B,EAAE,EAAE,EAAE,EACN,OAAO,EAAE,OAAO,EAChB,WAAW,EAAE,SAAS,MAAM,EAAE,EAC9B,OAAO,EAAE,oBAAoB,GAC5B,OAAO,CAAC,QAAQ,CAAC,CAmDnB"}
@@ -0,0 +1,64 @@
1
+ import { emit, emitText } from "../output.js";
2
+ export async function runPeople(io, session, positionals, options) {
3
+ const query = positionals[0]?.trim().toLowerCase() ?? "";
4
+ const top = options.top ?? 200;
5
+ // Every item, including finished work: a person who closed everything they
6
+ // ever had is still a person worth mentioning.
7
+ const ids = await session.client.wiql.queryIds(`SELECT [System.Id] FROM WorkItems WHERE [System.TeamProject] = @project `
8
+ + `ORDER BY [System.ChangedDate] DESC`);
9
+ const wanted = ids.slice(0, top);
10
+ const found = new Map();
11
+ if (wanted.length > 0) {
12
+ const items = await session.client.workItems.getBatch(wanted, {
13
+ fields: ["System.AssignedTo", "System.CreatedBy", "System.ChangedBy"],
14
+ });
15
+ for (const item of items) {
16
+ record(found, item.fields?.["System.AssignedTo"], "assigned");
17
+ record(found, item.fields?.["System.CreatedBy"], "created");
18
+ record(found, item.fields?.["System.ChangedBy"], "changed");
19
+ }
20
+ }
21
+ const people = [...found.values()]
22
+ .filter((person) => query === ""
23
+ || person.displayName.toLowerCase().includes(query)
24
+ || person.uniqueName.toLowerCase().includes(query))
25
+ .sort((a, b) => a.displayName.localeCompare(b.displayName));
26
+ if (session.pretty) {
27
+ if (people.length === 0) {
28
+ emitText(io, query === "" ? "Nobody on this board yet." : `Nobody matching "${query}".`);
29
+ return 0;
30
+ }
31
+ for (const person of people) {
32
+ emitText(io, `${person.mention} ${person.displayName} <${person.uniqueName}>`);
33
+ }
34
+ emitText(io, "");
35
+ emitText(io, "Paste the @<…> form into a comment or a description. The readable form "
36
+ + "(@Name) notifies nobody.");
37
+ return 0;
38
+ }
39
+ emit(io, { scanned: wanted.length, people });
40
+ return 0;
41
+ }
42
+ /** One identity field, folded into the set. */
43
+ function record(into, value, seenAs) {
44
+ if (value === null || typeof value !== "object")
45
+ return;
46
+ const identity = value;
47
+ const id = identity.id;
48
+ if (id === undefined || id === "")
49
+ return;
50
+ const existing = into.get(id);
51
+ if (existing !== undefined) {
52
+ if (!existing.seenAs.includes(seenAs))
53
+ existing.seenAs.push(seenAs);
54
+ return;
55
+ }
56
+ into.set(id, {
57
+ displayName: identity.displayName ?? id,
58
+ uniqueName: identity.uniqueName ?? "",
59
+ id,
60
+ mention: `@<${id}>`,
61
+ seenAs: [seenAs],
62
+ });
63
+ }
64
+ //# sourceMappingURL=people.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"people.js","sourceRoot":"","sources":["../../src/commands/people.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,43 @@
1
+ import { type ExitCode, type Io } from "../output.js";
2
+ import type { Session } from "../session.js";
3
+ /**
4
+ * `pharos query` — the verb whose absence made every session start with `curl`.
5
+ *
6
+ * A real session was asked what was assigned to @user. It spent 1m16s and five
7
+ * calls — two on `--help`, one checking environment variables setup had already
8
+ * written, two more on per-command help — and then gave up on this CLI and hit
9
+ * the WIQL endpoint directly, because `task` takes one id and nothing here
10
+ * answered "what is assigned to me". That is the most common opening question
11
+ * anybody asks a board.
12
+ *
13
+ * ## Why the output is hydrated items rather than ids
14
+ *
15
+ * WIQL returns **references only** — ids, never field values, however many
16
+ * columns the SELECT names. The curl recipe this replaces therefore handed back
17
+ * sixteen bare numbers, which is not an answer to any question somebody asked.
18
+ * `queryAndFetch` runs the query and then hydrates through `workitemsbatch` in
19
+ * pages of 200; both routes are read-shaped POSTs and neither counts against
20
+ * the write budget.
21
+ *
22
+ * ## Why it says how it decided "open"
23
+ *
24
+ * Because the answer is uninterpretable without it. "12 open" means nothing
25
+ * until you know which states were counted as finished, and Azure DevOps lets a
26
+ * process template rename every one of them. See {@link Openness}.
27
+ */
28
+ export interface QueryCommandOptions {
29
+ mine: boolean;
30
+ assignee: string | undefined;
31
+ states: readonly string[];
32
+ types: readonly string[];
33
+ sprint: string | undefined;
34
+ area: string | undefined;
35
+ tags: readonly string[];
36
+ all: boolean;
37
+ team: string | undefined;
38
+ top: number | undefined;
39
+ /** The escape hatch: a statement written by hand, used verbatim. */
40
+ wiql: string | undefined;
41
+ }
42
+ export declare function runQuery(io: Io, session: Session, options: QueryCommandOptions): Promise<ExitCode>;
43
+ //# sourceMappingURL=query.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"query.d.ts","sourceRoot":"","sources":["../../src/commands/query.ts"],"names":[],"mappings":"AAEA,OAAO,EAA8B,KAAK,QAAQ,EAAE,KAAK,EAAE,EAAE,MAAM,cAAc,CAAC;AAClF,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,eAAe,CAAC;AAG7C;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AAEH,MAAM,WAAW,mBAAmB;IAClC,IAAI,EAAE,OAAO,CAAC;IACd,QAAQ,EAAE,MAAM,GAAG,SAAS,CAAC;IAC7B,MAAM,EAAE,SAAS,MAAM,EAAE,CAAC;IAC1B,KAAK,EAAE,SAAS,MAAM,EAAE,CAAC;IACzB,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC;IAC3B,IAAI,EAAE,MAAM,GAAG,SAAS,CAAC;IACzB,IAAI,EAAE,SAAS,MAAM,EAAE,CAAC;IACxB,GAAG,EAAE,OAAO,CAAC;IACb,IAAI,EAAE,MAAM,GAAG,SAAS,CAAC;IACzB,GAAG,EAAE,MAAM,GAAG,SAAS,CAAC;IACxB,oEAAoE;IACpE,IAAI,EAAE,MAAM,GAAG,SAAS,CAAC;CAC1B;AAgBD,wBAAsB,QAAQ,CAC5B,EAAE,EAAE,EAAE,EACN,OAAO,EAAE,OAAO,EAChB,OAAO,EAAE,mBAAmB,GAC3B,OAAO,CAAC,QAAQ,CAAC,CAsEnB"}
@@ -0,0 +1,202 @@
1
+ import { authenticatedIdentity } from "@floh-solutions/ado-core";
2
+ import { emit, emitText, usageError } from "../output.js";
3
+ import { buildWiql, opennessFor } from "../wiql-build.js";
4
+ /** What the hydrate step asks for. Anything a type does not have is simply absent. */
5
+ const FIELDS = [
6
+ "System.Id",
7
+ "System.WorkItemType",
8
+ "System.Title",
9
+ "System.State",
10
+ "System.AssignedTo",
11
+ "System.IterationPath",
12
+ "System.AreaPath",
13
+ "System.Tags",
14
+ "System.ChangedDate",
15
+ "Microsoft.VSTS.Common.Priority",
16
+ ];
17
+ export async function runQuery(io, session, options) {
18
+ if (options.mine && options.assignee !== undefined) {
19
+ throw usageError("Pass either --mine or --assignee, not both — they filter the same field and would AND "
20
+ + "together into a query that can never match.");
21
+ }
22
+ const filters = {
23
+ mine: options.mine,
24
+ assignee: options.assignee,
25
+ states: options.states,
26
+ types: options.types,
27
+ sprint: options.sprint,
28
+ area: options.area,
29
+ tags: options.tags,
30
+ all: options.all,
31
+ ...(session.client.project === undefined ? {} : { project: session.client.project }),
32
+ };
33
+ let statement;
34
+ let openness;
35
+ if (options.wiql !== undefined) {
36
+ if (describesFilters(options)) {
37
+ throw usageError("--wiql is the whole statement, so the other filters would be silently ignored. Pass "
38
+ + "--wiql alone, or drop it and use the flags.");
39
+ }
40
+ statement = options.wiql;
41
+ openness = { source: "none", terminal: [], note: "Your statement, used verbatim." };
42
+ }
43
+ else {
44
+ // Only read the catalogue when the answer depends on it. `--all` and an
45
+ // explicit `--state` both decide openness without asking the project, and a
46
+ // request nobody needs is a request that can fail for nobody's benefit.
47
+ let catalog;
48
+ let failure;
49
+ if (!options.all && options.states.length === 0) {
50
+ try {
51
+ catalog = await session.client.workItemTypes.states();
52
+ }
53
+ catch (error) {
54
+ // NOT fatal. A token without the work-item-type read still queries fine;
55
+ // it just cannot be told what "finished" means here, and saying so and
56
+ // guessing beats refusing to answer at all.
57
+ failure = error instanceof Error ? error.message : String(error);
58
+ }
59
+ }
60
+ openness = opennessFor(filters, catalog, failure);
61
+ statement = buildWiql(filters, openness, catalog);
62
+ }
63
+ const items = await session.client.wiql.queryAndFetch(statement, {
64
+ fields: FIELDS,
65
+ ...(options.top === undefined ? {} : { top: options.top }),
66
+ ...(options.team === undefined ? {} : { team: options.team }),
67
+ });
68
+ const result = {
69
+ count: items.length,
70
+ ...(items.length === 0 ? { emptyBecause: silentFilters(options) } : {}),
71
+ // Emitted so a caller can see exactly what ran — and edit it into --wiql
72
+ // when the flags do not reach far enough.
73
+ wiql: statement,
74
+ openness,
75
+ assignedTo: options.mine ? await resolveMe(session) : (options.assignee ?? null),
76
+ items: items.map(summarise),
77
+ };
78
+ return session.pretty ? emitText(io, render(result)) : emit(io, result);
79
+ }
80
+ /**
81
+ * Which filters could have emptied this result **without anybody being told**.
82
+ *
83
+ * An empty list and exit 0 is a successful query that matched nothing, and a
84
+ * session reports it as a fact about the board. That is right for most filters
85
+ * and dangerous for the ones Azure DevOps does not validate:
86
+ *
87
+ * - `--sprint` and `--area` are SAFE and it is worth saying so, because it is
88
+ * the opposite of what you would guess: a path that does not exist answers
89
+ * `TF51011` naming the path, so a wrong one fails loudly. Measured.
90
+ * - `--assignee` is the silent one. An identity Azure DevOps has never heard of
91
+ * is not an error — it simply matches nothing, so "0 items assigned to
92
+ * ada@contoso.com" and "you spelt it wrong" are the same output.
93
+ * - `--tag` is a substring match against a semicolon-joined string, so a
94
+ * plausible-looking tag that is not on anything reads the same way.
95
+ * - `--state` is checked on `update` but not here, because a query naming
96
+ * several states across several types is legitimate.
97
+ *
98
+ * Named rather than counted: telling somebody "this might be wrong" without
99
+ * saying which flag is the one to re-read is a warning they cannot act on.
100
+ */
101
+ function silentFilters(options) {
102
+ const suspects = [];
103
+ if (options.assignee !== undefined) {
104
+ suspects.push(`--assignee "${options.assignee}" — Azure DevOps does not reject an identity it does not `
105
+ + "know; it matches nothing. `pharos whoami` shows the form your own identity takes.");
106
+ }
107
+ for (const tag of options.tags) {
108
+ suspects.push(`--tag "${tag}" — a substring match; no error if nothing carries it.`);
109
+ }
110
+ for (const state of options.states) {
111
+ suspects.push(`--state "${state}" — run \`pharos types\` to see what each type accepts.`);
112
+ }
113
+ return suspects.length === 0 ? undefined : suspects;
114
+ }
115
+ /** Whether anything other than `--wiql` was asked for. */
116
+ function describesFilters(options) {
117
+ return (options.mine
118
+ || options.assignee !== undefined
119
+ || options.states.length > 0
120
+ || options.types.length > 0
121
+ || options.tags.length > 0
122
+ || options.sprint !== undefined
123
+ || options.area !== undefined);
124
+ }
125
+ /**
126
+ * Who `@Me` actually was.
127
+ *
128
+ * The macro resolves server-side against whoever owns `ADO_PAT`, which is not
129
+ * necessarily the person reading the output — a shared or service token makes
130
+ * "assigned to me" quietly mean somebody else. One GET, and it never fails the
131
+ * query: an unnameable identity is reported as such.
132
+ */
133
+ async function resolveMe(session) {
134
+ try {
135
+ const identity = await authenticatedIdentity(session.client.http);
136
+ return identity.displayName ?? identity.uniqueName ?? "@Me (unnamed)";
137
+ }
138
+ catch {
139
+ return "@Me — whoever owns ADO_PAT; the identity could not be read";
140
+ }
141
+ }
142
+ function summarise(item) {
143
+ const fields = item.fields ?? {};
144
+ const text = (name) => {
145
+ const value = fields[name];
146
+ return typeof value === "string" && value !== "" ? value : undefined;
147
+ };
148
+ const tags = text("System.Tags");
149
+ const priority = fields["Microsoft.VSTS.Common.Priority"];
150
+ return {
151
+ id: item.id,
152
+ type: text("System.WorkItemType"),
153
+ title: text("System.Title"),
154
+ state: text("System.State"),
155
+ assignedTo: displayName(fields["System.AssignedTo"]),
156
+ iteration: text("System.IterationPath"),
157
+ tags: tags === undefined ? [] : tags.split(";").map((tag) => tag.trim()).filter((tag) => tag !== ""),
158
+ priority: typeof priority === "number" ? priority : undefined,
159
+ changed: text("System.ChangedDate"),
160
+ };
161
+ }
162
+ /** `null` rather than absent: unassigned is an answer, and a common one. */
163
+ function displayName(value) {
164
+ if (typeof value === "string" && value !== "")
165
+ return value;
166
+ if (typeof value === "object" && value !== null) {
167
+ const display = value.displayName;
168
+ if (typeof display === "string")
169
+ return display;
170
+ }
171
+ return null;
172
+ }
173
+ /**
174
+ * The human rendering.
175
+ *
176
+ * The openness note is at the BOTTOM rather than the top: it is the thing to
177
+ * check when the list looks wrong, and putting a paragraph of caveat above the
178
+ * answer buries the answer.
179
+ */
180
+ function render(result) {
181
+ const lines = [];
182
+ const who = result.assignedTo === null ? "" : ` assigned to ${result.assignedTo}`;
183
+ lines.push(`${result.count} work item${result.count === 1 ? "" : "s"}${who}`, "");
184
+ if (result.items.length === 0) {
185
+ lines.push(" (nothing matched — this is a successful query, not a failure)");
186
+ for (const suspect of result.emptyBecause ?? [])
187
+ lines.push(` ${suspect}`);
188
+ }
189
+ for (const item of result.items) {
190
+ const assignee = item.assignedTo ?? "unassigned";
191
+ lines.push(` #${String(item.id ?? "?").padEnd(6)} ${(item.type ?? "").padEnd(10)} `
192
+ + `${(item.state ?? "").padEnd(12)} ${(item.title ?? "(untitled)").slice(0, 60).padEnd(60)} ${assignee}`);
193
+ }
194
+ if (result.openness.note !== undefined) {
195
+ lines.push("", `openness: ${result.openness.note}`);
196
+ }
197
+ if (result.openness.terminal.length > 0) {
198
+ lines.push(`treated as finished: ${result.openness.terminal.join(", ")}`);
199
+ }
200
+ return lines.join("\n");
201
+ }
202
+ //# sourceMappingURL=query.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"query.js","sourceRoot":"","sources":["../../src/commands/query.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,10 @@
1
+ import { type ExitCode, type Io } from "../output.js";
2
+ import { type Session } from "../session.js";
3
+ export declare function runDelete(io: Io, session: Session, positionals: readonly string[]): Promise<ExitCode>;
4
+ export declare function runRestore(io: Io, session: Session, positionals: readonly string[]): Promise<ExitCode>;
5
+ export interface DeletedCommandOptions {
6
+ /** How many to name. The bin is not usually small. */
7
+ top: number;
8
+ }
9
+ export declare function runDeleted(io: Io, session: Session, options: DeletedCommandOptions): Promise<ExitCode>;
10
+ //# sourceMappingURL=recycle.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"recycle.d.ts","sourceRoot":"","sources":["../../src/commands/recycle.ts"],"names":[],"mappings":"AAAA,OAAO,EAA8B,KAAK,QAAQ,EAAE,KAAK,EAAE,EAAE,MAAM,cAAc,CAAC;AAClF,OAAO,EAAW,KAAK,OAAO,EAAE,MAAM,eAAe,CAAC;AA0CtD,wBAAsB,SAAS,CAC7B,EAAE,EAAE,EAAE,EACN,OAAO,EAAE,OAAO,EAChB,WAAW,EAAE,SAAS,MAAM,EAAE,GAC7B,OAAO,CAAC,QAAQ,CAAC,CA8BnB;AAED,wBAAsB,UAAU,CAC9B,EAAE,EAAE,EAAE,EACN,OAAO,EAAE,OAAO,EAChB,WAAW,EAAE,SAAS,MAAM,EAAE,GAC7B,OAAO,CAAC,QAAQ,CAAC,CAYnB;AAED,MAAM,WAAW,qBAAqB;IACpC,sDAAsD;IACtD,GAAG,EAAE,MAAM,CAAC;CACb;AAED,wBAAsB,UAAU,CAC9B,EAAE,EAAE,EAAE,EACN,OAAO,EAAE,OAAO,EAChB,OAAO,EAAE,qBAAqB,GAC7B,OAAO,CAAC,QAAQ,CAAC,CAyCnB"}