litmus-cli 1.4.6 → 1.4.7-eng1502.1
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 +15 -0
- package/dist/commands/connect.d.ts.map +1 -1
- package/dist/commands/connect.js +2 -20
- package/dist/commands/connect.js.map +1 -1
- package/dist/commands/init.d.ts +3 -1
- package/dist/commands/init.d.ts.map +1 -1
- package/dist/commands/init.js +74 -10
- package/dist/commands/init.js.map +1 -1
- package/dist/commands/submit.d.ts.map +1 -1
- package/dist/commands/submit.js +66 -4
- package/dist/commands/submit.js.map +1 -1
- package/dist/index.js +3 -2
- package/dist/index.js.map +1 -1
- package/dist/lib/config.d.ts +2 -0
- package/dist/lib/config.d.ts.map +1 -1
- package/dist/lib/config.js.map +1 -1
- package/dist/lib/editor.d.ts +73 -0
- package/dist/lib/editor.d.ts.map +1 -0
- package/dist/lib/editor.js +222 -0
- package/dist/lib/editor.js.map +1 -0
- package/dist/lib/open-url.d.ts +9 -0
- package/dist/lib/open-url.d.ts.map +1 -0
- package/dist/lib/open-url.js +30 -0
- package/dist/lib/open-url.js.map +1 -0
- package/dist/lib/select.d.ts +64 -0
- package/dist/lib/select.d.ts.map +1 -0
- package/dist/lib/select.js +170 -0
- package/dist/lib/select.js.map +1 -0
- package/dist/lib/session.d.ts +85 -0
- package/dist/lib/session.d.ts.map +1 -0
- package/dist/lib/session.js +284 -0
- package/dist/lib/session.js.map +1 -0
- package/package.json +1 -1
package/dist/lib/config.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../../src/lib/config.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../../src/lib/config.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAA;AAE3C,MAAM,WAAW,YAAY;IAC3B,YAAY,EAAE,MAAM,CAAA;IACpB,cAAc,EAAE,MAAM,CAAA;IACtB,cAAc,EAAE,MAAM,CAAA;IACtB,aAAa,EAAE,MAAM,CAAA;IACrB,KAAK,EAAE,MAAM,CAAA;IACb,SAAS,EAAE,MAAM,CAAA;IACjB,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAA;IACvB,SAAS,EAAE,MAAM,GAAG,IAAI,CAAA;IACxB,wBAAwB,CAAC,EAAE,MAAM,CAAA;IACjC,OAAO,EAAE,MAAM,CAAA;IACf,UAAU,CAAC,EAAE,MAAM,CAAA;IAOnB,UAAU,CAAC,EAAE,MAAM,CAAA;IAInB,MAAM,CAAC,EAAE,QAAQ,CAAA;CAClB;AAED;;;GAGG;AACH,wBAAgB,oBAAoB,CAAC,MAAM,EAAE,IAAI,CAAC,YAAY,EAAE,UAAU,GAAG,WAAW,GAAG,WAAW,CAAC,GAAG,MAAM,GAAG,IAAI,CAStH;AAKD,wBAAsB,WAAW,CAC/B,WAAW,EAAE,MAAM,EACnB,MAAM,EAAE,YAAY,GACnB,OAAO,CAAC,IAAI,CAAC,CAcf;AAED;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,oBAAoB,CAClC,IAAI,EAAE;IAAE,SAAS,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IAAC,SAAS,EAAE,MAAM,GAAG,IAAI,CAAA;CAAE,EAC9E,KAAK,EAAE,MAAM,GACZ,MAAM,GAAG,IAAI,CASf;AAED;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAgB,eAAe,CAC7B,OAAO,EAAE,YAAY,EACrB,KAAK,EAAE;IAAE,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IAAC,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IAAC,SAAS,EAAE,MAAM,GAAG,IAAI,CAAA;CAAE,EACtF,KAAK,EAAE,MAAM,GACZ;IAAE,IAAI,EAAE,SAAS,CAAC;IAAC,IAAI,EAAE,YAAY,CAAA;CAAE,GAAG;IAAE,IAAI,EAAE,SAAS,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,GAAG,IAAI,CAsBtF;AAED;;;GAGG;AACH,wBAAgB,eAAe,IAAI,MAAM,GAAG,IAAI,CAe/C;AAED;;;GAGG;AACH,wBAAsB,UAAU,IAAI,OAAO,CAAC,YAAY,GAAG,IAAI,CAAC,CAwB/D"}
|
package/dist/lib/config.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"config.js","sourceRoot":"","sources":["../../src/lib/config.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,SAAS,EAAE,KAAK,EAAE,MAAM,EAAE,MAAM,aAAa,CAAA;AAChE,OAAO,EAAE,UAAU,EAAE,MAAM,IAAI,CAAA;AAC/B,OAAO,IAAI,MAAM,MAAM,CAAA;AACvB,OAAO,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAA;
|
|
1
|
+
{"version":3,"file":"config.js","sourceRoot":"","sources":["../../src/lib/config.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Opening — and later closing — the candidate's editor on their own machine.
|
|
3
|
+
*
|
|
4
|
+
* `litmus init` offers the editors it can actually launch and opens the
|
|
5
|
+
* assessment in the chosen one; `litmus submit` closes that window again once
|
|
6
|
+
* the work is in, so the agent chat session that was open against the
|
|
7
|
+
* assessment ends with the assessment instead of outliving it.
|
|
8
|
+
*
|
|
9
|
+
* Closing is best-effort and SILENT. Neither VS Code nor Cursor exposes a
|
|
10
|
+
* "close this window" command, so every route here is an OS window-manager
|
|
11
|
+
* trick that can legitimately be unavailable: macOS gates UI scripting behind
|
|
12
|
+
* Accessibility permission, and a Linux box may have neither wmctrl nor
|
|
13
|
+
* xdotool. A candidate who has just submitted should not be handed a warning
|
|
14
|
+
* about a window they can close themselves, so a failure produces no output at
|
|
15
|
+
* all.
|
|
16
|
+
*/
|
|
17
|
+
export type EditorId = "vscode" | "cursor";
|
|
18
|
+
interface EditorSpec {
|
|
19
|
+
id: EditorId;
|
|
20
|
+
label: string;
|
|
21
|
+
/** Command the editor installs on PATH ("Shell Command: Install ... in PATH"). */
|
|
22
|
+
cli: string;
|
|
23
|
+
/** Bundle name under /Applications on macOS. */
|
|
24
|
+
macApp: string;
|
|
25
|
+
/** Process name as System Events sees it (NOT the bundle name for VS Code). */
|
|
26
|
+
macProcess: string;
|
|
27
|
+
/** Process name for Get-Process on Windows, and the WM_CLASS prefix on Linux. */
|
|
28
|
+
processName: string;
|
|
29
|
+
}
|
|
30
|
+
export interface DetectedEditor {
|
|
31
|
+
spec: EditorSpec;
|
|
32
|
+
/** "cli" launches through the editor's own PATH command; "app" is the macOS
|
|
33
|
+
* bundle fallback for candidates who never ran "Install 'code' in PATH". */
|
|
34
|
+
how: "cli" | "app";
|
|
35
|
+
appPath?: string;
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* The editors we can actually open on this machine, in menu order. An editor
|
|
39
|
+
* the candidate has installed but never added to PATH still shows up on macOS
|
|
40
|
+
* (we open the bundle directly) — that gap is common enough with Cursor that
|
|
41
|
+
* omitting it would hide the option most candidates want.
|
|
42
|
+
*/
|
|
43
|
+
export declare function detectEditors(): DetectedEditor[];
|
|
44
|
+
/**
|
|
45
|
+
* Open `dir` in the chosen editor. Resolves false only when the launcher itself
|
|
46
|
+
* is missing — spawn reports that asynchronously via 'error', so we give it a
|
|
47
|
+
* short grace period rather than reporting success and printing a line that
|
|
48
|
+
* turns out to be a lie. The editor is detached: it outlives the CLI.
|
|
49
|
+
*/
|
|
50
|
+
export declare function openInEditor(editor: DetectedEditor, dir: string): Promise<boolean>;
|
|
51
|
+
/**
|
|
52
|
+
* Close every window of `process` whose title mentions `folderName`, by
|
|
53
|
+
* pressing its close button through the Accessibility API. Editor window titles
|
|
54
|
+
* carry the workspace folder ("index.ts — acme-backend"), so this hits the
|
|
55
|
+
* assessment window and leaves the candidate's other projects alone.
|
|
56
|
+
*
|
|
57
|
+
* Exported for tests: the escaping is the part worth pinning.
|
|
58
|
+
*/
|
|
59
|
+
export declare function macCloseScript(processName: string, folderName: string): string;
|
|
60
|
+
/**
|
|
61
|
+
* Windows equivalent. `.Contains` rather than `-like '*x*'` so a folder name
|
|
62
|
+
* with a wildcard character in it can't widen the match. CloseMainWindow is the
|
|
63
|
+
* polite close — an editor with unsaved work still gets to prompt.
|
|
64
|
+
*/
|
|
65
|
+
export declare function winCloseScript(processName: string, folderName: string): string;
|
|
66
|
+
/**
|
|
67
|
+
* Best-effort, silent close of the assessment's editor window. Every failure
|
|
68
|
+
* mode — no permission, no window manager, editor already quit, window renamed
|
|
69
|
+
* — is a no-op with no output.
|
|
70
|
+
*/
|
|
71
|
+
export declare function closeEditorWindow(editorId: EditorId, folderName: string): void;
|
|
72
|
+
export {};
|
|
73
|
+
//# sourceMappingURL=editor.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"editor.d.ts","sourceRoot":"","sources":["../../src/lib/editor.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAOH,MAAM,MAAM,QAAQ,GAAG,QAAQ,GAAG,QAAQ,CAAA;AAE1C,UAAU,UAAU;IAClB,EAAE,EAAE,QAAQ,CAAA;IACZ,KAAK,EAAE,MAAM,CAAA;IACb,kFAAkF;IAClF,GAAG,EAAE,MAAM,CAAA;IACX,gDAAgD;IAChD,MAAM,EAAE,MAAM,CAAA;IACd,+EAA+E;IAC/E,UAAU,EAAE,MAAM,CAAA;IAClB,iFAAiF;IACjF,WAAW,EAAE,MAAM,CAAA;CACpB;AAOD,MAAM,WAAW,cAAc;IAC7B,IAAI,EAAE,UAAU,CAAA;IAChB;iFAC6E;IAC7E,GAAG,EAAE,KAAK,GAAG,KAAK,CAAA;IAClB,OAAO,CAAC,EAAE,MAAM,CAAA;CACjB;AA0BD;;;;;GAKG;AACH,wBAAgB,aAAa,IAAI,cAAc,EAAE,CAWhD;AAED;;;;;GAKG;AACH,wBAAgB,YAAY,CAAC,MAAM,EAAE,cAAc,EAAE,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAqBlF;AAYD;;;;;;;GAOG;AACH,wBAAgB,cAAc,CAAC,WAAW,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,MAAM,CAe9E;AAED;;;;GAIG;AACH,wBAAgB,cAAc,CAAC,WAAW,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,MAAM,CAQ9E;AA8CD;;;;GAIG;AACH,wBAAgB,iBAAiB,CAAC,QAAQ,EAAE,QAAQ,EAAE,UAAU,EAAE,MAAM,GAAG,IAAI,CAwB9E"}
|
|
@@ -0,0 +1,222 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Opening — and later closing — the candidate's editor on their own machine.
|
|
3
|
+
*
|
|
4
|
+
* `litmus init` offers the editors it can actually launch and opens the
|
|
5
|
+
* assessment in the chosen one; `litmus submit` closes that window again once
|
|
6
|
+
* the work is in, so the agent chat session that was open against the
|
|
7
|
+
* assessment ends with the assessment instead of outliving it.
|
|
8
|
+
*
|
|
9
|
+
* Closing is best-effort and SILENT. Neither VS Code nor Cursor exposes a
|
|
10
|
+
* "close this window" command, so every route here is an OS window-manager
|
|
11
|
+
* trick that can legitimately be unavailable: macOS gates UI scripting behind
|
|
12
|
+
* Accessibility permission, and a Linux box may have neither wmctrl nor
|
|
13
|
+
* xdotool. A candidate who has just submitted should not be handed a warning
|
|
14
|
+
* about a window they can close themselves, so a failure produces no output at
|
|
15
|
+
* all.
|
|
16
|
+
*/
|
|
17
|
+
import os from "os";
|
|
18
|
+
import path from "path";
|
|
19
|
+
import { existsSync } from "fs";
|
|
20
|
+
import { execFileSync, spawn } from "child_process";
|
|
21
|
+
const EDITORS = [
|
|
22
|
+
{ id: "vscode", label: "VS Code", cli: "code", macApp: "Visual Studio Code", macProcess: "Code", processName: "Code" },
|
|
23
|
+
{ id: "cursor", label: "Cursor", cli: "cursor", macApp: "Cursor", macProcess: "Cursor", processName: "Cursor" },
|
|
24
|
+
];
|
|
25
|
+
function onPath(cmd) {
|
|
26
|
+
try {
|
|
27
|
+
if (process.platform === "win32") {
|
|
28
|
+
execFileSync("where.exe", [cmd], { stdio: "ignore", timeout: 5000 });
|
|
29
|
+
}
|
|
30
|
+
else {
|
|
31
|
+
// `command -v` rather than `which`: POSIX, and present on the minimal
|
|
32
|
+
// shells some candidates run. cmd is a literal from EDITORS, never input.
|
|
33
|
+
execFileSync("/bin/sh", ["-c", `command -v ${cmd}`], { stdio: "ignore", timeout: 5000 });
|
|
34
|
+
}
|
|
35
|
+
return true;
|
|
36
|
+
}
|
|
37
|
+
catch {
|
|
38
|
+
return false;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
function macAppPath(bundleName) {
|
|
42
|
+
if (process.platform !== "darwin")
|
|
43
|
+
return null;
|
|
44
|
+
for (const base of ["/Applications", path.join(os.homedir(), "Applications")]) {
|
|
45
|
+
const p = path.join(base, `${bundleName}.app`);
|
|
46
|
+
if (existsSync(p))
|
|
47
|
+
return p;
|
|
48
|
+
}
|
|
49
|
+
return null;
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* The editors we can actually open on this machine, in menu order. An editor
|
|
53
|
+
* the candidate has installed but never added to PATH still shows up on macOS
|
|
54
|
+
* (we open the bundle directly) — that gap is common enough with Cursor that
|
|
55
|
+
* omitting it would hide the option most candidates want.
|
|
56
|
+
*/
|
|
57
|
+
export function detectEditors() {
|
|
58
|
+
const found = [];
|
|
59
|
+
for (const spec of EDITORS) {
|
|
60
|
+
if (onPath(spec.cli)) {
|
|
61
|
+
found.push({ spec, how: "cli" });
|
|
62
|
+
continue;
|
|
63
|
+
}
|
|
64
|
+
const appPath = macAppPath(spec.macApp);
|
|
65
|
+
if (appPath)
|
|
66
|
+
found.push({ spec, how: "app", appPath });
|
|
67
|
+
}
|
|
68
|
+
return found;
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Open `dir` in the chosen editor. Resolves false only when the launcher itself
|
|
72
|
+
* is missing — spawn reports that asynchronously via 'error', so we give it a
|
|
73
|
+
* short grace period rather than reporting success and printing a line that
|
|
74
|
+
* turns out to be a lie. The editor is detached: it outlives the CLI.
|
|
75
|
+
*/
|
|
76
|
+
export function openInEditor(editor, dir) {
|
|
77
|
+
const [cmd, args] = editor.how === "cli" ? [editor.spec.cli, [dir]] : ["open", ["-a", editor.appPath, dir]];
|
|
78
|
+
return new Promise((resolve) => {
|
|
79
|
+
let settled = false;
|
|
80
|
+
const done = (ok) => {
|
|
81
|
+
if (!settled) {
|
|
82
|
+
settled = true;
|
|
83
|
+
resolve(ok);
|
|
84
|
+
}
|
|
85
|
+
};
|
|
86
|
+
try {
|
|
87
|
+
const child = spawn(cmd, args, { detached: true, stdio: "ignore" });
|
|
88
|
+
child.on("error", () => done(false));
|
|
89
|
+
child.unref();
|
|
90
|
+
setTimeout(() => done(true), 400).unref();
|
|
91
|
+
}
|
|
92
|
+
catch {
|
|
93
|
+
done(false);
|
|
94
|
+
}
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
/** AppleScript string literal escaping (backslash, then quote). */
|
|
98
|
+
function escapeAppleScript(s) {
|
|
99
|
+
return s.replace(/\\/g, "\\\\").replace(/"/g, '\\"');
|
|
100
|
+
}
|
|
101
|
+
/** PowerShell single-quoted string escaping (double the quote). */
|
|
102
|
+
function escapePowerShell(s) {
|
|
103
|
+
return s.replace(/'/g, "''");
|
|
104
|
+
}
|
|
105
|
+
/**
|
|
106
|
+
* Close every window of `process` whose title mentions `folderName`, by
|
|
107
|
+
* pressing its close button through the Accessibility API. Editor window titles
|
|
108
|
+
* carry the workspace folder ("index.ts — acme-backend"), so this hits the
|
|
109
|
+
* assessment window and leaves the candidate's other projects alone.
|
|
110
|
+
*
|
|
111
|
+
* Exported for tests: the escaping is the part worth pinning.
|
|
112
|
+
*/
|
|
113
|
+
export function macCloseScript(processName, folderName) {
|
|
114
|
+
const proc = escapeAppleScript(processName);
|
|
115
|
+
const folder = escapeAppleScript(folderName);
|
|
116
|
+
return [
|
|
117
|
+
'tell application "System Events"',
|
|
118
|
+
` if not (exists process "${proc}") then return`,
|
|
119
|
+
` tell process "${proc}"`,
|
|
120
|
+
` repeat with w in (every window whose name contains "${folder}")`,
|
|
121
|
+
" try",
|
|
122
|
+
' perform action "AXPress" of (first button of w whose subrole is "AXCloseButton")',
|
|
123
|
+
" end try",
|
|
124
|
+
" end repeat",
|
|
125
|
+
" end tell",
|
|
126
|
+
"end tell",
|
|
127
|
+
].join("\n");
|
|
128
|
+
}
|
|
129
|
+
/**
|
|
130
|
+
* Windows equivalent. `.Contains` rather than `-like '*x*'` so a folder name
|
|
131
|
+
* with a wildcard character in it can't widen the match. CloseMainWindow is the
|
|
132
|
+
* polite close — an editor with unsaved work still gets to prompt.
|
|
133
|
+
*/
|
|
134
|
+
export function winCloseScript(processName, folderName) {
|
|
135
|
+
const proc = escapePowerShell(processName);
|
|
136
|
+
const folder = escapePowerShell(folderName);
|
|
137
|
+
return (`Get-Process -Name '${proc}' -ErrorAction SilentlyContinue | ` +
|
|
138
|
+
`Where-Object { $_.MainWindowTitle -and $_.MainWindowTitle.Contains('${folder}') } | ` +
|
|
139
|
+
`ForEach-Object { $_.CloseMainWindow() | Out-Null }`);
|
|
140
|
+
}
|
|
141
|
+
/**
|
|
142
|
+
* Launch a close attempt and walk away.
|
|
143
|
+
*
|
|
144
|
+
* MEASURED, and the reason this is detached rather than a sync call: the first
|
|
145
|
+
* UI-scripting attempt on macOS blocks until the candidate answers a system
|
|
146
|
+
* permission dialog — a probe with a 10s cap timed out rather than returning.
|
|
147
|
+
* Waiting on that would freeze `litmus submit` at the exact moment the
|
|
148
|
+
* candidate has been told they are done. Detached, the attempt outlives us and
|
|
149
|
+
* either lands or doesn't; either way submit keeps moving. A new process group
|
|
150
|
+
* also means the hang-up submit sends to the assessment shell moments later
|
|
151
|
+
* doesn't take this with it.
|
|
152
|
+
*/
|
|
153
|
+
function fireAndForget(command, args) {
|
|
154
|
+
try {
|
|
155
|
+
const child = spawn(command, args, { detached: true, stdio: "ignore" });
|
|
156
|
+
child.on("error", () => {
|
|
157
|
+
// Launcher missing (no osascript, no powershell) — nothing to say.
|
|
158
|
+
});
|
|
159
|
+
child.unref();
|
|
160
|
+
}
|
|
161
|
+
catch {
|
|
162
|
+
// Silent by design — see the file header.
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
/** Linux: ask the window manager, if the candidate has wmctrl. */
|
|
166
|
+
function linuxCloseWindow(processName, folderName) {
|
|
167
|
+
// -lx adds WM_CLASS ("code.Code", "cursor.Cursor"), so we can require both the
|
|
168
|
+
// right app and the right title before closing anything.
|
|
169
|
+
const listing = execFileSync("wmctrl", ["-lx"], { encoding: "utf8", timeout: 3000 });
|
|
170
|
+
const wantClass = processName.toLowerCase();
|
|
171
|
+
for (const line of listing.split("\n")) {
|
|
172
|
+
const m = line.match(/^(0x[0-9a-fA-F]+)\s+\S+\s+(\S+)\s+\S+\s+(.*)$/);
|
|
173
|
+
if (!m)
|
|
174
|
+
continue;
|
|
175
|
+
const [, winId, wmClass, title] = m;
|
|
176
|
+
if (!wmClass.toLowerCase().includes(wantClass))
|
|
177
|
+
continue;
|
|
178
|
+
if (!title.includes(folderName))
|
|
179
|
+
continue;
|
|
180
|
+
try {
|
|
181
|
+
execFileSync("wmctrl", ["-ic", winId], { stdio: "ignore", timeout: 5000 });
|
|
182
|
+
}
|
|
183
|
+
catch {
|
|
184
|
+
// one window refusing to close doesn't stop the rest
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
/**
|
|
189
|
+
* Best-effort, silent close of the assessment's editor window. Every failure
|
|
190
|
+
* mode — no permission, no window manager, editor already quit, window renamed
|
|
191
|
+
* — is a no-op with no output.
|
|
192
|
+
*/
|
|
193
|
+
export function closeEditorWindow(editorId, folderName) {
|
|
194
|
+
const spec = EDITORS.find((e) => e.id === editorId);
|
|
195
|
+
// A one- or two-character folder name would match far too many window titles
|
|
196
|
+
// to be safe to close on.
|
|
197
|
+
if (!spec || folderName.trim().length < 3)
|
|
198
|
+
return;
|
|
199
|
+
try {
|
|
200
|
+
if (process.platform === "darwin") {
|
|
201
|
+
fireAndForget("osascript", ["-e", macCloseScript(spec.macProcess, folderName)]);
|
|
202
|
+
}
|
|
203
|
+
else if (process.platform === "win32") {
|
|
204
|
+
fireAndForget("powershell.exe", [
|
|
205
|
+
"-NoProfile",
|
|
206
|
+
"-NonInteractive",
|
|
207
|
+
"-Command",
|
|
208
|
+
winCloseScript(spec.processName, folderName),
|
|
209
|
+
]);
|
|
210
|
+
}
|
|
211
|
+
else {
|
|
212
|
+
// The X11 route needs the window list back before it can close anything,
|
|
213
|
+
// so it is the one synchronous path — wmctrl answers immediately and asks
|
|
214
|
+
// no permission of anyone.
|
|
215
|
+
linuxCloseWindow(spec.processName, folderName);
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
catch {
|
|
219
|
+
// Silent by design — see the file header.
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
//# sourceMappingURL=editor.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"editor.js","sourceRoot":"","sources":["../../src/lib/editor.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Open a URL in the candidate's default browser. Best-effort, cross-platform.
|
|
3
|
+
*
|
|
4
|
+
* Returns whether the opener was launched — NOT whether a page appeared, which
|
|
5
|
+
* we can't know. Callers that are about to take the URL off the candidate's
|
|
6
|
+
* screen use that answer to decide whether it is safe to do so.
|
|
7
|
+
*/
|
|
8
|
+
export declare function openUrl(url: string, onFailure?: (url: string) => void): boolean;
|
|
9
|
+
//# sourceMappingURL=open-url.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"open-url.d.ts","sourceRoot":"","sources":["../../src/lib/open-url.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAIH,wBAAgB,OAAO,CAAC,GAAG,EAAE,MAAM,EAAE,SAAS,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,GAAG,OAAO,CAqB/E"}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Open a URL in the candidate's default browser. Best-effort, cross-platform.
|
|
3
|
+
*
|
|
4
|
+
* Returns whether the opener was launched — NOT whether a page appeared, which
|
|
5
|
+
* we can't know. Callers that are about to take the URL off the candidate's
|
|
6
|
+
* screen use that answer to decide whether it is safe to do so.
|
|
7
|
+
*/
|
|
8
|
+
import { spawn } from "child_process";
|
|
9
|
+
export function openUrl(url, onFailure) {
|
|
10
|
+
const spec = process.platform === "darwin"
|
|
11
|
+
? ["open", [url]]
|
|
12
|
+
: process.platform === "win32"
|
|
13
|
+
? ["cmd", ["/c", "start", "", url]]
|
|
14
|
+
: ["xdg-open", [url]];
|
|
15
|
+
try {
|
|
16
|
+
const child = spawn(spec[0], spec[1], { detached: true, stdio: "ignore" });
|
|
17
|
+
// spawn reports a missing opener ASYNCHRONOUSLY via 'error', not a sync
|
|
18
|
+
// throw — without this listener an ENOENT (no xdg-open on a minimal Linux /
|
|
19
|
+
// WSL / headless box) becomes an uncaughtException that crashes the CLI
|
|
20
|
+
// right after a success line. The try/catch alone cannot see it.
|
|
21
|
+
child.on("error", () => onFailure?.(url));
|
|
22
|
+
child.unref();
|
|
23
|
+
return true;
|
|
24
|
+
}
|
|
25
|
+
catch {
|
|
26
|
+
onFailure?.(url);
|
|
27
|
+
return false;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
//# sourceMappingURL=open-url.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"open-url.js","sourceRoot":"","sources":["../../src/lib/open-url.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,EAAE,KAAK,EAAE,MAAM,eAAe,CAAA;AAErC,MAAM,UAAU,OAAO,CAAC,GAAW,EAAE,SAAiC;IACpE,MAAM,IAAI,GACR,OAAO,CAAC,QAAQ,KAAK,QAAQ;QAC3B,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC;QACjB,CAAC,CAAC,OAAO,CAAC,QAAQ,KAAK,OAAO;YAC5B,CAAC,CAAC,CAAC,KAAK,EAAE,CAAC,IAAI,EAAE,OAAO,EAAE,EAAE,EAAE,GAAG,CAAC,CAAC;YACnC,CAAC,CAAC,CAAC,UAAU,EAAE,CAAC,GAAG,CAAC,CAAC,CAAA;IAE3B,IAAI,CAAC;QACH,MAAM,KAAK,GAAG,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,QAAQ,EAAE,IAAI,EAAE,KAAK,EAAE,QAAQ,EAAE,CAAC,CAAA;QAC1E,wEAAwE;QACxE,4EAA4E;QAC5E,wEAAwE;QACxE,iEAAiE;QACjE,KAAK,CAAC,EAAE,CAAC,OAAO,EAAE,GAAG,EAAE,CAAC,SAAS,EAAE,CAAC,GAAG,CAAC,CAAC,CAAA;QACzC,KAAK,CAAC,KAAK,EAAE,CAAA;QACb,OAAO,IAAI,CAAA;IACb,CAAC;IAAC,MAAM,CAAC;QACP,SAAS,EAAE,CAAC,GAAG,CAAC,CAAA;QAChB,OAAO,KAAK,CAAA;IACd,CAAC;AACH,CAAC"}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A single-choice arrow-key picker for the candidate-facing prompts.
|
|
3
|
+
*
|
|
4
|
+
* Written by hand rather than pulled from a prompt library: the CLI ships to
|
|
5
|
+
* candidates' machines, so every dependency is one more thing that can fail to
|
|
6
|
+
* install on an unknown Node/npm. The surface here is one function.
|
|
7
|
+
*
|
|
8
|
+
* Non-interactive stdin (piped, CI, the watcher's auto-submit) never blocks —
|
|
9
|
+
* `select` resolves to null immediately and the caller falls back to printing
|
|
10
|
+
* instructions.
|
|
11
|
+
*/
|
|
12
|
+
export interface SelectChoice<T> {
|
|
13
|
+
value: T;
|
|
14
|
+
label: string;
|
|
15
|
+
/** Dim trailing text — e.g. "not installed", "recommended". */
|
|
16
|
+
hint?: string;
|
|
17
|
+
}
|
|
18
|
+
export type SelectKey = "up" | "down" | "select" | "abort";
|
|
19
|
+
/**
|
|
20
|
+
* Escape sequence → intent. Exported for tests; there is no other way to
|
|
21
|
+
* exercise key handling without a real TTY.
|
|
22
|
+
*
|
|
23
|
+
* A bare ESC deliberately means nothing. An arrow key IS an ESC followed by two
|
|
24
|
+
* more bytes, and a terminal is free to deliver those bytes in separate reads —
|
|
25
|
+
* so a lone ESC is far more likely to be half an arrow than a candidate asking
|
|
26
|
+
* to dismiss the picker. Dismissing has its own row in the list; navigation
|
|
27
|
+
* that closes the picker at random does not.
|
|
28
|
+
*/
|
|
29
|
+
export declare function decodeKey(seq: string): SelectKey | null;
|
|
30
|
+
/**
|
|
31
|
+
* Pull every complete keypress out of the bytes read so far, and hand back
|
|
32
|
+
* whatever trailing fragment is still the start of a sequence.
|
|
33
|
+
*
|
|
34
|
+
* Reads are not keypresses in either direction: holding an arrow key down
|
|
35
|
+
* coalesces several presses into one read, and a slow link or a multiplexer can
|
|
36
|
+
* split a single arrow across two. The caller carries `pending` into the next
|
|
37
|
+
* read, so a split arrow still arrives as one "up".
|
|
38
|
+
*
|
|
39
|
+
* A fragment that turns out NOT to be a sequence start — ESC then a plain
|
|
40
|
+
* letter — drops just the ESC and re-reads the rest, so the letter is not
|
|
41
|
+
* swallowed along with it.
|
|
42
|
+
*/
|
|
43
|
+
export declare function consumeKeys(buffer: string): {
|
|
44
|
+
keys: SelectKey[];
|
|
45
|
+
pending: string;
|
|
46
|
+
};
|
|
47
|
+
/**
|
|
48
|
+
* The rendered block, one string per terminal line. Pure, so tests can assert
|
|
49
|
+
* which row carries the pointer without driving a TTY.
|
|
50
|
+
*/
|
|
51
|
+
export declare function renderBlock<T>(title: string, choices: SelectChoice<T>[], active: number): string[];
|
|
52
|
+
/** Whether we can drive a picker at all: both ends must be a TTY. */
|
|
53
|
+
export declare function isInteractive(): boolean;
|
|
54
|
+
/**
|
|
55
|
+
* Show `title` above `choices` and let the candidate move with ↑/↓ and confirm
|
|
56
|
+
* with enter. Resolves to the chosen value, or to null when the picker can't
|
|
57
|
+
* run at all (non-TTY). Ctrl-C exits with 130 — the same thing an un-swallowed
|
|
58
|
+
* SIGINT would have done.
|
|
59
|
+
*
|
|
60
|
+
* The block is erased on exit, so the caller owns what the candidate is left
|
|
61
|
+
* looking at.
|
|
62
|
+
*/
|
|
63
|
+
export declare function select<T>(title: string, choices: SelectChoice<T>[]): Promise<T | null>;
|
|
64
|
+
//# sourceMappingURL=select.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"select.d.ts","sourceRoot":"","sources":["../../src/lib/select.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAQH,MAAM,WAAW,YAAY,CAAC,CAAC;IAC7B,KAAK,EAAE,CAAC,CAAA;IACR,KAAK,EAAE,MAAM,CAAA;IACb,+DAA+D;IAC/D,IAAI,CAAC,EAAE,MAAM,CAAA;CACd;AAED,MAAM,MAAM,SAAS,GAAG,IAAI,GAAG,MAAM,GAAG,QAAQ,GAAG,OAAO,CAAA;AAK1D;;;;;;;;;GASG;AACH,wBAAgB,SAAS,CAAC,GAAG,EAAE,MAAM,GAAG,SAAS,GAAG,IAAI,CAkBvD;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,WAAW,CAAC,MAAM,EAAE,MAAM,GAAG;IAAE,IAAI,EAAE,SAAS,EAAE,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,CAgBlF;AAED;;;GAGG;AACH,wBAAgB,WAAW,CAAC,CAAC,EAAE,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,YAAY,CAAC,CAAC,CAAC,EAAE,EAAE,MAAM,EAAE,MAAM,GAAG,MAAM,EAAE,CAelG;AAED,qEAAqE;AACrE,wBAAgB,aAAa,IAAI,OAAO,CAEvC;AAED;;;;;;;;GAQG;AACH,wBAAgB,MAAM,CAAC,CAAC,EAAE,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,YAAY,CAAC,CAAC,CAAC,EAAE,GAAG,OAAO,CAAC,CAAC,GAAG,IAAI,CAAC,CA4DtF"}
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A single-choice arrow-key picker for the candidate-facing prompts.
|
|
3
|
+
*
|
|
4
|
+
* Written by hand rather than pulled from a prompt library: the CLI ships to
|
|
5
|
+
* candidates' machines, so every dependency is one more thing that can fail to
|
|
6
|
+
* install on an unknown Node/npm. The surface here is one function.
|
|
7
|
+
*
|
|
8
|
+
* Non-interactive stdin (piped, CI, the watcher's auto-submit) never blocks —
|
|
9
|
+
* `select` resolves to null immediately and the caller falls back to printing
|
|
10
|
+
* instructions.
|
|
11
|
+
*/
|
|
12
|
+
import { stdin, stdout } from "process";
|
|
13
|
+
import chalk from "chalk";
|
|
14
|
+
const ESC = "\u001b";
|
|
15
|
+
const CTRL_C = "\u0003";
|
|
16
|
+
/** The starts of sequences we understand — held back rather than acted on. */
|
|
17
|
+
const SEQUENCE_PREFIXES = new Set([ESC, `${ESC}[`, `${ESC}O`]);
|
|
18
|
+
/**
|
|
19
|
+
* Escape sequence → intent. Exported for tests; there is no other way to
|
|
20
|
+
* exercise key handling without a real TTY.
|
|
21
|
+
*
|
|
22
|
+
* A bare ESC deliberately means nothing. An arrow key IS an ESC followed by two
|
|
23
|
+
* more bytes, and a terminal is free to deliver those bytes in separate reads —
|
|
24
|
+
* so a lone ESC is far more likely to be half an arrow than a candidate asking
|
|
25
|
+
* to dismiss the picker. Dismissing has its own row in the list; navigation
|
|
26
|
+
* that closes the picker at random does not.
|
|
27
|
+
*/
|
|
28
|
+
export function decodeKey(seq) {
|
|
29
|
+
switch (seq) {
|
|
30
|
+
case `${ESC}[A`: // arrow up, normal mode
|
|
31
|
+
case `${ESC}OA`: // arrow up, application mode (tmux, some terminals)
|
|
32
|
+
case "k":
|
|
33
|
+
return "up";
|
|
34
|
+
case `${ESC}[B`:
|
|
35
|
+
case `${ESC}OB`:
|
|
36
|
+
case "j":
|
|
37
|
+
return "down";
|
|
38
|
+
case "\r":
|
|
39
|
+
case "\n":
|
|
40
|
+
return "select";
|
|
41
|
+
case CTRL_C: // raw mode swallows SIGINT, so we handle Ctrl-C ourselves
|
|
42
|
+
return "abort";
|
|
43
|
+
default:
|
|
44
|
+
return null;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Pull every complete keypress out of the bytes read so far, and hand back
|
|
49
|
+
* whatever trailing fragment is still the start of a sequence.
|
|
50
|
+
*
|
|
51
|
+
* Reads are not keypresses in either direction: holding an arrow key down
|
|
52
|
+
* coalesces several presses into one read, and a slow link or a multiplexer can
|
|
53
|
+
* split a single arrow across two. The caller carries `pending` into the next
|
|
54
|
+
* read, so a split arrow still arrives as one "up".
|
|
55
|
+
*
|
|
56
|
+
* A fragment that turns out NOT to be a sequence start — ESC then a plain
|
|
57
|
+
* letter — drops just the ESC and re-reads the rest, so the letter is not
|
|
58
|
+
* swallowed along with it.
|
|
59
|
+
*/
|
|
60
|
+
export function consumeKeys(buffer) {
|
|
61
|
+
const keys = [];
|
|
62
|
+
let rest = buffer;
|
|
63
|
+
while (rest.length > 0) {
|
|
64
|
+
const three = rest.length >= 3 ? decodeKey(rest.slice(0, 3)) : null;
|
|
65
|
+
if (three) {
|
|
66
|
+
keys.push(three);
|
|
67
|
+
rest = rest.slice(3);
|
|
68
|
+
continue;
|
|
69
|
+
}
|
|
70
|
+
if (SEQUENCE_PREFIXES.has(rest))
|
|
71
|
+
break; // incomplete — wait for the rest
|
|
72
|
+
const one = decodeKey(rest[0]);
|
|
73
|
+
if (one)
|
|
74
|
+
keys.push(one);
|
|
75
|
+
rest = rest.slice(1);
|
|
76
|
+
}
|
|
77
|
+
return { keys, pending: rest };
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* The rendered block, one string per terminal line. Pure, so tests can assert
|
|
81
|
+
* which row carries the pointer without driving a TTY.
|
|
82
|
+
*/
|
|
83
|
+
export function renderBlock(title, choices, active) {
|
|
84
|
+
const rows = choices.map((c, i) => {
|
|
85
|
+
const on = i === active;
|
|
86
|
+
const pointer = on ? chalk.cyan("❯") : " ";
|
|
87
|
+
const label = on ? chalk.cyan.bold(c.label) : c.label;
|
|
88
|
+
const hint = c.hint ? " " + chalk.dim(c.hint) : "";
|
|
89
|
+
return ` ${pointer} ${label}${hint}`;
|
|
90
|
+
});
|
|
91
|
+
return [
|
|
92
|
+
` ${chalk.bold(title)}`,
|
|
93
|
+
"",
|
|
94
|
+
...rows,
|
|
95
|
+
"",
|
|
96
|
+
chalk.dim(" ↑/↓ to move · enter to choose"),
|
|
97
|
+
];
|
|
98
|
+
}
|
|
99
|
+
/** Whether we can drive a picker at all: both ends must be a TTY. */
|
|
100
|
+
export function isInteractive() {
|
|
101
|
+
return Boolean(stdin.isTTY && stdout.isTTY && typeof stdin.setRawMode === "function");
|
|
102
|
+
}
|
|
103
|
+
/**
|
|
104
|
+
* Show `title` above `choices` and let the candidate move with ↑/↓ and confirm
|
|
105
|
+
* with enter. Resolves to the chosen value, or to null when the picker can't
|
|
106
|
+
* run at all (non-TTY). Ctrl-C exits with 130 — the same thing an un-swallowed
|
|
107
|
+
* SIGINT would have done.
|
|
108
|
+
*
|
|
109
|
+
* The block is erased on exit, so the caller owns what the candidate is left
|
|
110
|
+
* looking at.
|
|
111
|
+
*/
|
|
112
|
+
export function select(title, choices) {
|
|
113
|
+
if (!isInteractive() || choices.length === 0)
|
|
114
|
+
return Promise.resolve(null);
|
|
115
|
+
let active = 0;
|
|
116
|
+
const lineCount = renderBlock(title, choices, active).length;
|
|
117
|
+
const paint = (first) => {
|
|
118
|
+
if (!first)
|
|
119
|
+
stdout.write(`${ESC}[${lineCount}A`);
|
|
120
|
+
for (const line of renderBlock(title, choices, active)) {
|
|
121
|
+
stdout.write(`${ESC}[2K${line}\n`);
|
|
122
|
+
}
|
|
123
|
+
};
|
|
124
|
+
const erase = () => {
|
|
125
|
+
stdout.write(`${ESC}[${lineCount}A${ESC}[0J`);
|
|
126
|
+
};
|
|
127
|
+
return new Promise((resolve) => {
|
|
128
|
+
const wasRaw = stdin.isRaw;
|
|
129
|
+
stdout.write(`${ESC}[?25l`); // hide cursor
|
|
130
|
+
stdin.setRawMode(true);
|
|
131
|
+
stdin.resume();
|
|
132
|
+
stdin.setEncoding("utf8");
|
|
133
|
+
paint(true);
|
|
134
|
+
const cleanup = () => {
|
|
135
|
+
stdin.off("data", onData);
|
|
136
|
+
stdin.setRawMode(Boolean(wasRaw));
|
|
137
|
+
stdin.pause();
|
|
138
|
+
stdout.write(`${ESC}[?25h`); // show cursor
|
|
139
|
+
};
|
|
140
|
+
// Carries a half-delivered escape sequence from one read to the next.
|
|
141
|
+
let pending = "";
|
|
142
|
+
const onData = (chunk) => {
|
|
143
|
+
const read = consumeKeys(pending + chunk);
|
|
144
|
+
pending = read.pending;
|
|
145
|
+
for (const key of read.keys) {
|
|
146
|
+
if (key === "up") {
|
|
147
|
+
active = (active - 1 + choices.length) % choices.length;
|
|
148
|
+
paint(false);
|
|
149
|
+
}
|
|
150
|
+
else if (key === "down") {
|
|
151
|
+
active = (active + 1) % choices.length;
|
|
152
|
+
paint(false);
|
|
153
|
+
}
|
|
154
|
+
else if (key === "select") {
|
|
155
|
+
erase();
|
|
156
|
+
cleanup();
|
|
157
|
+
resolve(choices[active].value);
|
|
158
|
+
return;
|
|
159
|
+
}
|
|
160
|
+
else if (key === "abort") {
|
|
161
|
+
erase();
|
|
162
|
+
cleanup();
|
|
163
|
+
process.exit(130);
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
};
|
|
167
|
+
stdin.on("data", onData);
|
|
168
|
+
});
|
|
169
|
+
}
|
|
170
|
+
//# sourceMappingURL=select.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"select.js","sourceRoot":"","sources":["../../src/lib/select.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,OAAO,EAAE,KAAK,EAAE,MAAM,EAAE,MAAM,SAAS,CAAA;AACvC,OAAO,KAAK,MAAM,OAAO,CAAA;AAEzB,MAAM,GAAG,GAAG,QAAQ,CAAA;AACpB,MAAM,MAAM,GAAG,QAAQ,CAAA;AAWvB,8EAA8E;AAC9E,MAAM,iBAAiB,GAAG,IAAI,GAAG,CAAC,CAAC,GAAG,EAAE,GAAG,GAAG,GAAG,EAAE,GAAG,GAAG,GAAG,CAAC,CAAC,CAAA;AAE9D;;;;;;;;;GASG;AACH,MAAM,UAAU,SAAS,CAAC,GAAW;IACnC,QAAQ,GAAG,EAAE,CAAC;QACZ,KAAK,GAAG,GAAG,IAAI,CAAC,CAAC,wBAAwB;QACzC,KAAK,GAAG,GAAG,IAAI,CAAC,CAAC,oDAAoD;QACrE,KAAK,GAAG;YACN,OAAO,IAAI,CAAA;QACb,KAAK,GAAG,GAAG,IAAI,CAAC;QAChB,KAAK,GAAG,GAAG,IAAI,CAAC;QAChB,KAAK,GAAG;YACN,OAAO,MAAM,CAAA;QACf,KAAK,IAAI,CAAC;QACV,KAAK,IAAI;YACP,OAAO,QAAQ,CAAA;QACjB,KAAK,MAAM,EAAE,0DAA0D;YACrE,OAAO,OAAO,CAAA;QAChB;YACE,OAAO,IAAI,CAAA;IACf,CAAC;AACH,CAAC;AAED;;;;;;;;;;;;GAYG;AACH,MAAM,UAAU,WAAW,CAAC,MAAc;IACxC,MAAM,IAAI,GAAgB,EAAE,CAAA;IAC5B,IAAI,IAAI,GAAG,MAAM,CAAA;IACjB,OAAO,IAAI,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QACvB,MAAM,KAAK,GAAG,IAAI,CAAC,MAAM,IAAI,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAA;QACnE,IAAI,KAAK,EAAE,CAAC;YACV,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,CAAA;YAChB,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAA;YACpB,SAAQ;QACV,CAAC;QACD,IAAI,iBAAiB,CAAC,GAAG,CAAC,IAAI,CAAC;YAAE,MAAK,CAAC,iCAAiC;QACxE,MAAM,GAAG,GAAG,SAAS,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAA;QAC9B,IAAI,GAAG;YAAE,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,CAAA;QACvB,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAA;IACtB,CAAC;IACD,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,IAAI,EAAE,CAAA;AAChC,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,WAAW,CAAI,KAAa,EAAE,OAA0B,EAAE,MAAc;IACtF,MAAM,IAAI,GAAG,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE;QAChC,MAAM,EAAE,GAAG,CAAC,KAAK,MAAM,CAAA;QACvB,MAAM,OAAO,GAAG,EAAE,CAAC,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAA;QAC1C,MAAM,KAAK,GAAG,EAAE,CAAC,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,KAAK,CAAA;QACrD,MAAM,IAAI,GAAG,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,GAAG,GAAG,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,EAAE,CAAA;QAClD,OAAO,KAAK,OAAO,IAAI,KAAK,GAAG,IAAI,EAAE,CAAA;IACvC,CAAC,CAAC,CAAA;IACF,OAAO;QACL,KAAK,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,EAAE;QACxB,EAAE;QACF,GAAG,IAAI;QACP,EAAE;QACF,KAAK,CAAC,GAAG,CAAC,iCAAiC,CAAC;KAC7C,CAAA;AACH,CAAC;AAED,qEAAqE;AACrE,MAAM,UAAU,aAAa;IAC3B,OAAO,OAAO,CAAC,KAAK,CAAC,KAAK,IAAI,MAAM,CAAC,KAAK,IAAI,OAAO,KAAK,CAAC,UAAU,KAAK,UAAU,CAAC,CAAA;AACvF,CAAC;AAED;;;;;;;;GAQG;AACH,MAAM,UAAU,MAAM,CAAI,KAAa,EAAE,OAA0B;IACjE,IAAI,CAAC,aAAa,EAAE,IAAI,OAAO,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,OAAO,CAAC,OAAO,CAAC,IAAI,CAAC,CAAA;IAE1E,IAAI,MAAM,GAAG,CAAC,CAAA;IACd,MAAM,SAAS,GAAG,WAAW,CAAC,KAAK,EAAE,OAAO,EAAE,MAAM,CAAC,CAAC,MAAM,CAAA;IAE5D,MAAM,KAAK,GAAG,CAAC,KAAc,EAAE,EAAE;QAC/B,IAAI,CAAC,KAAK;YAAE,MAAM,CAAC,KAAK,CAAC,GAAG,GAAG,IAAI,SAAS,GAAG,CAAC,CAAA;QAChD,KAAK,MAAM,IAAI,IAAI,WAAW,CAAC,KAAK,EAAE,OAAO,EAAE,MAAM,CAAC,EAAE,CAAC;YACvD,MAAM,CAAC,KAAK,CAAC,GAAG,GAAG,MAAM,IAAI,IAAI,CAAC,CAAA;QACpC,CAAC;IACH,CAAC,CAAA;IAED,MAAM,KAAK,GAAG,GAAG,EAAE;QACjB,MAAM,CAAC,KAAK,CAAC,GAAG,GAAG,IAAI,SAAS,IAAI,GAAG,KAAK,CAAC,CAAA;IAC/C,CAAC,CAAA;IAED,OAAO,IAAI,OAAO,CAAW,CAAC,OAAO,EAAE,EAAE;QACvC,MAAM,MAAM,GAAG,KAAK,CAAC,KAAK,CAAA;QAC1B,MAAM,CAAC,KAAK,CAAC,GAAG,GAAG,OAAO,CAAC,CAAA,CAAC,cAAc;QAC1C,KAAK,CAAC,UAAU,CAAC,IAAI,CAAC,CAAA;QACtB,KAAK,CAAC,MAAM,EAAE,CAAA;QACd,KAAK,CAAC,WAAW,CAAC,MAAM,CAAC,CAAA;QACzB,KAAK,CAAC,IAAI,CAAC,CAAA;QAEX,MAAM,OAAO,GAAG,GAAG,EAAE;YACnB,KAAK,CAAC,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;YACzB,KAAK,CAAC,UAAU,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,CAAA;YACjC,KAAK,CAAC,KAAK,EAAE,CAAA;YACb,MAAM,CAAC,KAAK,CAAC,GAAG,GAAG,OAAO,CAAC,CAAA,CAAC,cAAc;QAC5C,CAAC,CAAA;QAED,sEAAsE;QACtE,IAAI,OAAO,GAAG,EAAE,CAAA;QAEhB,MAAM,MAAM,GAAG,CAAC,KAAa,EAAE,EAAE;YAC/B,MAAM,IAAI,GAAG,WAAW,CAAC,OAAO,GAAG,KAAK,CAAC,CAAA;YACzC,OAAO,GAAG,IAAI,CAAC,OAAO,CAAA;YACtB,KAAK,MAAM,GAAG,IAAI,IAAI,CAAC,IAAI,EAAE,CAAC;gBAC5B,IAAI,GAAG,KAAK,IAAI,EAAE,CAAC;oBACjB,MAAM,GAAG,CAAC,MAAM,GAAG,CAAC,GAAG,OAAO,CAAC,MAAM,CAAC,GAAG,OAAO,CAAC,MAAM,CAAA;oBACvD,KAAK,CAAC,KAAK,CAAC,CAAA;gBACd,CAAC;qBAAM,IAAI,GAAG,KAAK,MAAM,EAAE,CAAC;oBAC1B,MAAM,GAAG,CAAC,MAAM,GAAG,CAAC,CAAC,GAAG,OAAO,CAAC,MAAM,CAAA;oBACtC,KAAK,CAAC,KAAK,CAAC,CAAA;gBACd,CAAC;qBAAM,IAAI,GAAG,KAAK,QAAQ,EAAE,CAAC;oBAC5B,KAAK,EAAE,CAAA;oBACP,OAAO,EAAE,CAAA;oBACT,OAAO,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,KAAK,CAAC,CAAA;oBAC9B,OAAM;gBACR,CAAC;qBAAM,IAAI,GAAG,KAAK,OAAO,EAAE,CAAC;oBAC3B,KAAK,EAAE,CAAA;oBACP,OAAO,EAAE,CAAA;oBACT,OAAO,CAAC,IAAI,CAAC,GAAG,CAAC,CAAA;gBACnB,CAAC;YACH,CAAC;QACH,CAAC,CAAA;QAED,KAAK,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;IAC1B,CAAC,CAAC,CAAA;AACJ,CAAC"}
|